ContentProvider使用簡介

原文出處: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實例,當我們通過ContentResolverregisterContentObserver註冊某一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:writePermissionandroid:readPermission權限的優先級比android:permission的優先級高,所以設置了read和write權限後,訪問方就必須申請相應的read和write權限,否則是無法訪問的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章