原文出處:http://www.ccbu.cc/index.php/android/android-contentprovider.html
ContentProvider中文名“內容提供者”,是Android系統不同應用程序之間進行數據交換的標準API,ContentProvide以Uri的形式對外提供數據,允許其他應用訪問和修改數據;其他應用使用ContentResolve根據Uri進行訪問操作指定的數據。Android內置的許多數據也都是使用ContentProvider形式,供開發者調用的;如視頻,音頻,圖片,通訊錄等。
統一資源標識符(URI)
訪問ContentProvider都是統一通過Uri進行訪問的,URI(Uniform Resource Identifier
,即統一資源標識符)由三部分組成,即協議(scheme),所有者(authority),路徑(path)。
在ContentProvider中,scheme統一爲content:// ;authority用來作爲當前Provider的唯一標識,一般以公司域名+數據標識; Path爲具體的數據路徑,一般爲 “數據類型 / 數據ID” 的形式 。如上面例子中的100即爲id。
UriMatcher類
既然ContentProvider需要通過RUI進行訪問,所以android系統提供了UriMatcher來進行Uri的匹配處理。提供了以下兩個接口,一個用來添加匹配規則,一個用來獲取匹配結果。
public void addURI(String authority, String path, int code)
public int match(Uri uri)
定義匹配規則
public static final int ID1 = 1;
public static final int ID2 = 2;
public static final String HOST = "com.test.provider.DataContentProvider";
public static final String PATH = "students";
private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(HOST,PATH, ID1);
uriMatcher.addURI(HOST,PATH + "/#", ID2); // # 爲通配符
}
匹配路徑
switch (uriMatcher.match(Uri uri)) {
case ID1:
break;
case ID2:
break;
default:
break;
}
如匹配的uri爲:content://com.test.provider.DataContentProvider/students/10 , 則得到的匹配碼爲ID2。
ContentUris類
這個類是一個操作Uri字符串的工具類,主要是拼接Uri字符串用,它有兩個方法:
public static Uri withAppendedId(Uri uri, long id) // 用於爲路徑加上id部分
public static long parseId(Uri uri) // 用於從指定的Uri中解析出所包含的id
Uri uri = Uri.parse("content://com.test.provider.DataContentProvider/students");
Uri newUri = ContentUris.withAppendedId(uri, 100);
Uri uri = Uri.parse("content://com.test.provider.DataContentProvider/students/100")
long personid = ContentUris.parseId(uri); // 獲取的結果爲:100
ContentProvider類介紹
ContentProvider類是一個抽象類,具體的實現類需要實現其主要的幾個虛函數,也正是通過這些虛函數的實現來完成具體的ContentProvider的具體功能的。主要的函數如下:
函數 | 說明 |
---|---|
boolean onCreate() | ContentProvider創建後就會被調用,ContentProvider在其它應用第一次訪問它時纔會被創建 |
Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) | 用於供外部應用從ContentProvider中獲取數據 |
String getType(Uri uri) | 用於返回當前Url所代表數據的MIME類型 |
Uri insert(Uri uri, ContentValues values) | 用於供外部應用往ContentProvider添加數據 |
int delete(Uri uri, String selection, String[] selectionArgs) | 用於供外部應用從ContentProvider刪除數據 |
int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) | 用於供外部應用更新ContentProvider中的數據 |
下面通過一個實列來展示ContentProvider的實現。
public class StudentProvider extends ContentProvider {
public static final int TYPE_ITEM = 1;
public static final int TYPE_TABLE = 2;
public static final String HOST = "cc.ccbu.provider.StudentProvider";
public static final String PATH = "students";
private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.dir/students.item";
public static final String CONTENT_TYPE_TABLE = "vnd.android.cursor.item/students.table";
static {
uriMatcher.addURI(HOST, PATH, TYPE_TABLE);
uriMatcher.addURI(HOST,PATH + "/#", TYPE_ITEM);
}
private DbOpenHelper dbOpenHelper;
@Override
public boolean onCreate() {
dbOpenHelper = new DbOpenHelper(getContext());
return true;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)){
case TYPE_ITEM:
return CONTENT_TYPE_ITEM;
case TYPE_TABLE:
return CONTENT_TYPE_TABLE;
default:
throw new IllegalArgumentException("this is unknown uri:" + uri);
}
}
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case TYPE_TABLE:
long id = db.insert(DbOpenHelper.STUDENT_TABLE_NAME, null, values);
Uri insertUri = ContentUris.withAppendedId(uri, id);
return insertUri;
}
return null;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case TYPE_TABLE:
return db.query(DbOpenHelper.STUDENT_TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
case TYPE_ITEM:
long id = ContentUris.parseId(uri);
String where = "_id = " + id;
if (null != selection && !"".equals(selection.trim()))
{
where += " and " + selection;
}
return dbOpenHelper.getWritableDatabase().query(DbOpenHelper.STUDENT_TABLE_NAME, projection, where, selectionArgs, null, null, sortOrder);
}
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case TYPE_TABLE:
return db.delete(DbOpenHelper.STUDENT_TABLE_NAME, selection, selectionArgs);
case TYPE_ITEM:
long id = ContentUris.parseId(uri);
String where = "_id = " + id;
if (null != selection && !"".equals(selection.trim()))
{
where += " and " + selection;
}
return db.delete(DbOpenHelper.STUDENT_TABLE_NAME, where, selectionArgs);
}
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case TYPE_TABLE:
return db.update(DbOpenHelper.STUDENT_TABLE_NAME, values, selection, selectionArgs);
case TYPE_ITEM:
long id = ContentUris.parseId(uri);
String where = "_id = " + id;
if (null != selection && !"".equals(selection.trim()))
{
where += " and " + selection;
}
return db.update(DbOpenHelper.STUDENT_TABLE_NAME, values, where, selectionArgs);
}
return 0;
}
}
Provider類寫好後還需要在AndroidManifest.xml中進行註冊。爲了能使其他應用可以訪問ContentProvider中的數據,需要在xml中將android:exported屬性設置爲true;若設置成false,則只能被自己所在的應用使用。
<provider
android:authorities="cc.ccbu.provider.StudentProvider"
android:name=".StudentProvider"
android:enabled="true"
android:exported="true"/>
下面是一個簡單的測試例子。展示了常見的insert, update, qurey, delete操作。
public class MainActivity extends Activity {
public static final String AUTHORITY = "cc.ccbu.provider.StudentProvider";
public static final String STUDENT = "students";
private static final String TAG = "MainActivity";
private Uri uri;
private long insertId = -1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
uri = Uri.parse("content://" + AUTHORITY + "/" + STUDENT);
onInsert();
onQuery();
onUpdate();
onQuery();
onDelete();
onQuery();
}
public void onInsert() {
ContentValues cvs = new ContentValues();
cvs.put("name", "tom");
Uri uriRet = getContentResolver().insert(uri, cvs);
if (uriRet != null) {
insertId = ContentUris.parseId(uriRet);
Log.d(TAG, "onInsert : " + insertId);
}
}
public void onUpdate() {
if (insertId >= 0) {
Uri uriUpdate = ContentUris.withAppendedId(uri, insertId);
ContentValues cvs = new ContentValues();
cvs.put("name", "jack");
int updateId = getContentResolver().update(uriUpdate, cvs, null, null);
Log.d(TAG, "onUpdate : " + updateId);
}
}
public void onQuery(){
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
Log.d(TAG, "onQuery : " + cursor.getInt(0) + ", " + cursor.getString(1));
}
}
}
public void onDelete() {
Uri uriDelete = ContentUris.withAppendedId(uri, insertId);
getContentResolver().delete(uriDelete, null, null);
Log.d(TAG, "onDelete");
}
}
運行結果如下:
D/MainActivity: onInsert : 1
D/MainActivity: onQuery : 1, tom
D/MainActivity: onUpdate : 1
D/MainActivity: onQuery : 1, jack
D/MainActivity: onDelete
ContentProvider的監聽
ContentProvider以Uri的形式爲其他應用提供了簡單的訪問方式,通過次方式,外部應用可以很輕鬆的訪問ContentProvider中的數據,但這些訪問動作都是應用主動發起的,當ContentProvider中數據變化了之後,有沒有辦法被通知數據變化了呢,答案當時是有的。ContentObserver是系統爲我們提供的ContentProvider數據監聽的基類,需要監聽ContentProvider的數據變化必須繼承此類,從類名可以看出,此次的監聽是以觀察者模式來實現的。
private void testObserver() {
getContentResolver().registerContentObserver(uri, true, new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange, Uri uri) {
Log.d(TAG, "onChange : " + uri.getPath());
}
});
}
ContentObserver在構造函數中需要提供一個Handler實例,當我們通過ContentResolver
的registerContentObserver
註冊某一Uri的監聽後,當此Uri的數據發生變化時,我們就可以在onChange
回調函數中收到通知。當然,需要完全實現此功能,在ContentProvider中還需要做些相應的處理,當ContentProvider中的數據發生變化時,需要通知數據變化了。
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case TYPE_TABLE:
long id = db.insert(DbOpenHelper.STUDENT_TABLE_NAME, null, values);
Uri insertUri = ContentUris.withAppendedId(uri, id);
getContext().getContentResolver().notifyChange(insertUri, null);
return insertUri;
default:
throw new IllegalArgumentException("this is unknown uri:" + uri);
}
}
上面的例子中,當插入新的數據時,將新數據的Uri通知出去;監聽者也就可以收到想要的通知。
ContentProvider的使用權限
ContentProvider可以被其他應用公開的訪問,同時也可以設置爲只有本應用自己纔可以訪問,或者在對其他應用公開訪問時設置訪問權限。ContentProvider需要在provider
標籤中設置android:permission
屬性來設置訪問權限。權限通常爲一個字符串,爲了與其他應用區分,防止重複,一般以應用的PackageName開頭。
<provider
android:authorities="cc.ccbu.provider.StudentProvider"
android:name=".StudentProvider"
android:enabled="true"
android:exported="true"
android:permission="cc.ccbu.provider.StudentProvider"/>
同時需要在AndroidManifest.xml的根節點下用permission標籤來申明權限。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.ccbu.provider">
<permission android:name="cc.ccbu.provider.StudentProvider"
android:label="StudentProvider"
android:protectionLevel="normal"/>
......
</manifest>
此時,其他應用需要有權限訪問此ContentProvider的數據,則需要在AndroidManifest.xml申請權限。
<uses-permission android:name="cc.ccbu.provider.StudentProvider"/>
訪問權限還可以進一步的細化,分爲讀權限和寫權限。通過來設置android:readPermission
屬性和android:writePermission
屬性項來設置。
<provider
android:authorities="cc.ccbu.provider.StudentProvider"
android:name=".StudentProvider"
android:enabled="true"
android:exported="true"
android:readPermission="cc.ccbu.provider.StudentProvider.READ"
android:writePermission="cc.ccbu.provider.StudentProvider.WRITE"/>
同樣的需要申明一下這兩種權限
<permission android:name="cc.ccbu.provider.StudentProvider.READ"
android:label="StudentProvider.read"
android:protectionLevel="normal"/>
<permission android:name="cc.ccbu.provider.StudentProvider.WRITE"
android:label="StudentProvider.write"
android:protectionLevel="normal"/>
訪問的應用則也需要在AndroidManifest
中進行對應的權限申請纔可以進行訪問。
另外,android:writePermission
與android:readPermission
權限的優先級比android:permission
的優先級高,所以設置了read和write權限後,訪問方就必須申請相應的read和write權限,否則是無法訪問的。