知識點
- uri類
- ContentProvider,ContentResolver
- 實現跨進程訪問數據
一、Uri類
- uri定義:Uniform Resource Identifier,即統一資源標識符
- uri作用:外界進程通過 uri找到對應的contentProvider& 其中的數據,再進行數據操作
-
uri詳解:
uri分爲 系統預置 & 自定義,分別對應系統內置的數據(如通訊錄、日程表等等)和自定義數據庫
自定義的一般格式如下圖:
自定義uri的栗子
設置URI
Uri uri = Uri.parse("content://com.returnTolife.provider/test/1") ;
上述URI指向的資源是:名爲 `com.returnTolife.provider`的`ContentProvider` 中表名 爲`test` 中的 `id`爲1的數據
特別注意:URI模式存在匹配通配符* & #
*:匹配任意長度的任何有效字符的字符串
以下的URI 表示 匹配provider的任何內容
"content://com.returnTolife.provider/* "
#:匹配任意長度的數字字符的字符串
以下的URI 表示 匹配provider中的test表的所有行
"content://com.returnTolife.provider/test/# "
二、ContentProvider和ContentResolver
- 關於ContentProvider:由於進程間共享數據的本質是:添加、刪除、獲取 & 修改(更新)數據,所以ContentProvider 其中的核心方法主要如下:
該方法在ContentProvider被創建後調用,當其他應用程序第一次訪問ContentProvider時,該ContentProvider被創建。
public boolean onCreate():
根據Uri查詢出selection條件所匹配的全部記錄
public Cursor query():
該方法返回當前Uri所代表的數據的MIME類型。如果該Uri對應的數據可能包括多條記錄,那麼MIME類型字符串應該以vnd.android.cursor.dir/開頭,單條記錄則爲vnd.android.cursor.item/開頭;
個人理解該方法返回的MIME在android主要是用來做頁面跳轉
public String getType()
根據該Uri插入values對應的數據。
public Uri insert():
根據Uri刪除selection條件所匹配的全部記錄。
public int delete():
根據Uri修改selection條件所匹配的全部記錄
public int update():
1.1 關於getType()的作用可以參考ContentProvider數據庫共享之——MIME類型與getType() ,文章中寫得很詳細,而且清晰易懂.
1.2 ContentProvider類並不會直接與外部進程交互,而是通過ContentResolver 類
-
關於ContentResolver :外部進程通過 ContentResolver類 從而與ContentProvider類進行交互,原因是:如果一款應用要使用多個ContentProvider,若需要了解每個ContentProvider的不同實現從而再完成數據交互,操作成本高並且難度會比較大所以再ContentProvider類上加多了一個 ContentResolver類對所有的ContentProvider進行統一管理。
使用栗子
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://com.returnTolife.provider/test/");
Cursor cursor = resolver.query(uri, null, null, null, null);
//其他方法類似,不重複
-
UriMatcher類:該類爲contentProvider的輔助類,主要用於將一個uri地址與provider進行綁定
栗子
public static final int TABLE_STUDENT_CODE = 1;
public static final int TABLE_BOOKE_CODE = 2;
static{
//參數表示初始化時不匹配任何東西
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
//matcher .addURI的作用是在後面使用matcher.match(string s)匹配的時候,如果匹配到/student"時返回TABLE_STUDENT_CODE即數值1,匹配“/book”時返回MATCH_SECOND
matcher .addURI(AUTHORITY, "student", TABLE_STUDENT_CODE);
matcher .addURI(AUTHORITY, "book", TABLE_BOOKE_CODE)
}
三、實例
- 數據準備,先創建一個數據庫
public class MyDbHelper extends SQLiteOpenHelper {
private static final String DB_NAME="myprovider.db";
public static final String TABLE_SUTEMT="student";
public static final String TABLE_BOOK="book";
private static final int VERSION=1;
public MyDbHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_SUTEMT + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " name TEXT)");
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_BOOK + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " name TEXT)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
- 新建一個MyContentProvider類,繼承ContentProvider類
public class MyContentProvider extends ContentProvider {
private Context mContext;
private MyDbHelper mMyDbHelper;
// 設置ContentProvider的唯一標識
public static final String AUTOHORITY = "com.returntolife.myprovider";
public static final int TABLE_STUDENT_CODE = 1;
public static final int TABLE_BOOKE_CODE = 2;
// UriMatcher類使用:在ContentProvider 中註冊URI
private static final UriMatcher mMatcher;
SQLiteDatabase db = null;
//重要的一步,將uri和provider綁定
static {
mMatcher=new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(AUTOHORITY,MyDbHelper.TABLE_SUTEMT, TABLE_STUDENT_CODE);
mMatcher.addURI(AUTOHORITY, MyDbHelper.TABLE_BOOK, TABLE_BOOKE_CODE);
}
@Override
public boolean onCreate() {
mContext=getContext();
mMyDbHelper=new MyDbHelper(mContext);
db=mMyDbHelper.getWritableDatabase();
// 初始化兩個表的數據(先清空兩個表,再各加入一個記錄)
db.execSQL("delete from student");
db.execSQL("insert into student values(1,'張三');");
db.execSQL("insert into student values(2,'李四');");
db.execSQL("delete from book");
db.execSQL("insert into book values(1,'Android');");
db.execSQL("insert into book values(2,'iOS');");
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
String table=getTableName(uri);
return db.query(table,null,null,null,null,null,null);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
LogUtil.d("getType uri="+uri);
return "vnd.android.cursor.dir/harvic.first";
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
String table=getTableName(uri);
db.insert(table,null,values);
// 當該URI的ContentProvider數據發生變化時,通知外界(即訪問該ContentProvider數據的訪問者)
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
/**
* 根據URI匹配 URI_CODE,從而匹配ContentProvider中相應的表名
*/
private String getTableName(Uri uri){
String tableName = null;
switch (mMatcher.match(uri)) {
case TABLE_STUDENT_CODE:
tableName = MyDbHelper.TABLE_SUTEMT;
break;
case TABLE_BOOKE_CODE:
tableName = MyDbHelper.TABLE_BOOK;
break;
default:
break;
}
return tableName;
}
}
- 在AndroidManifest.xml文件註冊這個ContentProvider,綁定一個Uri
<provider
android:authorities="com.returntolife.myprovider"
android:name=".mycontentprovider.MyContentProvider"/>
- 自身內部訪問直接通過ContentResolver就可以訪問,不用添加什麼權限
private void getData() {
ContentResolver contentResolver=getContentResolver();
Uri uri=Uri.parse("content://com.returntolife.myprovider/student");
Cursor cursor=contentResolver.query(uri,new String[]{"_id,name"},null,null,null);
if(cursor!=null){
while(cursor.moveToNext()){
LogUtil.d("id="+cursor.getInt(0)+"--name="+cursor.getString(1));
tvTest.append("id="+cursor.getInt(0)+"--name="+cursor.getString(1)+"\n");
}
cursor.close();
}
Uri uriBook=Uri.parse("content://com.returntolife.myprovider/book");
Cursor cursorBook=contentResolver.query(uriBook,new String[]{"_id,name"},null,null,null);
if(cursorBook!=null){
while(cursorBook.moveToNext()){
LogUtil.d("id="+cursorBook.getInt(0)+"--name="+cursorBook.getString(1));
}
cursorBook.close();
}
}
- 外部進程訪問需要定義和添加權限
5.1 提供數據的進程(進程1)中自定義一個訪問權限,並設置到對應的provider中,如下
<permission
android:name="com.returntolife.jjconde.providertestservice.provider"
android:protectionLevel="normal" />
<provider
android:name=".mycontentprovider.MyContentProvider"
android:authorities="com.returntolife.myprovider"
android:exported="true"
android:permission="com.returntolife.myprovider.permission" />
5.2 需要訪問數據的進程(進程2)中添加上面定義的訪問權限,然後其餘獲取數據基本和應用自身獲取數據一致
<uses-permission android:name="com.returntolife.myprovider.permission"/>
// 設置URI
Uri uri=Uri.parse("content://com.returntolife.myprovider/student");
// 插入表中數據
// ContentValues values = new ContentValues();
// values.put("_id", 10);
// values.put("name", "Jordan");
// 獲取ContentResolver
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(uri, new String[]{"_id","name"}, null, null, null);
if(cursor!=null){
while (cursor.moveToNext()){
Log.d("MainActivity","id="+cursor.getInt(0)+"--name="+cursor.getString(1)+"\n");
}
cursor.close();
}
總結
- contentProvider的難點主要是在理解uri的定義,以及如何將uri和provider綁定,訪問方式其實不難
- contentProvider實現跨進程通信相對於AIDL的通信方式,會簡潔方便很多
參考文章
Android:關於ContentProvider的知識都在這裏了!
ContentProvider數據庫共享之——MIME類型與getType()