前言
- 作為
Android
的四大組件之一趾疚,ContentProvider
可以說是無處不在了。 - 但是對于我而言丛肮,開發(fā)過程中看似
ContentProvider
用得很嫻熟魄缚,卻一直沒能形成一個完整的體系冶匹。 - 也許大家也有著和我類似的煩惱,于是我特地花了幾天的時間诽里,總結(jié)了我所知道的知識點飞蛹,以及面試中可能遇到的問題卧檐。將本文分享給大家,希望能幫助大家重新梳理下我們的這個老朋友
ContentProvider
拒贱。
希望大家閱讀愉快佛嬉!
文章目錄
-
ContentProvider
應(yīng)用程序間非常通用的共享數(shù)據(jù)的一種方式暖呕,也是Android
官方推薦的方式。 -
Android
中許多系統(tǒng)應(yīng)用都使用該方式實現(xiàn)數(shù)據(jù)共享瓤逼,比如通訊錄霸旗、短信等戚揭。
方便大家學(xué)習(xí)民晒,我在 GitHub 上建立個 倉庫
倉庫內(nèi)容與博客同步更新。由于我在
稀土掘金
簡書
CSDN
博客園
等站點靴姿,都有新內(nèi)容發(fā)布佛吓。所以大家可以直接關(guān)注該倉庫辈毯,以免錯過精彩內(nèi)容!倉庫地址:
超級干貨钝凶!精心歸納Android
耕陷、JVM
据沈、算法等锌介,各位帥氣的老鐵支持一下!給個 Star 隆敢!
1.1 Android 為什么要設(shè)計 ContentProvider 這個組件拂蝎?
- 很多做
Android
開發(fā)的人都不怎么使用它惶室,覺得直接讀取數(shù)據(jù)庫會更簡單方便皇钞。 - 那么
Android
搞一個內(nèi)容提供者在數(shù)據(jù)和應(yīng)用之間鹅士,只是為了裝高大上,故弄玄虛也拜?我認為其設(shè)計用意在于:
- 封裝慢哈。對數(shù)據(jù)進行封裝永票,提供統(tǒng)一的接口侣集,使用者完全不必關(guān)心這些數(shù)據(jù)是在
DB
,XML
编振、Preferences
或者網(wǎng)絡(luò)請求來的踪央。當(dāng)項目需求要改變數(shù)據(jù)來源時瓢阴,使用我們的地方完全不需要修改荣恐。 - 提供一種跨進程數(shù)據(jù)共享的方式。
- 應(yīng)用程序間的數(shù)據(jù)共享還有另外的一個重要話題少漆,就是數(shù)據(jù)更新通知機制了检疫。因為數(shù)據(jù)是在多個應(yīng)用程序中共享的祷嘶,當(dāng)其中一個應(yīng)用程序改變了這些共享數(shù)據(jù)的時候论巍,它有責(zé)任通知其它應(yīng)用程序嘉汰,讓它們知道共享數(shù)據(jù)被修改了,這樣它們就可以作相應(yīng)的處理双泪。
1.2 如何訪問自定義 ContentProvider
-
ContentResolver
接口的notifyChange
函數(shù)來通知那些注冊了監(jiān)控特定 URI的ContentObserver 對象焙矛,使得它們可以相應(yīng)地執(zhí)行一些處理村斟。 - ContentObserver 可以通過 registerContentObserver 進行注冊。
- 通過
ContentProvider
的Uri
訪問開放的數(shù)據(jù)孩灯。
-
ContenResolver
對象通過Context
提供的方法getContenResolver()
來獲得峰档。 -
ContenResolver
提供了以下方法來操作:insert
delete
update
query
這些方法分別會調(diào)用ContenProvider
中與之對應(yīng)的方法并得到返回的結(jié)果匣距。
1.3 通過 ContentResolver 獲取 ContentProvider 內(nèi)容的基本步驟
- 得到
ContentResolver
類對象:ContentResolver cr = getContentResolver ( )
毅待。 - 定義要查詢的字段
String
數(shù)組尸红。 - 使用
cr.query()
; 返回一個Cursor
對象外里。 - 使用
while
循環(huán)得到Cursor
里面的內(nèi)容。
1.4 ContentProvider 是如何實現(xiàn)數(shù)據(jù)共享的:
- 在
Android
中如果想將自己應(yīng)用的數(shù)據(jù) ( 一般多為數(shù)據(jù)庫中的數(shù)據(jù) ) 提供給第三發(fā)應(yīng)用, 那么我們只能通過ContentProvider
來實現(xiàn)了鳖链。ContentProvider
是應(yīng)用程序之間共享數(shù)據(jù)的接口芙委。 - 使用的時候首先自定義 一個類繼承
ContentProvider
, 然后覆寫query
灌侣、insert
吴藻、update
、delete
等 方法皮壁。 - 因為其是四大組件之一因此必須在
AndroidManifest
文件中進行注冊符喝。 - 把自己的數(shù)據(jù)通過
uri
的形式共享出去android
系統(tǒng)下 不同程序 數(shù)據(jù)默認是不能共享訪問 需要去實現(xiàn)一個類去繼承ContentProvider
协饲。
public class PersonContentProvider extends ContentProvider{
public boolean onCreate(){ }
query(Url, String[], String, String[], String);
insert(Uri,ContentValues);
update(Uri,ContentValues,String[]);
delete(Uri,String,String[]);
}
1.5 為什么要用 ContentProvider ?它和 sql 的實現(xiàn)上有什么差別?
-
ContentProvider
屏蔽了數(shù)據(jù)存儲的細節(jié) , 內(nèi)部實現(xiàn)對用戶完全透明 , 用戶只需要關(guān)心操作數(shù)據(jù)的uri
就可以了,ContentProvider
可以實現(xiàn)不同app
之間 共享茉稠。 -
Sql
也有增刪改查的方法, 但是sql
只能查詢本應(yīng)用下的數(shù)據(jù)庫而线。 - 而
ContentProvider
還可以去增刪改查本地文件.xml
文件的讀取等膀篮。
1.6 Uri 介紹
- 為系統(tǒng)的每一個資源給其一個名字誓竿,比方說通話記錄谈截。
- 每一個
ContentProvider
都擁有一個公共的URI
簸喂,這個URI
用于表示這個ContentProvider
所提供的數(shù)據(jù)喻鳄。 -
Android
所提供的ContentProvider
都存放在android.provider
包中。
- 將其分為
A再菊,B袄简,C泛啸,D
4個部分: -
A
:標(biāo)準(zhǔn)前綴,用來說明一個Content Provider
控制這些數(shù)據(jù)种柑,無法改變的匹耕;"content://"
稳其; -
B
:URI
的標(biāo)識,用于唯一標(biāo)識這個ContentProvider
煤傍,外部調(diào)用者可以根據(jù)這個標(biāo)識來找到它蚯姆。它定義了是哪個ContentProvider
提供這些數(shù)據(jù)龄恋。對于第三方應(yīng)用程序凶伙,為了保證URI
標(biāo)識的唯一性镊靴,它必須是一個完整的、小寫的類名煮落。這個標(biāo)識在元素的authorities
屬性中說明:一般是定義該ContentProvider
的包類的名稱蝉仇; -
C
:路徑(path
)轿衔,通俗的講就是你要操作的數(shù)據(jù)庫中表的名字睦疫,或者你也可以自己定義蛤育,記得在使用的時候保持一致就可以了;"content://com.bing.provider.myprovider/tablename"
腋么。 -
D
:如果URI中包含表示需要獲取的記錄的ID
亥揖;則就返回該id對應(yīng)的數(shù)據(jù)费变,如果沒有ID
胡控,就表示返回全部;"content://com.bing.provider.myprovider/tablename/#"
#
表示數(shù)據(jù)id
。
1.7 如何訪問 asserts 資源目錄下的數(shù)據(jù)庫?
- 把數(shù)據(jù)庫
db
復(fù)制到/data/data/packagename/databases/
目錄下, 然后直接就能訪問了橙困。
1.8 多個進程同時調(diào)用一個 ContentProvider 的 query 獲取數(shù)據(jù)凡傅,ContentPrvoider 是如何反應(yīng)的呢夏跷?
- 一個
ContentProvider
可以接受來自另外一個進程的數(shù)據(jù)請求猫态。 - 盡管
ContentResolver
與ContentProvider
類隱藏了實現(xiàn)細節(jié)披摄,但是ContentProvider
所提供的query()
疚膊,insert()
寓盗,delete()
夺巩,update()
都是在ContentProvider
進程的線程池中被調(diào)用執(zhí)行的柳譬,而不是進程的主線程中美澳。 - 這個線程池是有
Binder
創(chuàng)建和維護的摸航,其實使用的就是每個應(yīng)用進程中的Binder
線程池酱虎。
1.9 Android 設(shè)計 ContentProvider 的目的是什么呢读串?
- 隱藏數(shù)據(jù)的實現(xiàn)方式恢暖,對外提供統(tǒng)一的數(shù)據(jù)訪問接口杰捂;
- 更好的數(shù)據(jù)訪問權(quán)限管理。
ContentProvider
可以對開發(fā)的數(shù)據(jù)進行權(quán)限設(shè)置挨队,不同的URI
可以對應(yīng)不同的權(quán)限盛垦,只有符合權(quán)限要求的組件才能訪問到ContentProvider
的具體操作熄浓。 -
ContentProvider
封裝了跨進程共享的邏輯赌蔑,我們只需要Uri
即可訪問數(shù)據(jù)娃惯。由系統(tǒng)來管理ContentProvider
的創(chuàng)建趾浅、生命周期及訪問的線程分配馒稍,簡化我們在應(yīng)用間共享數(shù)據(jù)( 進程間通信 )的方式纽谒。我們只管通過ContentResolver
訪問ContentProvider
所提示的數(shù)據(jù)接口鼓黔,而不需要擔(dān)心它所在進程是啟動還是未啟動澳化。
1.10 運行在主線程的 ContentProvider 為什么不會影響主線程的UI操作?
-
ContentProvider
的onCreate()
是運行在UI
線程的缎谷,而query()
灶似,insert()
喻奥,delete()
撞蚕,update()
是運行在線程池中的工作線程的 - 所以調(diào)用這向個方法并不會阻塞
ContentProvider
所在進程的主線程甥厦,但可能會阻塞調(diào)用者所在的進程的UI
線程刀疙! - 所以扫倡,調(diào)用
ContentProvider
的操作仍然要放在子線程中去做撵溃。 - 雖然直接的
CRUD
的操作是在工作線程的缘挑,但系統(tǒng)會讓你的調(diào)用線程等待這個異步的操作完成语淘,你才可以繼續(xù)線程之前的工作际歼。
1.11 外提供數(shù)據(jù)共享鹅心,那么如何限制對方的使用呢旭愧?
android:exported
屬性非常重要榕茧。這個屬性用于指示該服務(wù)是否能夠被其他應(yīng)用程序組件調(diào)用或跟它交互用押。如果設(shè)置為
true
靶剑,則能夠被調(diào)用或交互桩引,否則不能坑匠。設(shè)置為
false
時厘灼,只有同一個應(yīng)用程序的組件或帶有相同用戶ID
的應(yīng)用程序才能啟動或綁定該服務(wù)设凹。對于需要開放的組件應(yīng)設(shè)置合理的權(quán)限舰讹,如果只需要對同一個簽名的其它應(yīng)用開放
ContentProvider
,則可以設(shè)置signature
級別的權(quán)限闪朱。大家可以參考一下系統(tǒng)自帶應(yīng)用的代碼月匣,自定義了
signature
級別的permission
:
<permission android:name="com.android.gallery3d.filtershow.permission.READ"
android:protectionLevel="signature" />
<permission android:name="com.android.gallery3d.filtershow.permission.WRITE"
android:protectionLevel="signature" />
<provider
android:name="com.android.gallery3d.filtershow.provider.SharedImageProvider"
android:authorities="com.android.gallery3d.filtershow.provider.SharedImageProvider"
android:grantUriPermissions="true"
android:readPermission="com.android.gallery3d.filtershow.permission.READ"
android:writePermission="com.android.gallery3d.filtershow.permission.WRITE" />
1.11.1 如果我們只需要開放部份的 URI
給其他的應(yīng)用訪問呢?
- 可以參考
Provider
的URI
權(quán)限設(shè)置奋姿,只允許訪問部份URI
锄开,可以參考原生ContactsProvider2
的相關(guān)代碼( 注意path-permission
這個選項 ):
<provider android:name="ContactsProvider2"
android:authorities="contacts;com.android.contacts"
android:label="@string/provider_label"
android:multiprocess="false"
android:exported="true"
android:grantUriPermissions="true"
android:readPermission="android.permission.READ_CONTACTS"
android:writePermission="android.permission.WRITE_CONTACTS">
<path-permission
android:pathPrefix="/search_suggest_query"
android:readPermission="android.permission.GLOBAL_SEARCH" />
<path-permission
android:pathPrefix="/search_suggest_shortcut"
android:readPermission="android.permission.GLOBAL_SEARCH" />
<path-permission
android:pathPattern="/contacts/.*/photo"
android:readPermission="android.permission.GLOBAL_SEARCH" />
<grant-uri-permission android:pathPattern=".*" />
</provider>
1.12 ContentProvider 接口方法運行在哪個線程中呢胀蛮?
-
ContentProvider
可以在AndroidManifest.xml
中配置一個叫做android:multiprocess
的屬性院刁,默認值是 false ,表示 ContentProvider 是單例的 - 無論哪個客戶端應(yīng)用的訪問都將是同一個
ContentProvider
對象粪狼,如果設(shè)為true
退腥,系統(tǒng)會為每一個訪問該ContentProvider
的進程創(chuàng)建一個實例任岸。
1.12.1 這點還是比較好理解的,那如果我要問每個 ContentProvider 的操作是在哪個線程中運行的呢?( 其實我們關(guān)心的是 UI 線程和工作線程 )
比如我們在UI線程調(diào)用getContentResolver().query查詢數(shù)據(jù)狡刘,而當(dāng)數(shù)據(jù)量很大時(或者需要進行較長時間的計算)會不會阻塞UI線程呢享潜?
要分兩種情況回答這個問題:
-
ContentProvider
和調(diào)用者在同一個進程,ContentProvider
的方法(query/insert/update/delete
等 )和調(diào)用者在同一線程中嗅蔬; -
ContentProvider
和調(diào)用者在不同的進程剑按,ContentProvider
的方法會運行在它自身所在進程的一個 Binder 線程中。
但是澜术,注意這兩種方式在ContentProvider
的方法沒有執(zhí)行完成前都會blocked
調(diào)用者艺蝴。所以你應(yīng)該知道這個上面這個問題的答案了吧。 - 也可以看看
CursorLoader
這個類的源碼鸟废,看Google
自己是怎么使用getContentResolver().query
的猜敢。
1.13 ContentProvider 是如何在不同應(yīng)用程序之間傳輸數(shù)據(jù)的?
- 一個應(yīng)用進程有
16
個Binder
線程去和遠程服務(wù)進行交互盒延,而每個線程可占用的緩存空間是128KB
這樣缩擂,超過會報異常。 -
ContentResolver
雖然是通過Binder
進程間通信機制打通了應(yīng)用程序之間共享數(shù)據(jù)的通道添寺,但ContentProvider
組件在不同應(yīng)用程序之間傳輸數(shù)據(jù)是基于匿名共享內(nèi)存機制來實現(xiàn)的胯盯。 - 有興趣的可以查看一下老羅的文章Android系統(tǒng)匿名共享內(nèi)存Ashmem(Anonymous Shared Memory)簡要介紹和學(xué)習(xí)計劃。
總結(jié)
- 在這篇文章中计露,我對我所知道的
BroadcastReceiver
知識總進行了詳細的總結(jié)博脑,希望大家通過本次閱讀都能有所收獲。 -
重點
:學(xué)Android
有一段時間了票罐,我打算好好的梳理一下所學(xué)知識趋厉,到現(xiàn)在為止,我才總結(jié)完Activity
胶坠、Service
、BroadcastRecevier
等繁堡,有關(guān) 事件分發(fā)沈善、滑動沖突、新能優(yōu)化等重要模塊椭蹄,我后面也將詳盡的總結(jié)闻牡,歡迎大家關(guān)注 _yuanhao 的簡書~ ,方便及時接收更新 绳矩,方便及時接收更新 - 如果有可以補充的知識點罩润,歡迎大家在評論區(qū)指出。
碼字不易翼馆,你的點贊是我總結(jié)的最大動力割以!
由于我在「稀土掘金」「簡書」「
CSDN
」「博客園」等站點金度,都有新內(nèi)容發(fā)布。所以大家可以直接關(guān)注我的GitHub
倉庫严沥,以免錯過精彩內(nèi)容猜极!倉庫地址:
超級干貨!精心歸納Android
消玄、JVM
跟伏、算法等,各位帥氣的老鐵支持一下翩瓜!給個 Star 受扳!一萬多字長文,加上精美思維導(dǎo)圖兔跌,記得點贊哦勘高,歡迎關(guān)注 _yuanhao 的 簡書~ ,方便及時接收更新 浮定,我們下篇文章見相满!