如果你公司開發了多款應用且應用間需要共享數據,如果你的應用中存在android:process=”:remote”這樣的多進程的操作,是否還在憂愁如何傳遞數據這時候ContentProvider就可以派上用場了,貴爲四大組件之一專門爲不同應用不同進程共享數據使用。
首先我們需要了解URI的結構,因爲ContentProvider每一個操作都跟URI有關係。
content://com.neacy.provider/books
一個典型的Uri結構的構造是以content://開頭的然後接着是用於定位ContentProvider的唯一表示符,然後是路徑標示,所以大體的結構體是:
content://authority-name/path-segment1/path-segment2/...
既然知道了URI的構造後如何解析呢?這裏就需要引入UriMatcher類。
/**
* 定義Uri匹配
*/
private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final int BOOK_COLLECT = 1;
private static final int BOOK_SINGLE = 2;
static {
mUriMatcher.addURI(BookProviderMetaData.AUTHORITY, "books", BOOK_COLLECT);
mUriMatcher.addURI(BookProviderMetaData.AUTHORITY, "books/#", BOOK_SINGLE);
}
你告訴實例需要什麼樣的URI模式,並將對應的唯一標識符與每一個模式進行綁定,註冊完這些模式之後,UriMatcher就是根據你傳入的URI來進行模式匹配從而執行具體的業務代碼。
switch (mUriMatcher.match(uri)) {
case BOOK_COLLECT:
//do something
break;
case BOOK_SINGLE:
//do something
break;
}
我們實例中採用的SQLite數據庫,所以我們需要先定義一個數據庫需要用到的常量元數據類:
public class BookProviderMetaData {
public static final String AUTHORITY = "com.neacy.provider";
public static final String DATABASE_NAME = "book.db";
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_TABLE = "books";
/**
* BaseColums內部自己提供了一個_id字段
*/
public static final class BookTableMetaData implements BaseColumns {
public static final String TABLE_NAME = "books";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/books");
public static final String BOOK_NAME = "name";
public static final String BOOK_ISBN = "isbn";
public static final String BOOK_AUTHOR = "author";
}
}
然後再構建數據庫幫助類:
private static class BookDataHelper extends SQLiteOpenHelper {
public BookDataHelper(Context context) {
super(context, BookProviderMetaData.DATABASE_NAME, null, BookProviderMetaData.DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table books (" +
BookProviderMetaData.BookTableMetaData._ID + " integer primary key, " +
BookProviderMetaData.BookTableMetaData.BOOK_NAME + " text, " +
BookProviderMetaData.BookTableMetaData.BOOK_ISBN + " text, " +
BookProviderMetaData.BookTableMetaData.BOOK_AUTHOR + " text)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists books");
onCreate(db);
}
}
然後實現ContentProvider組件並複寫其中的方法來實現功能:
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
insert方法:
將數據記錄插入到基礎數據庫中,並返回一個新創建的記錄URI,數據記錄採用ContentValues來存放。
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowId = db.insert(BookProviderMetaData.BookTableMetaData.TABLE_NAME, null, values);
Uri result = ContentUris.withAppendedId(BookProviderMetaData.BookTableMetaData.CONTENT_URI, rowId);
Log.w(TAG, "insert Uri Result = " + result);
getContext().getContentResolver().notifyChange(result, null);
return result;
}
delete方法:
根據傳入的where條件把對應的數據刪除並返回刪除的行數。
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int deleteId = 0;
switch (mUriMatcher.match(uri)) {
case BOOK_COLLECT:
db.execSQL("DROP TABLE IF EXIST books");
break;
case BOOK_SINGLE:
String id = uri.getPathSegments().get(1);
deleteId = db.delete(BookProviderMetaData.BookTableMetaData.TABLE_NAME, BookProviderMetaData.BookTableMetaData._ID + "=" + id, selectionArgs);
Log.w(TAG, "delete deleteId = " + deleteId);
break;
}
getContext().getContentResolver().notifyChange(uri, null);
return deleteId;
}
update方法
根據傳入的ContentValues數據記錄還有where條件來更新記錄,並返回的是對應的行數。
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int updateId = 0;
switch (mUriMatcher.match(uri)) {
case BOOK_SINGLE:
String id = uri.getPathSegments().get(1);
updateId = db.update(BookProviderMetaData.BookTableMetaData.TABLE_NAME, values, BookProviderMetaData.BookTableMetaData._ID + "=" + id, selectionArgs);
Log.w(TAG, "update updateId = " + updateId);
break;
}
getContext().getContentResolver().notifyChange(uri, null);
return updateId;
}
query方法
根據傳入的URI或者和where語句一起返回Cursor數據:
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(BookProviderMetaData.BookTableMetaData.TABLE_NAME);
qb.setProjectionMap(mBookProjectMap);
switch (mUriMatcher.match(uri)) {
case BOOK_COLLECT:
break;
case BOOK_SINGLE:
qb.appendWhere(BookProviderMetaData.BookTableMetaData._ID + "=" + uri.getPathSegments().get(1));
break;
}
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, null);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
Log.w(TAG, "query cursor count = " + cursor.getCount());
return cursor;
}
上面我們可以看出有這麼一個方法:uri.getPathSegments()返回的就是URI後面的路徑值。
還有那麼一個方法:
getContext().getContentResolver().notifyChange(uri, null);
這個類的作用就是操作一條數據成功的時候實時發佈通知出去,一種異步監聽數據庫中的數據發生了變動。
private static class BookContentObserver extends ContentObserver {
/**
* Creates a content observer.
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public BookContentObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.w("Jayuchou", "--- onChange --- " + selfChange);
/**
* do something...
*/
}
}
我們可以發現他需要一個Handler作爲將參數來構造的,因爲內部採用了線程所以需要Handler將結果post到主線程中去。
getContentResolver().registerContentObserver(BookProviderMetaData.BookTableMetaData.CONTENT_URI, true, mObserver);
需要的三個參數分別是:
uri—-Uri類型,是需要監聽的數據庫的uri.
notifyForDescendents—boolean true的話就會監聽所有與此uri相關的uri。false的話則是直接特殊的uri纔會監聽。一般都設置爲true.
observer—–ContentObserver 就是需要的contentobserver.
protected void onDestroy() {
super.onDestroy();
getContentResolver().unregisterContentObserver(mObserver);// 移除監聽 防止內存泄露
}
當然需要在註冊界面也要有移除註冊防止內存泄露。
多進程多應用調用
首先在聲明被調用的ContentProvider在註冊的時候需要加入
android:exported="true"
類聲明這個ContentProvider可以被外部調用使用,舉一個查詢的例子:
Cursor c = getContentResolver().query(Uri.parse("content://com.neacy.provider/books"), null, null, null, null);
操作其實相似主要ContentProvider路徑要完成即可。
最後不要忘了聲明註冊ContentProvider
<provider android:authorities="com.neacy.provider"
android:name="com.provider.demo.BookProvider"
android:exported="true"/>
這樣一個ContentProvider就完成了。