ContentProvider作為Android的四大元件之一,是屬于需要掌握的基礎知識,可能在我們的應用中,對于Activity和Service這兩個元件用的很常見,了解的也很多,但是對ContentProvider所知卻甚少,是以有必要去整理歸納下其中的内容,講講為什麼要用ContentProvider這個元件、ContentProvider是什麼、ContentProvider用法如何,讓大家對ContentProvider有個整體上的了解,友善以後在開發過程中如果忘記了可以及時回顧。
為什麼要用ContentProvider
ContentProvider是什麼
ContentProvider用法
小結
我們都知道在Android中有資料持久化技術,常見的幾種方式比如:括檔案存儲、SharedPreferences 存 儲、以及資料庫存儲。但這幾個存儲方式有個共性,那就是隻能在應用内通路存儲的資料,如果有需要共享的資料呢,就不能對外提供了,雖然SharedPreferences 存儲中提供了MODE_WORLD_READABLE 和MODE_WORLD_WRITEABLE這兩種操作模式,但這兩種模式在Android 4.2的版本已經被廢棄了。
那該如何實作跨程式的資料共享呢?此時,就引出了ContentProvider内容提供器,或許你會問,我們為什麼要實作跨程式的資料共享,很簡單,如果我們想在基礎系統上進行二次開發,想引用Android系統本身的資料,就需要一些程式的資料共享,比如,你想做基于通訊錄的二次開發,基于短信系統的二次開發,就需要用Android系統提供的資料,因為這些基礎資料本身被封裝到系統内,你也不太可能自己去設定吧,如果這些資料都不允許第三方的程式進行 通路的話,恐怕很多應用的功能都要大打折扣了。
内容提供器(ContentProvider)主要用于在不同的應用程式之間實作資料共享的功能, 它提供了一套完整的機制,允許一個程式通路另一個程式中的資料,同時還能保證被訪資料 的安全性。目前,使用内容提供器是 Android實作跨程式共享資料的标準方式。
ContentProvider的用法一般有兩種,一種是使用現有的ContentProdiver來讀取和操作相應程式中的資料,另一種是建立自己的ContentProvider給我們程式的資料提供外部通路接口。
利用現有的ContentProvider來讀取和操作
自己建立ContentProvider提供資料
對于每一個應用程式來說,如果想要通路内容提供器中共享的資料,就一定要借助 ContentResolve 類,可以通過 Context 中的 getContentResolver()方法擷取到該類的執行個體。 ContentResolver 中提供了一系列的方法用于對資料進行 CRUD 操作,其中 insert()方法用于 添加資料,update()方法用于更新資料,delete()方法用于删除資料,query()方法用于查詢數 據。
有沒有似曾相識的感覺?沒錯,SQLiteDatabase中也是使用的這幾個方法來進行 CRUD 操作的,隻不過它們在方法參數上稍微有一些差別。 不同于 SQLiteDatabase,ContentResolver中的增删改查方法都是不接收表名參數的,而 是使用一個 Uri參數代替,這個參數被稱為内容 URI。。内容 URI給内容提供器中的資料建立 了唯一辨別符,它主要由兩部分組成,權限(authority)和路徑(path)。權限是用于對不同 的應用程式做區分的,一般為了避免沖突,都會采用程式包名的方式來進行命名。比如某個 程式的包名是 com.example.app,那麼該程式對應的權限就可以命名為 com.example.app. provider。路徑則是用于對同一應用程式中不同的表做區分的,通常都會添加到權限的後面。 比如某個程式的資料庫裡存在兩張表,table1和 table2,這時就可以将路徑分别命名為/table1 和/table2,然後把權限和路徑進行組合,内容 URI就變成了 com.example.app.provider/table1 和 com.example.app.provider/table2。
内容 URI最标準的格式寫法:
在得到了内容 URI字元串之後,我們還需要将它解析成 Uri對象才可以作為參數傳入。 解析的方法也相當簡單,代碼如下所示:
隻需要調用 Uri.parse()方法,就可以将内容 URI字元串解析成 Uri對象了。
現在我們就可以使用這個 Uri對象來查詢 table1表中的資料了,代碼如下所示:
那麼我們可以看看getContentResolver()這個方法跟Sql部分的對應關系吧

查詢完成後傳回的仍然是一個 Cursor 對象,這時我們就可以将資料從 Cursor 對象中逐 個讀取出來了。讀取的思路仍然是通過移動遊标的位置來周遊 Cursor的所有行,然後再取出每一行中相應列的資料,代碼如下所示:
剩下的增加、修改、删除操作就簡單多了。
我們先來看看如何向 table1表中添加一條資料,代碼如下所示:
可以看到,仍然是将待添加的資料組裝到 ContentValues 中,然後調用 ContentResolver。
在table1中更新一條資料
在table1中删除一條資料
整體來說,我們可以利用現有的ContentProvider來擷取資料,比如讀取聯系人的資訊:
同時需要要注意的是在注冊清單中加上讀取聯系人的權限,表示允許應用通路聯系人資訊。
如果想要實作跨程式共享資料的功能,官方推薦的方式就是使用内容 提供器,可以通過建立一個類去繼承 ContentProvider的方式來建立一個自己的内容提供器。 ContentProvider類中有六個抽象方法,我們在使用子類繼承它的時候,需要将這六個方法全部重寫。
比如新寫一個MyProvider類,需要繼承ContentProvider。
在這六個方法中,相信大多數你都已經非常熟悉了。
onCreate() 初始化内容提供器的時候調用。通常會在這裡完成對資料庫的建立和更新等操作, 傳回 true 表示内容提供器初始化成功,傳回 false 則表示失敗。注意,隻有當存在 ContentResolver嘗試通路我們程式中的資料時,内容提供器才會被初始化。
query() 從内容提供器中查詢資料。使用 uri參數來确定查詢哪張表,projection參數用于确 定查詢哪些列,selection和 selectionArgs參數用于限制查詢哪些行,sortOrder參數用于 對結果進行排序,查詢的結果存放在 Cursor對象中傳回。
insert() 向内容提供器中添加一條資料。使用 uri 參數來确定要添加到的表,待添加的資料 儲存在 values參數中。添加完成後,傳回一個用于表示這條新記錄的 URI。
update() 更新内容提供器中已有的資料。使用 uri 參數來确定更新哪一張表中的資料,新數 據儲存在 values參數中,selection和 selectionArgs參數用于限制更新哪些行,受影響的 行數将作為傳回值傳回。
delete() 從内容提供器中删除資料。使用 uri 參數來确定删除哪一張表中的資料,selection和 selectionArgs參數用于限制删除哪些行,被删除的行數将作為傳回值傳回。
getType() 根據傳入的内容 URI來傳回相應的 MIME類型。
可以看到,幾乎每一個方法都會帶有Uri這個參數,這個參數也正是調用 ContentResolver 的增删改查方法時傳遞過來的。而現在我們需要對傳入的 Uri參數進行解析,從中分析出 調用方期望通路的表和資料。 回顧一下,一個标準的内容 URI寫法是這樣的:
這就表示調用方期望通路的是 com.example.app 這個應用的 table1 表中的資料。除此之 外,我們還可以在這個内容 URI的後面加上一個 id,如下所示:
這就表示調用方期望通路的是 com.example.app這個應用的 table1表中 id 為 1的資料。
内容 URI的格式主要就隻有以上兩種,以路徑結尾就表示期望通路該表中所有的資料, 以 id 結尾就表示期望通路該表中擁有相應 id 的資料。我們可以使用通配符的方式來分别匹 配這兩種格式的内容 URI,規則如下。
"*":表示比對任意長度的任意字元
"#" :表示比對任意長度的數字
content://com.example.app.provider/* 表示一個能夠比對任意表的内容 URI格式content://com.example.app.provider/table1/# 表示一個能夠比對 table1表中任意一行資料的内容 URI格式
此時,我們需要引出一個UriMatcher這個類,這個類就可以輕松地實作比對内容URI的功能。UriMatcher 中提供了一個 addURI()方法,這個方法接收三個參數,可以分别把權限、路徑和一個自定義 代碼傳進去。這樣,當調用 UriMatcher 的 match()方法時,就可以将一個 Uri 對象傳入,傳回值是某個能夠比對這個 Uri對象所對應的自定義代碼,利用這個代碼,我們就可以判斷出調用方期望通路的是哪張表中的資料了。
可以看到,MyProvider 中新增了四個整型常量,其中 TABLE1_DIR 表示通路 table1 表 中的所有資料,TABLE1_ITEM 表示通路 table1 表中的單條資料,TABLE2_DIR 表示通路 table2 表中的所有資料,TABLE2_ITEM 表示通路 table2 表中的單條資料。接着在靜态代碼塊裡我們建立了 UriMatcher 的執行個體,并調用 addURI()方法,将期望比對的内容 URI 格式傳 遞進去,注意這裡傳入的路徑參數是可以使用通配符的。然後當 query()方法被調用的時候, 就會通過 UriMatcher的 match()方法對傳入的 Uri 對象進行比對,如果發現 UriMatcher 中某 個内容 URI格式成功比對了該 Uri對象,則會傳回相應的自定義代碼,然後我們就可以判斷 出調用方期望通路的到底是什麼資料了。
除此之外,還有一個方法你會比較陌生,即 getType()方法。它是所有的内容提供器都必 須提供的一個方法,用于擷取 Uri對象所對應的 MIME類型。一個内容 URI所對應的 MIME 字元串主要由三部分組分,Android對這三個部分做了如下格式規定。
必須以 vnd開頭。
如果内容 URI 以路徑結尾,則後接 android.cursor.dir/,如果内容 URI 以 id 結尾, 則後接 android.cursor.item/。
最後接上 vnd..
。
到這裡,一個完整的内容提供器就建立完成了,現在任何一個應用程式都可以使用 ContentResolver來通路我們程式中的資料。那麼前面所提到的,如何才能保證隐私資料不會 洩漏出去呢?其實多虧了内容提供器的良好機制,這個問題在不知不覺中已經被解決了。因 為所有的 CRUD 操作都一定要比對到相應的内容 URI 格式才能進行的,而我們當然不可能 向 UriMatcher中添加隐私資料的 URI,是以這部分資料根本無法被外部程式通路到,安全問 題也就不存在了。
下面比如我們自己建立一個内容提供器
這裡結合了SQLiteDatabase資料庫的操作,把想把共享的資料開放出去。
不過同樣需要注意的是,需要在清單中注冊。
是以總的來說,ContentProvider是四大元件之一,這也是我們需要掌握的基礎知識之一,從通過分析為何需要ContentProvider,以及ContentProvider是什麼,并最後是如何使用,對這一個過程是不是清楚很多,當然這些都是基本用法,如果有興趣的話,可以具體去看源碼,了解其中實作的原理,很多時候我們要做到知其然而之是以然,這樣使用起來了,以後有什麼問題,可以迅速定位到其中原因。
源于對掌握的Android開發基礎點進行整理,羅列下已經總結的文章,從中可以看到技術積累的過程。
1,Android系統簡介
2,ProGuard代碼混淆
3,講講Handler+Looper+MessageQueue關系
4,Android圖檔加載庫了解
5,談談Android運作時權限了解
6,EventBus初了解
7,Android 常見工具類
8,對于Fragment的一些了解
9,Android 四大元件之 " Activity "
10,Android 四大元件之" Service "
11,Android 四大元件之“ BroadcastReceiver "
12,Android 四大元件之" ContentProvider "
13,講講 Android 事件攔截機制
14,Android 動畫的了解
15,Android 生命周期和啟動模式
16,Android IPC 機制
17,View 的事件體系
18,View 的工作原理
19,了解 Window 和 WindowManager
20,Activity 啟動過程分析
21,Service 啟動過程分析
22,Android 性能優化
23,Android 消息機制
24,Android Bitmap相關
25,Android 線程和線程池
26,Android 中的 Drawable 和動畫
27,RecylerView 中的裝飾者模式
28,Android 觸摸事件機制
29,Android 事件機制應用
30,Cordova 架構的一些了解
31,有關 Android 插件化思考
32,開發人員必備技能——單元測試