本子產品共有四篇文章,參考郭神的《第一行代碼》,對Content Provider的學習做一個詳細的筆記,大家可以一起交流一下:
- 跨程式共享資料——Content Provider 之 運作時權限解析以及申請的實作(可完美解決java.lang.SecurityException:Permission Denial 問題)
- 跨程式共享資料——Content Provider 之 ContentResolver基本用法 & 一個讀取系統聯系人的Demo
- 跨程式共享資料——Content Provider 之 建立自己的内容提供器(即本文)
- Content Provider 之 最終彈 實戰體驗跨程式資料共享(結合SQLiteDemo)
在上一節中,我們學習了如何在自己的程式中通路其他應用程式的資料。總體來說思路還是非常簡單的,隻需要擷取到該應用程式的内容URI,然後借助ContentResolver進行CRUD操作就可以了。可是你有沒有想過,那些提供外部通路接口的應用程式都是如何實作這種功能的呢?它們又是怎樣保證資料的安全性,使得隐私資料不會洩漏出去?學習完本節的知識後,你的疑惑将會被 一 一解開。
建立内容提供器的步驟
1 建立一個類去繼承ContentProvider;
2 在這個類中重寫6個抽象方法(詳見下文)
3 定義自定義代碼常量;
建立靜态代碼塊,在代碼塊中執行個體化UriMatcher的一個執行個體并調用addURI()将Uri以及對應的自定義代碼常量傳遞進去;
補充query(),處理比對結果;insert()、update()、delete()同理;
4 補充getType()方法,完畢.
下面進行步驟的詳細解析
前面已經提到過,如果想要實作跨程式共享資料的功能,官方推薦的方式就是使用内容提供器,可以通過建立一個類去繼承ContentProvider的方式來建立一個自己的内容提供器。ContentProvider類中有6個抽象方法,我們在使用子類繼承它的時候,需要将這6個方法全部重寫。建立MyProvider繼承自ContentProvider,代碼如下所示(這裡先不用急着寫,到後面待會兒的實戰講解中會告訴大家内容提供器的快捷建立方法):
public class MyProvider extends ContentProvider{
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
6大重寫抽象方法解析(其實CRUD操作的跟前一節的差不多):
1.onCreate()
- 初始化内容提供器的時候調用。
- 通常會在這裡完成對資料庫的建立和更新等操作。
- 傳回 true 表示内容提供器初始化成功,傳回 false 則表示失敗。
- 注意,隻有當存在ContentResoIver嘗試通路我們程式中的資料時,内容提供器才會被初始化。
2.query()
- 從内容提供器中查詢資料。
-
使用uri參數來确定查詢哪張表,
projection參數用于确定查詢哪些列,
selection和selectionArgs參數用于限制查詢哪些行,
sortorder參數用于對結果進行排序,
查洵的結果存放在Cursor對象中傳回。
3.insert()
向内容提供器中添加一條資料。使用uri參數來确定要添加到的表,待添加的資料儲存在
values參數中。添加完成後,傳回一個用于表示這條新記錄的URI。
4,update()
更新内容提供器中已有的資料。使用uri參數來确定更新哪一張表中的資料,新資料儲存在
values參數中,selection和selectionArgs參數用于限制更新哪些行,受影響的行數将作
為傳回值傳回。
5.delete()
從内容提供器中删除資料。使用uri參數來确定删除哪一張表中的資料,selection和
selectionArgs參數用于限制删除哪些行,被删除的行數将作為傳回值傳回。
6、getType()
- 根據傳入的内容URI來傳回相應的MIME類型。
可以看到,幾乎每一個方法都會帶有Uri這個參數,這個參數也正是調用ContentResoIver的增删改查方法時傳遞過來的。
而現在,我們需要對傳入的Uri參數進行解析,從中分析出調用方期望通路的表和資料。
Uri的兩種寫法
使用通配符比對Uri
UriMatcher比對
- 接着,我們再借助UriMatcher這個類就可以輕松地實作比對内容URI的功能。
- UriMatcher中提供了一個addURI()方法,這個方法接收3個參數,可以分别把authority、path和一個自定義代碼傳進去。
- 這樣,當調用UriMatcher的match()方法時,就可以将一個Uri對象傳入,傳回值是某個能夠比對這個Uri對象所對應的自定義代碼,
- 利用這個代碼,我們就可以判斷出調用方期望通路的是哪張表中的資料了。
修改MyProvider中的代碼,如下所示:
上面的代碼簡析:
-
可以看到,MyProvider中新增了4個整型常量,其中
TABLEI_DIR表示通路tablel表中的所有資料,
TABLEI_ITEM表示通路tablel表中的單條資料,
TABLE2_DIR表示通路table2表中的所有資料,
TABLE2_ITEM表示通路table2表中的單條資料。
- 接着在靜态代碼塊裡我們建立了UriMatcher的執行個體,并調用 addURI() 方法,将期望比對的内容URI格式傳遞進去,注意這裡傳入的路徑參數是可以使用通配符的。
- 然後當 query()方法 被調用的時候,就會通過UriMatcher的match()方法對傳入的Uri對象進行比對,如果發現UriMatcher中某個内容URI格式成功比對了該Uri對象,則會傳回相應的自定義代碼,然後我們就可以判斷出調用方期望通路的到底是什麼資料了。
getType()與MIME類型的感情糾葛
上述代碼隻是以query()方法為例做了個示範,其實insert()、update()、delete()這幾個方法的實作也是差不多的,它們都會攜帶Uri這個參數,然後同樣利用UriMatcher的match()方法判斷出調用方期望通路的是哪張表,再對該表中的資料進行相應的操作就可以了。
除此之外,還有一個方法你會比較陌生,即getType()方法。它是所有的内容提供器都必須提供的一個方法,用于擷取Uri對象所對應的MIME類型。一個内容URI所對應的MIME字元串主要由3部分組成,Android對這3個部分做了如下格式規定。
1.必須以vnd開頭。
2.如果内容URI以路徑結尾,則後接android.cursor.dir/,如果内容URI以id結尾,
則後接android.cursor.item/。
3.最後接上vnd.<authority>.<path>。
接下來,補充getType()
内容提供器保證隐私資料不會洩漏出去
到這裡,一個完整的内容提供器就建立完成了,現在任何一個應用程式都可以使用ContentResolver來通路我們程式中的資料;
那麼前面所提到的,如何才能保證隐私資料不會洩漏出去呢?其實多虧了内容提供器的良好機制,這個問題在不知不覺中已經被解決了。因為所有的CRUD操作都一定要比對到相應的内容URI格式才能進行的,而我們當然不可能向UnMatcher中添加隐私資料的URI,是以這部分資料根本無法被外部程式通路到,安全問題也就不存在了。