【我的Android進階之旅】自定義ContentProvider
引言
我們知道Android有四大元件,ContentProvider是其中之一,顧名思義:內容提供者。什麼是內容提供者呢?一個抽象類,可以暴露應用的資料給其他應用。應用裡的資料通常說的是資料庫,事實上普通的檔案,甚至是記憶體中的物件,也可以作為內容提供者暴露的資料形式。為什麼要使用內容提供者呢?從上面定義就知道,內容提供者可以實現應用間的資料訪問,一般是暴露表格形式的資料庫中的資料。內容提供者的實現機制是什麼呢?由於是實現應用間的資料通訊,自然也是兩個程序間的通訊,其內部實現機制是Binder機制。那麼,內容提供者也是實現程序間通訊的一種方式。事實上在開發中,很少需要自己寫一個ContentProvider,一般都是去訪問其他應用的ContentProvider。本篇文章之所以去研究如何自己寫一個ContentProvider,也是為了更好的在開發中理解:如何訪問其他應用的內容提供者
如何自定義一個ContentProvider
接下來介紹如何自己去實現一個內容提供者,大致分三步進行:- 繼承抽象類ContentProvider,重寫onCreate,CUDR,getType六個方法:onCreate()方法中,獲取SQLiteDatabase物件;CUDR方法通過對uri進行判斷,做相應的增刪改查資料的操作;getType方法是返回uri對應的MIME型別。
- 註冊可以訪問內容提供者的uri:建立靜態程式碼塊,static{…code},在類載入的時候註冊可以訪問內容提供者的uri,使用類UriMatcher的addURI(…)完成。
- 清單檔案中配置provider:註冊內容提供者,加入authorities屬性,對外暴露該應用的內容提供者。
以下是程式碼部分
package com.wzy.myprovider; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; public class BookProvider extends ContentProvider { static final String DATABASE_NAME = "provider.db"; static final String DATABASE_TABLE = "book"; static final int DATABASE_VERSION = 1; static final String DATABASE_CREATE = "create table " + DATABASE_TABLE + " (_id integer primary key autoincrement, " + "name text not null, author text not null);"; static final String PROVIDER_NAME = "com.wzy.provider"; static final int BOOKS = 1; static final int BOOK_ID = 2; static UriMatcher uriMatcher; SQLiteDatabase db; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS); uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID); } private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(DATABASE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS BookInfo"); onCreate(db); } } @Override public boolean onCreate() { DatabaseHelper helper = new DatabaseHelper(getContext()); db = helper.getWritableDatabase(); return false; } @Nullable @Override public String getType(@NonNull Uri uri) { switch (uriMatcher.match(uri)) { case BOOKS: return "vnd.android.cursor.dir/vnd.com.wzy.provider.books"; case BOOK_ID: return "vnd.android.cursor.item/vnd.com.wzy.provider.books"; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { Cursor cursor = null; switch (uriMatcher.match(uri)) { case BOOK_ID: cursor = db.query(DATABASE_TABLE, projection, "_id = ", new String[]{uri.getPathSegments().get(1)}, null, null, sortOrder); break; case BOOKS: cursor = db.query(DATABASE_TABLE, projection, selection, selectionArgs, null, null, sortOrder); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } return cursor; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { long rowId = db.insert(DATABASE_TABLE, null, values); if (rowId > 0) { Uri bookUri = ContentUris.withAppendedId(uri, rowId); return bookUri; } throw new SQLException("Failed to insert row into " + uri); } @Override public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { int rowIDs; switch (uriMatcher.match(uri)) { case BOOK_ID: String bookID = uri.getPathSegments().get(1); rowIDs = db.delete(DATABASE_TABLE, "_id = ", new String[]{bookID}); break; case BOOKS: rowIDs = db.delete(DATABASE_TABLE, selection, selectionArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } return rowIDs; } @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { int rowIDs; switch (uriMatcher.match(uri)) { case BOOK_ID: String bookID = uri.getPathSegments().get(1); rowIDs = db.update(DATABASE_TABLE, values, "_id = ?", new String[]{bookID}); break; case BOOKS: rowIDs = db.update(DATABASE_TABLE, values, selection, selectionArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } return rowIDs; } }
初次觀察程式碼,我們可以發現,內容提供者就是基於sql實現的一層封裝。我們再觀察靜態程式碼塊,在這裡,值得一提的是靜態程式碼塊總是執行在類的構造方法執行之前。
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS);
uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID);
}
我們進入UriMatcher的構造方法去檢視下
public UriMatcher(int code)
{
mCode = code;
mWhich = -1;
mChildren = new ArrayList<UriMatcher>();
mText = null;
}
在這個裡面維護了一個ArrayList,可以預知的是,這個類裡面的addURI和match方法就是基於對這個列表新增和遍歷實現的。在這裡就不多做贅述了,有興趣的童鞋可以自行研究。
我們繼續往下觀察,我們建立了一個抽象幫助類SQLiteOpenHelper的子類並在onCreate中去例項化了一個物件,然後通過getWritableDatabase()獲取SQLiteDatabase例項。其實這裡應該的做法是單獨建立一個類繼承自SQLiteOpenHelper,並使用單例模式來獲取其例項。
我們來了解下uri。uri是如何構成的呢? uri 有三個構成元素scheme + authorities + path。先看這樣一個uri,uri = “content://com.wzy.provider/books”,在這裡,三個構成元素分別是:
scheme:“content://”; 一般是固定的 authorities :“com.wzy.provider”;authorities就是在清單檔案中配置的authorities屬性的值,唯一標識該應用的內容提供者。 path:"/books";path裡面常常放的是一些表名,欄位資訊,確定訪問該資料庫中哪個表的哪些資料.
繼續往下,我們接下里分析CUDR操作,我們重寫了這樣四個方法:query,insert,delete,update,這個四個方法的引數都是想訪問該應用的其他使用者傳遞過來的.我們呼叫uriMatcher.match(uri)來匹配uri進行對應的CRUD操作.如果返回的code沒有在uriMatcher中註冊過.就跑出IllegalArgumentException("Unsupported URI: " + uri)異常,代表uri不合法。如果合法就呼叫SQLiteDatabase的query,insert,delete,update四個方法進行增刪改查資料。
值得一提的是,在進行insert操作的時候返回的是一個uri,我們往資料庫插入一條記錄併成功返回該記錄的ID的時候,會將該ID以路徑的方式拼接到原始uri上面。舉個栗子:比如說我們通過"content://com.wzy.provider/books"往資料庫表book成功插入一條資料,並返回id為99,那麼我們得到的uri就是"content://com.wzy.provider/books/99".
這樣,一個完整的ContentProvider就定義完成了。不要忘記在清單檔案中進行申明:
<provider
android:name=".BookProvider"
android:authorities="com.wzy.provider"
android:exported="true">
</provider>
要注意,設定exported="true"是為了其他應用能訪問到我們的BookProvider。
關於呼叫
現在內容提供者也定義好了,我們該派他上場了。之前說過,內容提供者是基於Binder實現的,可以進行跨程序訪問。現在我們重新建立一個測試應用,寫個簡單的測試程式碼並列印log可以看下效果private void test() {
Uri uri = Uri.parse("content://com.wzy.provider/books");
ContentValues values = new ContentValues();
values.put("name","西遊記");
values.put("author","吳承恩");
Uri bookUri = getContentResolver().insert(uri,values);
values.put("name","紅樓夢");
values.put("author","曹雪芹");
getContentResolver().update(bookUri,values,null,null);
}