
本文的合集已經(jīng)編著成書,高級(jí)Android開發(fā)強(qiáng)化實(shí)戰(zhàn),歡迎各位讀友的建議和指導(dǎo)。在京東即可購(gòu)買:https://item.jd.com/12385680.html

ContentProvider主要應(yīng)用于進(jìn)程間數(shù)據(jù)共享. 對(duì)于應(yīng)用而言, 多進(jìn)程并不會(huì)經(jīng)常使用, 因而較少使用ContentProvider, 是最不常見的四大組件(Activity, Service, BroadcastReceiver, ContentProvider). 但是其優(yōu)異的性能與便捷, 對(duì)于多應(yīng)用共享數(shù)據(jù)而言, 非常重要, 比如共享同一份計(jì)步數(shù)據(jù)等. 開發(fā)者只有掌握多種技能, 才能在開發(fā)中游刃有余, 用最優(yōu)的方式完成項(xiàng)目, 提升應(yīng)用性能, 間接提高用戶體驗(yàn). 本文借用Demo, 講解ContentProvider共享數(shù)據(jù)的要點(diǎn).
本文源碼的GitHub下載地址
SQLite
ContentProvider需要媒介進(jìn)行數(shù)據(jù)存儲(chǔ), 最常用的就是SQLite數(shù)據(jù)庫(kù).
SQLite數(shù)據(jù)庫(kù)繼承SQLiteOpenHelper類, 提供數(shù)據(jù)庫(kù)名稱, 表名, 版本. 在onCreate方法中, 創(chuàng)建數(shù)據(jù)庫(kù)表, 添加字段.
本示例使用兩張表, 書籍和用戶.
public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "book_provider.db";
public static final String BOOK_TABLE_NAME = "book";
public static final String USER_TABLE_NAME = "user";
private static final int DB_VERSION = 1;
private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "
+ BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY, name TEXT)";
private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "
+ USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY, name TEXT, sex INT)";
public DbOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK_TABLE);
db.execSQL(CREATE_USER_TABLE);
}
@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
直接使用數(shù)據(jù)庫(kù)的情況較少, 也比較復(fù)雜, 推薦使用一些經(jīng)典ORM數(shù)據(jù)庫(kù), 如Sugar等, 簡(jiǎn)化管理. ORM, 即對(duì)象關(guān)系映射.
ContentProvider
ContentProvider提供數(shù)據(jù)訪問的接口, CRUD增刪改查. 在onCreate中, 初始化數(shù)據(jù)庫(kù), 并添加數(shù)據(jù).
@Override public boolean onCreate() {
showLogs("onCreate 當(dāng)前線程: " + Thread.currentThread().getName());
mContext = getContext();
initProviderData(); // 初始化Provider數(shù)據(jù)
return false;
}
private void initProviderData() {
mDb = new DbOpenHelper(mContext).getWritableDatabase();
mDb.execSQL("delete from " + DbOpenHelper.BOOK_TABLE_NAME);
mDb.execSQL("delete from " + DbOpenHelper.USER_TABLE_NAME);
mDb.execSQL("insert into book values(3,'Android');");
mDb.execSQL("insert into book values(4, 'iOS');");
mDb.execSQL("insert into book values(5, 'HTML5');");
mDb.execSQL("insert into user values(1, 'Spike', 1);");
mDb.execSQL("insert into user values(2, 'Wang', 0);");
}
CRUD的參數(shù)是Uri, 數(shù)據(jù)庫(kù)需要使用表名, 為了便于從Uri映射到表名, 使用關(guān)系轉(zhuǎn)換.
private String getTableName(Uri uri) {
String tableName = null;
switch (sUriMatcher.match(uri)) {
case BOOK_URI_CODE:
tableName = DbOpenHelper.BOOK_TABLE_NAME;
break;
case USER_URI_CODE:
tableName = DbOpenHelper.USER_TABLE_NAME;
break;
default:
break;
}
return tableName;
}
添加數(shù)據(jù)insert, 可以注冊(cè)內(nèi)容改變的監(jiān)聽, 插入數(shù)據(jù)時(shí), 廣播更新, 即notifyChange.
@Nullable @Override public Uri insert(Uri uri, ContentValues values) {
showLogs("insert");
String table = getTableName(uri);
if (TextUtils.isEmpty(table)) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
mDb.insert(table, null, values);
// 插入數(shù)據(jù)后通知改變
mContext.getContentResolver().notifyChange(uri, null);
return null;
}
刪除數(shù)據(jù)delete, 返回刪除數(shù)據(jù)的數(shù)量, 大于0即刪除成功.
@Override public int delete(Uri uri, String selection, String[] selectionArgs) {
showLogs("delete");
String table = getTableName(uri);
if (TextUtils.isEmpty(table)) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int count = mDb.delete(table, selection, selectionArgs);
if (count > 0) {
mContext.getContentResolver().notifyChange(uri, null);
}
return count; // 返回刪除的函數(shù)
}
修改數(shù)據(jù)update, 與刪除類似, 返回修改數(shù)據(jù)的數(shù)量.
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
showLogs("update");
String table = getTableName(uri);
if (TextUtils.isEmpty(table)) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int row = mDb.update(table, values, selection, selectionArgs);
if (row > 0) {
mContext.getContentResolver().notifyChange(uri, null);
}
return row; // 返回更新的行數(shù)
}
查詢數(shù)據(jù)query, 返回?cái)?shù)據(jù)庫(kù)的游標(biāo), 處理數(shù)據(jù).
@Nullable @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
showLogs("query 當(dāng)前線程: " + Thread.currentThread().getName());
String tableName = getTableName(uri);
if (TextUtils.isEmpty(tableName)) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
return mDb.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
}
注意Uri和表名的轉(zhuǎn)換可能為空, 使用
TextUtils.isEmpty判空.
共享數(shù)據(jù)
使用ContentProvider的獨(dú)立進(jìn)程, 模擬進(jìn)程間共享數(shù)據(jù).
<provider
android:name=".BookProvider"
android:authorities="org.wangchenlong.book.provider"
android:permission="org.wangchenlong.BOOK_PROVIDER"
android:process=":provider"/>
在AndroidManifest中, 把Provider注冊(cè)在
:provider進(jìn)程中, 與主進(jìn)程分離.
添加數(shù)據(jù), 通過Uri找到ContentProvider, 使用ContentResolver的insert方法, 添加ContentValues數(shù)據(jù).
public void addBooks(View view) {
Uri bookUri = BookProvider.BOOK_CONTENT_URI;
ContentValues values = new ContentValues();
values.put("_id", 6);
values.put("name", "信仰上帝");
getContentResolver().insert(bookUri, values);
}
查詢數(shù)據(jù)query, 與數(shù)據(jù)庫(kù)的使用方式類似, 解析出Cursor, 通過移動(dòng)Cursor, 找到所有匹配的結(jié)果.
public void showBooks(View view) {
String content = "";
Uri bookUri = BookProvider.BOOK_CONTENT_URI;
Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
if (bookCursor != null) {
while (bookCursor.moveToNext()) {
Book book = new Book();
book.bookId = bookCursor.getInt(0);
book.bookName = bookCursor.getString(1);
content += book.toString() + "\n";
Log.e(TAG, "query book: " + book.toString());
mTvShowBooks.setText(content);
}
bookCursor.close();
}
}
效果

ContentProvider封裝了跨進(jìn)程共享的邏輯, 我們只需要Uri即可訪問數(shù)據(jù), 使用共享數(shù)據(jù)非常便捷, 需要掌握簡(jiǎn)單的使用方式.
OK, that's all! Enjoy it!