一、概念
ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,和Messenger、AIDL一样,ContentProvider的底层实现同样也是Binder,但是它的使用过程要比AIDL简单许多,因为系统已经做了封装,使得我们无须关心底层细节即可轻松实现IPC。
二、使用
1.ContentProvider六个抽象方法
(1)onCreate:
ContentProvider创建时回调,一般用来做一些初始化工作。
(2)getType:
返回一个Url请求所对应的MIME类型(媒体类型),比如图片、视频等,如果我们的应用不关注这个选项,可以直接在这个方法中返回null或者"/"。
(3)query:对数据表进行查操作。
(4)update:对数据表进行改操作。
(5)insert:对数据表进行增操作。
(6)delete:对数据表进行删操作。
这六个方法均运行在ContentProvider的进程中,除了onCreate由系统回调并运行在主线程里,其它方法均由外界回调并运行在Binder线程池中。
2.底层数据存储方式
SQLite数据库、文件、内存。
3.属性
android:authorities属性,是ContentProvider的唯一标识,通过这个属性外部应用就可以访问我们的ContentProvider,因此该属性值必须是唯一的,建议在命名的时候加上包名前缀。
android:permission属性,添加访问权限。
android:readPermission属性,声明读权限。
android:writePermission属性,声明写权限。
4.UriMatcher
ContentProvider通过Uri来区分外界要访问的数据集合。如果ContentProvider维护的数据库存在很多数据表,那么为了知道外界要访问的是哪个表,需要定义单独的Uri和Uri_Code,并将Uri和对应的Uri_Code相关联,可以使用UriMatcher的addURI方法将Uri和Uri_Code关联在一起。这样,当外界请求访问ContentProvider时,可以使用UriMatcher的match方法根据请求的Uri来得到Uri_Code,有了Uri_Code我们就可以知道外界想要访问哪个表,然后就可以进行相应的数据操作了。
public static final String AUTHORITY = "com.tomorrow.contentprovidertest";
public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, DbOpenHelper.BOOK_TABLE_NAME, BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, DbOpenHelper.USER_TABLE_NAME, USER_URI_CODE);
}
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;
}
Log.d(TAG, "zwm, getTableName: " + tableName);
return tableName;
}
5.ContentUris
用于操作Uri。
//withAppendedId方法作用:向Uri追加一个id
Uri uri = Uri.parse("content://com.tomorrow.provider/user")
Uri resultUri = ContentUris.withAppendedId(uri, 8);
//最终生成后的Uri为:content://com.tomorrow.provider/user/8
//parseId方法作用:从Uri中获取id
Uri uri = Uri.parse("content://com.tomorrow.provider/user/8")
long personid = ContentUris.parseId(uri);
//获取的结果为:8
6.ContentObserver
要观察一个ContentProvider中的数据改变情况,可以通过ContentResolver的registerContentObserver方法来注册观察者,通过unregisterContentObserver方法来解除观察者。
7.线程同步
如果底层数据采用的是SQLite并且只有一个SQLiteDatabase的连接,可以正确应对多线程情况,因为SQLiteDatabase内部对数据库的操作是有同步处理的,但是如果通过多个SQLiteDatabase对象来操作数据库就无法保证线程同步,因为SQLiteDatabase对象之间无法进行线程同步。
如果底层数据采用的是内存,比如是List,那么List的遍历、插入、删除等操作就需要进行线程同步,否则会引起并发错误。
三、例子
//DbOpenHelper
public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "content_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) {
}
}
//TestContentProvider
public class TestContentProvider extends ContentProvider {
private static final String TAG = TestContentProvider.class.getSimpleName();
public static final String AUTHORITY = "com.tomorrow.contentprovidertest";
public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(AUTHORITY, DbOpenHelper.BOOK_TABLE_NAME, BOOK_URI_CODE);
sUriMatcher.addURI(AUTHORITY, DbOpenHelper.USER_TABLE_NAME, USER_URI_CODE);
}
private SQLiteDatabase mDb;
private Context mContext;
@Override
public boolean onCreate() {
Log.d(TAG, "zwm, onCreate, Thread: " + Thread.currentThread());
mContext = getContext();
initProviderData();
return true;
}
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(1, 'Android');");
mDb.execSQL("insert into book values(2, 'Java');");
mDb.execSQL("insert into book values(3, 'App后台');");
mDb.execSQL("insert into user values(1, 'TomyZhang', 1);");
mDb.execSQL("insert into user values(2, 'Tomorrow', 1);");
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.d(TAG, "zwm, query, Thread: " + Thread.currentThread());
String table = getTableName(uri);
if(table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
Log.d(TAG, "zwm, getType, Thread: " + Thread.currentThread());
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.d(TAG, "zwm, insert, Thread: " + Thread.currentThread());
String table = getTableName(uri);
if(table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
mDb.insert(table, null, values);
mContext.getContentResolver().notifyChange(uri, null);
return uri;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d(TAG, "zwm, delete, Thread: " + Thread.currentThread());
String table = getTableName(uri);
if(table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int count = mDb.delete(table, selection, selectionArgs);
if(count > 0) {
mContext.getContentResolver().notifyChange(uri, null);
}
return count;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d(TAG, "zwm, update, Thread: " + Thread.currentThread());
String table = getTableName(uri);
if(table == null) {
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
int row = mDb.update(table, values, selection, selectionArgs);
if(row > 0) {
mContext.getContentResolver().notifyChange(uri, null);
}
return row;
}
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;
}
Log.d(TAG, "zwm, getTableName: " + tableName);
return tableName;
}
}
//MainActivity
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
public static final String AUTHORITY = "com.tomorrow.contentprovidertest";
public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
private Cursor mBbookCursor;
private BookContentObserver mBookContentObserver = new BookContentObserver(new Handler());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getContentResolver().registerContentObserver(BOOK_CONTENT_URI, true, mBookContentObserver);
mBbookCursor = getContentResolver().query(BOOK_CONTENT_URI, new String[]{"_id", "name"}, null, null, null);
printBookCursor(mBbookCursor);
ContentValues values = new ContentValues();
values.put("_id", 4);
values.put("name", "性能优化");
getContentResolver().insert(BOOK_CONTENT_URI, values);
mBbookCursor = getContentResolver().query(BOOK_CONTENT_URI, new String[]{"_id", "name"}, null, null, null);
printBookCursor(mBbookCursor);
}
private void printBookCursor(Cursor bookCursor) {
Log.d(TAG, "zwm, printBookCursor, bookCursor: " + bookCursor);
if(bookCursor == null)
return;
while(bookCursor.moveToNext()) {
int id = bookCursor.getInt(0);
String name = bookCursor.getString(1);
Log.d(TAG, "zwm, id: " + id + ", name: " + name);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
getContentResolver().unregisterContentObserver(mBookContentObserver);
mBbookCursor.close();
}
private class BookContentObserver extends ContentObserver {
public BookContentObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.d("BookContentObserver", "zwm, onChange, selfChange: " + selfChange);
}
}
}
//AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tomorrow.contentprovidertest">
<uses-permission android:name="com.tomorrow.provider"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name=".TestContentProvider"
android:authorities="com.tomorrow.contentprovidertest"
android:permission="com.tomorrow.provider"
android:process=":provider"/>
</application>
</manifest>