如果你公司开发了多款应用且应用间需要共享数据,如果你的应用中存在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就完成了。