Android 這 13 道 ContentProvider 面試題,你都會了嗎?

前言


  • 做爲 Android 的四大組件之一,ContentProvider 能夠說是無處不在了。
  • 可是對於我而言,開發過程當中看似 ContentProvider 用得很嫺熟,卻一直沒能造成一個完整的體系。
  • 也許你們也有着和我相似的煩惱,因而我特意花了幾天的時間,總結了我所知道的知識點,以及面試中可能遇到的問題。將本文分享給你們,但願能幫助你們從新梳理下咱們的這個老朋友 ContentProvider

最後,但願你們閱讀愉快!java

文章目錄

  • ContentProvider 應用程序間很是通用的共享數據的一種方式,也是 Android 官方推薦的方式。
  • Android 中許多系統應用都使用該方式實現數據共享,好比通信錄、短信等。

ContentProvider

方便你們學習,我在 GitHub 上創建個 倉庫


1.1 Android 爲何要設計 ContentProvider 這個組件?

爲何要設計 ContentProvider

  • 不少作 Android 開發的人都不怎麼使用它,以爲直接讀取數據庫會更簡單方便。
  • 那麼 Android 搞一個內容提供者在數據和應用之間,只是爲了裝高大上,故弄玄虛?我認爲其設計用意在於:
  1. 封裝。對數據進行封裝,提供統一的接口,使用者徹底沒必要關心這些數據是在 DBXMLPreferences 或者網絡請求來的。當項目需求要改變數據來源時,使用咱們的地方徹底不須要修改。
  2. 提供一種跨進程數據共享的方式。
  3. 應用程序間的數據共享還有另外的一個重要話題,就是數據更新通知機制了。由於數據是在多個應用程序中共享的,當其中一個應用程序改變了這些共享數據的時候,它有責任通知其它應用程序,讓它們知道共享數據被修改了,這樣它們就能夠做相應的處理。

1.2 如何訪問自定義 ContentProvider

如何訪問自定義 ContentProvider

  • ContentResolver 接口的 notifyChange 函數來通知那些註冊了監控特定 URI的ContentObserver 對象,使得它們能夠相應地執行一些處理。
  • ContentObserver 能夠經過 registerContentObserver 進行註冊。
  • 經過 ContentProviderUri 訪問開放的數據。
  1. ContenResolver 對象經過 Context 提供的方法 getContenResolver() 來得到。
  2. ContenResolver 提供瞭如下方法來操做:insert delete update query 這些方法分別會調用 ContenProvider 中與之對應的方法並獲得返回的結果。

1.3 經過 ContentResolver 獲取 ContentProvider 內容的基本步驟

ContentResolver 獲取 ContentProvider 內容的基本步驟

  1. 獲得 ContentResolver 類對象:ContentResolver cr = getContentResolver ( )
  2. 定義要查詢的字段 String 數組。
  3. 使用 cr.query() ; 返回一個 Cursor 對象。
  4. 使用 while 循環獲得 Cursor 裏面的內容。

1.4 ContentProvider 是如何實現數據共享的:

ContentProvider 是如何實現數據共享的

  • Android 中若是想將本身應用的數據 ( 通常多爲數據庫中的數據 ) 提供給第三發應用, 那麼咱們只能經過 ContentProvider 來實現了。 ContentProvider 是應用程序之間共享數據的接口。
  • 使用的時候首先自定義 一個類繼承 ContentProvider , 而後覆寫 queryinsertupdatedelete 等 方法。
  • 由於其是四大組件之一所以必須在 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 ?它和 sql 的實現上有什麼差異

  • ContentProvider 屏蔽了數據存儲的細節 , 內部實現對用戶徹底透明 , 用戶只須要關心操做數據的 uri 就能夠了, ContentProvider 能夠實現不一樣 app之間 共享。
  • Sql 也有增刪改查的方法, 可是 sql 只能查詢本應用下的數據庫。
  • ContentProvider 還能夠去增刪改查本地文件. xml 文件的讀取等。

1.6 Uri 介紹

Uri 介紹

  • 爲系統的每個資源給其一個名字,比方說通話記錄。
  1. 每個 ContentProvider 都擁有一個公共的 URI ,這個 URI 用於表示這個 ContentProvider 所提供的數據。
  2. Android 所提供的 ContentProvider 都存放在 android.provider 包中。
  • 將其分爲 A,B,C,D 4個部分:
  • A:標準前綴,用來講明一個 Content Provider 控制這些數據,沒法改變的;"content://"
  • BURI 的標識,用於惟一標識這個 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 資源目錄下的數據庫?

訪問 asserts 資源目錄下的數據庫

  • 把數據庫 db 複製到 /data/data/packagename/databases/ 目錄下, 而後直接就能訪問了。

1.8 多個進程同時調用一個 ContentProvider 的 query 獲取數據,ContentPrvoider 是如何反應的呢?

調用一個 ContentProvider 的 query 獲取數據,ContentPrvoider 是如何反應的

  • 一個 ContentProvider 能夠接受來自另一個進程的數據請求。
  • 儘管 ContentResolverContentProvider 類隱藏了實現細節,可是 ContentProvider 所提供的 query()insert()delete()update() 都是在 ContentProvider 進程的線程池中被調用執行的,而不是進程的主線程中。
  • 這個線程池是有 Binder 建立和維護的,其實使用的就是每一個應用進程中的 Binder 線程池。

1.9 Android 設計 ContentProvider 的目的是什麼呢?

設計 ContentProvider 的目的

  • 隱藏數據的實現方式,對外提供統一的數據訪問接口;
  • 更好的數據訪問權限管理。ContentProvider 能夠對開發的數據進行權限設置,不一樣的 URI 能夠對應不一樣的權限,只有符合權限要求的組件才能訪問到 ContentProvider 的具體操做。
  • ContentProvider 封裝了跨進程共享的邏輯,咱們只須要 Uri 便可訪問數據。由系統來管理 ContentProvider 的建立、生命週期及訪問的線程分配,簡化咱們在應用間共享數據( 進程間通訊 )的方式。咱們只管經過 ContentResolver 訪問 ContentProvider 所提示的數據接口,而不須要擔憂它所在進程是啓動仍是未啓動。

1.10 運行在主線程的 ContentProvider 爲何不會影響主線程的UI操做?

ContentProvider 爲何不會影響主線程的UI操做

  • ContentProvideronCreate() 是運行在 UI 線程的,而 query()insert()delete()update() 是運行在線程池中的工做線程的
  • 因此調用這向個方法並不會阻塞 ContentProvider 所在進程的主線程,但可能會阻塞調用者所在的進程的 UI 線程!
  • 因此,調用 ContentProvider 的操做仍然要放在子線程中去作。
  • 雖然直接的 CRUD 的操做是在工做線程的,但系統會讓你的調用線程等待這個異步的操做完成,你才能夠繼續線程以前的工做。

1.11 外提供數據共享,那麼如何限制對方的使用呢?

如何限制對方的使用

  • android:exported 屬性很是重要。這個屬性用於指示該服務是否可以被其餘應用程序組件調用或跟它交互。
  • 若是設置爲 true,則可以被調用或交互,不然不能。
  • 設置爲 false 時,只有同一個應用程序的組件或帶有相同用戶 ID 的應用程序才能啓動或綁定該服務。github

  • 對於須要開放的組件應設置合理的權限,若是隻須要對同一個簽名的其它應用開放 ContentProvider ,則能夠設置 signature 級別的權限。
  • 你們能夠參考一下系統自帶應用的代碼,自定義了 signature 級別的 permissionweb

<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 給其餘的應用訪問呢?

若是咱們只須要開放部份的 URI 給其餘的應用訪問呢

  • 能夠參考 ProviderURI 權限設置,只容許訪問部份 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 接口方法運行在哪一個線程中

  • ContentProvider 能夠在 AndroidManifest.xml 中配置一個叫作 android:multiprocess 的屬性,默認值是 false ,表示 ContentProvider 是單例的
  • 不管哪一個客戶端應用的訪問都將是同一個 ContentProvider 對象,若是設爲 true ,系統會爲每個訪問該 ContentProvider 的進程建立一個實例。

1.12.1 這點仍是比較好理解的,那若是我要問每一個 ContentProvider 的操做是在哪一個線程中運行的呢?( 其實咱們關心的是 UI 線程和工做線程 )

每一個 ContentProvider 的操做是在哪一個線程中運行的

  • 好比咱們在UI線程調用getContentResolver().query查詢數據,而當數據量很大時(或者須要進行較長時間的計算)會不會阻塞UI線程呢?面試

  • 要分兩種狀況回答這個問題:算法

  1. ContentProvider 和調用者在同一個進程,ContentProvider 的方法( query/insert/update/delete 等 )和調用者在同一線程中;
  2. ContentProvider 和調用者在不一樣的進程,ContentProvider 的方法會運行在它自身所在進程的一個 Binder 線程中。
    可是,注意這兩種方式在 ContentProvider 的方法沒有執行完成前都會 blocked 調用者。因此你應該知道這個上面這個問題的答案了吧。
  3. 也能夠看看 CursorLoader 這個類的源碼,看 Google 本身是怎麼使用 getContentResolver().query 的。

1.13 ContentProvider 是如何在不一樣應用程序之間傳輸數據的?

ContentProvider 是如何在不一樣應用程序之間傳輸數據

  • 一個應用進程有 16Binder 線程去和遠程服務進行交互,而每一個線程可佔用的緩存空間是 128KB 這樣,超過會報異常。
  • ContentResolver 雖然是經過 Binder 進程間通訊機制打通了應用程序之間共享數據的通道,但 ContentProvider 組件在不一樣應用程序之間傳輸數據是基於匿名共享內存機制來實現的。
  • 有興趣的能夠查看一下老羅的文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃

ContentProvider

總結


  1. 在這篇文章中,我對我所知道的 BroadcastReceiver 知識總進行了詳細的總結,但願你們經過本次閱讀都能有所收穫。
  2. 重點:學 Android 有一段時間了,我打算好好的梳理一下所學知識,到如今爲止,我才總結完 ActivityServiceBroadcastRecevier 等,有關 事件分發、滑動衝突、新能優化等重要模塊,我後面也將詳盡的總結,歡迎你們關注 _yuanhao 的 博客園 ,方便及時接收更新
  3. 若是有能夠補充的知識點,歡迎你們在評論區指出。

碼字不易,你的點贊是我總結的最大動力!


Android

相關文章
相關標籤/搜索