本文的合集已經編著成書,高級Android開發強化實戰,歡迎各位讀友的建議和指導。在京東即可購買:https://item.jd.com/12385680.html
ContentProvider主要應用于進程間數據共享. 對于應用而言, 多進程并不會經常使用, 因而較少使用ContentProvider, 是最不常見的四大組件(Activity, Service, BroadcastReceiver, ContentProvider). 但是其優異的性能與便捷, 對于多應用共享數據而言, 非常重要, 比如共享同一份計步數據等. 開發者只有掌握多種技能, 才能在開發中游刃有余, 用最優的方式完成項目, 提升應用性能, 間接提高用戶體驗. 本文借用Demo, 講解ContentProvider共享數據的要點.
本文源碼的GitHub下載地址
SQLite
ContentProvider需要媒介進行數據存儲, 最常用的就是SQLite數據庫.
SQLite數據庫繼承SQLiteOpenHelper
類, 提供數據庫名稱, 表名, 版本. 在onCreate方法中, 創建數據庫表, 添加字段.
本示例使用兩張表, 書籍和用戶.
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) {
}
}
直接使用數據庫的情況較少, 也比較復雜, 推薦使用一些經典ORM數據庫, 如Sugar等, 簡化管理. ORM, 即對象關系映射.
ContentProvider
ContentProvider提供數據訪問的接口, CRUD增刪改查. 在onCreate中, 初始化數據庫, 并添加數據.
@Override public boolean onCreate() {
showLogs("onCreate 當前線程: " + Thread.currentThread().getName());
mContext = getContext();
initProviderData(); // 初始化Provider數據
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的參數是Uri, 數據庫需要使用表名, 為了便于從Uri映射到表名, 使用關系轉換.
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;
}
添加數據insert
, 可以注冊內容改變的監聽, 插入數據時, 廣播更新, 即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);
// 插入數據后通知改變
mContext.getContentResolver().notifyChange(uri, null);
return null;
}
刪除數據delete
, 返回刪除數據的數量, 大于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; // 返回刪除的函數
}
修改數據update
, 與刪除類似, 返回修改數據的數量.
@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; // 返回更新的行數
}
查詢數據query
, 返回數據庫的游標, 處理數據.
@Nullable @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
showLogs("query 當前線程: " + 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和表名的轉換可能為空, 使用
TextUtils.isEmpty
判空.
共享數據
使用ContentProvider的獨立進程, 模擬進程間共享數據.
<provider
android:name=".BookProvider"
android:authorities="org.wangchenlong.book.provider"
android:permission="org.wangchenlong.BOOK_PROVIDER"
android:process=":provider"/>
在AndroidManifest中, 把Provider注冊在
:provider
進程中, 與主進程分離.
添加數據, 通過Uri找到ContentProvider, 使用ContentResolver
的insert
方法, 添加ContentValues
數據.
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);
}
查詢數據query
, 與數據庫的使用方式類似, 解析出Cursor, 通過移動Cursor, 找到所有匹配的結果.
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封裝了跨進程共享的邏輯, 我們只需要Uri即可訪問數據, 使用共享數據非常便捷, 需要掌握簡單的使用方式.
OK, that's all! Enjoy it!