前言
- 作爲
Android
的四大組件之一,ContentProvider
可以說是無處不在了。 - 但是對於我而言,開發過程中看似
ContentProvider
用得很嫺熟,卻一直沒能形成一個完整的體系。 - 也許大家也有着和我類似的煩惱,於是我特地花了幾天的時間,總結了我所知道的知識點,以及面試中可能遇到的問題。將本文分享給大家,希望能幫助大家重新梳理下我們的這個老朋友
ContentProvider
。
希望大家閱讀愉快!
文章目錄
-
ContentProvider
應用程序間非常通用的共享數據的一種方式,也是Android
官方推薦的方式。 -
Android
中許多系統應用都使用該方式實現數據共享,比如通訊錄、短信等。
1.1 Android 爲什麼要設計 ContentProvider 這個組件?
- 很多做
Android
開發的人都不怎麼使用它,覺得直接讀取數據庫會更簡單方便。 - 那麼
Android
搞一個內容提供者在數據和應用之間,只是爲了裝高大上,故弄玄虛?我認爲其設計用意在於:
- 封裝。對數據進行封裝,提供統一的接口,使用者完全不必關心這些數據是在
DB
,XML
、Preferences
或者網絡請求來的。當項目需求要改變數據來源時,使用我們的地方完全不需要修改。 - 提供一種跨進程數據共享的方式。
- 應用程序間的數據共享還有另外的一個重要話題,就是數據更新通知機制了。因爲數據是在多個應用程序中共享的,當其中一個應用程序改變了這些共享數據的時候,它有責任通知其它應用程序,讓它們知道共享數據被修改了,這樣它們就可以作相應的處理。
1.2 如何訪問自定義 ContentProvider
-
ContentResolver
接口的notifyChange
函數來通知那些註冊了監控特定 URI的ContentObserver 對象,使得它們可以相應地執行一些處理。 - ContentObserver 可以通過 registerContentObserver 進行註冊。
- 通過
ContentProvider
的Uri
訪問開放的數據。
-
ContenResolver
對象通過Context
提供的方法getContenResolver()
來獲得。 -
ContenResolver
提供了以下方法來操作:insert
delete
update
query
這些方法分別會調用ContenProvider
中與之對應的方法並得到返回的結果。
1.3 通過 ContentResolver 獲取 ContentProvider 內容的基本步驟
- 得到
ContentResolver
類對象:ContentResolver cr = getContentResolver ( )
。 - 定義要查詢的字段
String
數組。 - 使用
cr.query()
; 返回一個Cursor
對象。 - 使用
while
循環得到Cursor
裏面的內容。
1.4 ContentProvider 是如何實現數據共享的:
- 在
Android
中如果想將自己應用的數據 ( 一般多爲數據庫中的數據 ) 提供給第三發應用, 那麼我們只能通過ContentProvider
來實現了。ContentProvider
是應用程序之間共享數據的接口。 - 使用的時候首先自定義 一個類繼承
ContentProvider
, 然後覆寫query
、insert
、update
、delete
等 方法。 - 因爲其是四大組件之一因此必須在
AndroidManifest
文件中進行註冊。 - 把自己的數據通過
uri
的形式共享出去android
系統下 不同程序 數據默認是不能共享訪問 需要去實現一個類去繼承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 的實現上有什麼差別?
-
ContentProvider
屏蔽了數據存儲的細節 , 內部實現對用戶完全透明 , 用戶只需要關心操作數據的uri
就可以了,ContentProvider
可以實現不同app
之間 共享。 -
Sql
也有增刪改查的方法, 但是sql
只能查詢本應用下的數據庫。 - 而
ContentProvider
還可以去增刪改查本地文件.xml
文件的讀取等。
1.6 Uri 介紹
- 爲系統的每一個資源給其一個名字,比方說通話記錄。
- 每一個
ContentProvider
都擁有一個公共的URI
,這個URI
用於表示這個ContentProvider
所提供的數據。 -
Android
所提供的ContentProvider
都存放在android.provider
包中。
- 將其分爲
A,B,C,D
4個部分: -
A
:標準前綴,用來說明一個Content Provider
控制這些數據,無法改變的;"content://"
; -
B
:URI
的標識,用於唯一標識這個ContentProvider
,外部調用者可以根據這個標識來找到它。它定義了是哪個ContentProvider
提供這些數據。對於第三方應用程序,爲了保證URI
標識的唯一性,它必須是一個完整的、小寫的類名。這個標識在元素的authorities
屬性中說明:一般是定義該ContentProvider
的包類的名稱; -
C
:路徑(path
),通俗的講就是你要操作的數據庫中表的名字,或者你也可以自己定義,記得在使用的時候保持一致就可以了;"content://com.bing.provider.myprovider/tablename"
。 -
D
:如果URI中包含表示需要獲取的記錄的ID
;則就返回該id對應的數據,如果沒有ID
,就表示返回全部;"content://com.bing.provider.myprovider/tablename/#"
#
表示數據id
。
1.7 如何訪問 asserts 資源目錄下的數據庫?
- 把數據庫
db
複製到/data/data/packagename/databases/
目錄下, 然後直接就能訪問了。
1.8 多個進程同時調用一個 ContentProvider 的 query 獲取數據,ContentPrvoider 是如何反應的呢?
- 一個
ContentProvider
可以接受來自另外一個進程的數據請求。 - 儘管
ContentResolver
與ContentProvider
類隱藏了實現細節,但是ContentProvider
所提供的query()
,insert()
,delete()
,update()
都是在ContentProvider
進程的線程池中被調用執行的,而不是進程的主線程中。 - 這個線程池是有
Binder
創建和維護的,其實使用的就是每個應用進程中的Binder
線程池。
1.9 Android 設計 ContentProvider 的目的是什麼呢?
- 隱藏數據的實現方式,對外提供統一的數據訪問接口;
- 更好的數據訪問權限管理。
ContentProvider
可以對開發的數據進行權限設置,不同的URI
可以對應不同的權限,只有符合權限要求的組件才能訪問到ContentProvider
的具體操作。 -
ContentProvider
封裝了跨進程共享的邏輯,我們只需要Uri
即可訪問數據。由系統來管理ContentProvider
的創建、生命週期及訪問的線程分配,簡化我們在應用間共享數據( 進程間通信 )的方式。我們只管通過ContentResolver
訪問ContentProvider
所提示的數據接口,而不需要擔心它所在進程是啓動還是未啓動。
1.10 運行在主線程的 ContentProvider 爲什麼不會影響主線程的UI操作?
-
ContentProvider
的onCreate()
是運行在UI
線程的,而query()
,insert()
,delete()
,update()
是運行在線程池中的工作線程的 - 所以調用這向個方法並不會阻塞
ContentProvider
所在進程的主線程,但可能會阻塞調用者所在的進程的UI
線程! - 所以,調用
ContentProvider
的操作仍然要放在子線程中去做。 - 雖然直接的
CRUD
的操作是在工作線程的,但系統會讓你的調用線程等待這個異步的操作完成,你纔可以繼續線程之前的工作。
1.11 外提供數據共享,那麼如何限制對方的使用呢?
-
android:exported
屬性非常重要。這個屬性用於指示該服務是否能夠被其他應用程序組件調用或跟它交互。 - 如果設置爲
true
,則能夠被調用或交互,否則不能。 - 設置爲
false
時,只有同一個應用程序的組件或帶有相同用戶ID
的應用程序才能啓動或綁定該服務。 - 對於需要開放的組件應設置合理的權限,如果只需要對同一個簽名的其它應用開放
ContentProvider
,則可以設置signature
級別的權限。 - 大家可以參考一下系統自帶應用的代碼,自定義了
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
給其他的應用訪問呢?
- 可以參考
Provider
的URI
權限設置,只允許訪問部份URI
,可以參考原生ContactsProvider2
的相關代碼( 注意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 是單例的 - 無論哪個客戶端應用的訪問都將是同一個
ContentProvider
對象,如果設爲true
,系統會爲每一個訪問該ContentProvider
的進程創建一個實例。
1.12.1 這點還是比較好理解的,那如果我要問每個 ContentProvider 的操作是在哪個線程中運行的呢?( 其實我們關心的是 UI 線程和工作線程 )
- 比如我們在UI線程調用getContentResolver().query查詢數據,而當數據量很大時(或者需要進行較長時間的計算)會不會阻塞UI線程呢?
- 要分兩種情況回答這個問題:
-
ContentProvider
和調用者在同一個進程,ContentProvider
的方法(query/insert/update/delete
等 )和調用者在同一線程中; -
ContentProvider
和調用者在不同的進程,ContentProvider
的方法會運行在它自身所在進程的一個 Binder 線程中。但是,注意這兩種方式在 `ContentProvider` 的方法沒有執行完成前都會 `blocked` 調用者。所以你應該知道這個上面這個問題的答案了吧。
- 也可以看看
CursorLoader
這個類的源碼,看Google
自己是怎麼使用getContentResolver().query
的。
1.13 ContentProvider 是如何在不同應用程序之間傳輸數據的?
- 一個應用進程有
16
個Binder
線程去和遠程服務進行交互,而每個線程可佔用的緩存空間是128KB
這樣,超過會報異常。 -
ContentResolver
雖然是通過Binder
進程間通信機制打通了應用程序之間共享數據的通道,但ContentProvider
組件在不同應用程序之間傳輸數據是基於匿名共享內存機制來實現的。 - 有興趣的可以查看一下老羅的文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃。
總結
- 在這篇文章中,我對我所知道的
ContentProvider
知識總進行了詳細的總結,希望大家通過本次閱讀都能有所收穫。 -
重點
:學Android
有一段時間了,我打算好好的梳理一下所學知識,到現在爲止,我才總結完Activity
、Service
、BroadcastRecevier
等,有關 事件分發、滑動衝突、新能優化等重要模塊,後面也將詳盡的總結,歡迎大家關注,方便及時接收更新 。 - 如果有可以補充的知識點,歡迎大家在評論區指出。
最後
今天分享的面試題就到這裏,還是那句話,有些東西你不僅要懂,而且要能夠很好地表達出來,能夠讓面試官認可你的理解,例如Handler機制,這個是面試必問之題。有些晦澀的點,或許它只活在面試當中,實際工作當中你壓根不會用到它,但是你要知道它是什麼東西。
不管怎麼樣,不論是什麼樣的大小面試,要想不被面試官虐的不要不要的,只有刷爆面試題題做好全面的準備,當然除了這個還需要在平時把自己的基礎打紮實,這樣不論面試官怎麼樣一個知識點裏往死裏鑿,你也能應付如流啊~