27 個問題突破所有重難點,BroadcastReceiver 、ContentProvider 知多少?「建議收藏」

  • 2019 年 11 月 8 日
  • 筆記

Android

前言


  • 距離上次更新過去一周多了,打破了之前兩到三天一更的慣例,主要還是這部分內容太多了。
  • 原先想把 BroadcastReceiverContentProvider 分兩篇來總結,但的確,這兩大組件的使用不像 ActivityService 那麼頻繁,所以還是決定一次性搞定。
  • 於是這篇近 1.5 W 字的文章就誕生了。可以說這周幾乎所有時間都花在這上面,自己看了幾遍感覺已經極為全面了。
  • 祝大家閱讀愉快。

最後,希望大家都能有所收穫,歡迎食用!

文章目錄


文章目錄

方便大家學習,我在 GitHub 上建立個 倉庫


一、BroadcastReceiver


  • BroadcastReceiver,顧名思義就是「廣播接收者」的意思,它是Android四大基本組件之一。
  • 這種組件本質上是一種全局的監聽器,用於監聽系統全局的廣播消息。
  • 它可以接收來自系統和應用的的廣播。

BroadcastReceiver

1.1 什麼是 BroadcastReceiver

什麼是 BroadcastReceiver

  • 是四大組件之一, 主要用於接收 app 發送的廣播
  • 內部通訊實現機制:通過 android 系統的 Binder 機制.

1.2 廣播分為兩種

廣播分為兩種

1.2.1 無序廣播

無序廣播

  • 也叫標準廣播,是一種完全非同步執行的廣播。
  • 在廣播發出之後,所有廣播接收器幾乎都會在同一時刻接收到這條廣播消息,它們之間沒有任何先後順序,廣播的效率較高。
  • 優點: 完全非同步, 邏輯上可被任何接受者收到廣播,效率高
  • 缺點: 接受者不能將處理結果交給下一個接受者, 且無法終止廣播.

1.2.2 有序廣播

有序廣播

  • 是一種同步執行的廣播。
  • 在廣播發出之後,同一時刻只有一個廣播接收器能夠收到這條廣播消息,當其邏輯執行完後該廣播接收器才會繼續傳遞。
  • 調用 SendOrderedBroadcast() 方法來發送廣播,同時也可調用 abortBroadcast() 方法攔截該廣播。可通過 <intent-filter> 標籤中設置 android:property 屬性來設置優先順序,未設置時按照註冊的順序接收廣播。
  • 有序廣播接受器間可以互傳數據。
  • 當廣播接收器收到廣播後,當前廣播也可以使用 setResultData 方法將數據傳給下一個接收器。
  • 使用 getStringExtra 函數獲取廣播的原始數據,通過 getResultData 方法取得上個廣播接收器自己添加的數據,並可用 abortBroadcast 方法丟棄該廣播,使該廣播不再被別的接收器接收到。

總結

  • 總結
  1. 按被接收者的優先順序循序傳播 A > B > C ,
  2. 每個都有權終止廣播, 下一個就得不到
  3. 每一個都可進行修改操作, 下一個就得到上一個修改後的結果.

1.2.3 最終廣播者

最終廣播者

  • Context.sendOrderedBroadcast ( intent , receiverPermission , resultReceiver , scheduler , initialCode , initialData , initialExtras ) 時我們可以指定 resultReceiver 為最終廣播接收者.
  • 如果比他優先順序高的接受者不終止廣播, 那麼他的 onReceive 會執行兩次
  • 第一次是正常的接收
  • 第二次是最終的接收
  • 如果優先順序高的那個終止廣播, 那麼他還是會收到一次最終的廣播

1.2.4 常見的廣播接收者運用場景

廣播接收者運用場景

  • 開機啟動, sd 卡掛載, 低電量, 外撥電話, 鎖屏等
  • 比如根據產品經理要求, 設計播放音樂時, 鎖屏是否決定暫停音樂.

1.3 BroadcastReceiver 的種類

1.3.1 廣播作為 Android 組件間的通訊方式,如下使用場景:

對前一部分 「 請描述一下 BroadcastReceiver 」 進行展開補充

BroadcastReceiver 使用場景

  • APP 內部的消息通訊。
  • 不同 APP 之間的消息通訊。

  • Android 系統在特定情況下與 APP 之間的消息通訊。

  • 廣播使用了觀察者模式,基於消息的發布 / 訂閱事件模型。廣播將廣播的發送者和接受者極大程度上解耦,使得系統能夠方便集成,更易擴展。

  • BroadcastReceiver 本質是一個全局監聽器,用於監聽系統全局的廣播消息,方便實現系統中不同組件間的通訊。

  • 自定義廣播接收器需要繼承基類 BroadcastReceiver ,並實現抽象方法 onReceive ( context, intent ) 。默認情況下,廣播接收器也是運行在主執行緒,因此 onReceiver() 中不能執行太耗時的操作( 不超過 10s ),否則將會產生 ANR 問題。onReceiver() 方法中涉及與其他組件之間的交互時,可以使用發送 Notification 、啟動 Service 等方式,最好不要啟動 Activity

1.3.2 系統廣播

系統廣播

  • Android 系統內置了多個系統廣播,只要涉及手機的基本操作,基本上都會發出相應的系統廣播,如開機啟動、網路狀態改變、拍照、螢幕關閉與開啟、電量不足等。在系統內部當特定時間發生時,系統廣播由系統自動發出。

  • 常見系統廣播 Intent 中的 Action 為如下值:

常見系統廣播 Intent 中的 Action

  1. 簡訊提醒:android.provider.Telephony.SMS_RECEIVED
  2. 電量過低:ACTION_BATIERY_LOW
  3. 電量發生改變:ACTION_BATTERY_CHANGED
  4. 連接電源:ACTION_POWER_CO             
  • Android 7.0 開始,系統不會再發送廣播 ACTION_NEW_PICTUREACTION_NEW_VIDEO ,對於廣播 CONNECTIVITY_ACTION 必須在程式碼中使用 registerReceiver 方法註冊接收器,在 AndroidManifest 文件中聲明接收器不起作用。
  • Android 8.0 開始,對於大多數隱式廣播,不能在 AndroidManifest 文件中聲明接收器。

1.3.3 局部廣播

局部廣播

  • 局部廣播的發送者和接受者都同屬於一個 APP
  • 相比於全局廣播具有以下優點:
  1. 其他的 APP 不會受到局部廣播,不用擔心數據泄露的問題。
  2. 其他 APP 不可能向當前的 APP 發送局部廣播,不用擔心有安全漏洞被其他 APP 利用。
  3. 局部廣播比通過系統傳遞的全局廣播的傳遞效率更高。
  • Android v4 包中提供了 LocalBroadcastManager 類,用於統一處理 APP 局部廣播,使用方式與全局廣播幾乎相同,只是調用註冊 / 取消註冊廣播接收器和發送廣播偶讀方法時,需要通過 LocalBroadcastManager 類的 getInstance() 方法獲取的實例調用。

1.4 BroadcastReceiver 註冊方式

BroadcastReceiver 註冊方式

1.4.1 靜態註冊

AndroidManifest.xml 文件中配置。

<receiver android:name=".MyReceiver" android:exported="true">      <intent-filter>          <!-- 指定該 BroadcastReceiver 所響應的 Intent 的 Action -->          <action android:name="android.intent.action.INPUT_METHOD_CHANGED"          <action android:name="android.intent.action.BOOT_COMPLETED" />      </intent-filter>  </receiver>
  • 兩個重要屬性需要關註:

兩個重要屬性需要關注

  1. android: exported
    其作用是設置此 BroadcastReceiver 能否接受其他 APP 發出的廣播 ,當設為 false 時,只能接受同一應用的的組件或具有相同 user ID 的應用發送的消息。這個屬性的默認值是由 BroadcastReceiver 中有無 Intent-filter 決定的,如果有 Intent-filter ,默認值為 true ,否則為 false
  2. android: permission
    如果設置此屬性,具有相應許可權的廣播發送方發送的廣播才能被此 BroadcastReceiver 所接受;如果沒有設置,這個值賦予整個應用所申請的許可權。

1.4.2 動態註冊

  • 調用 ContextregisterReceiver ( BroadcastReceiver receiver , IntentFilter filter ) 方法指定。

1.5 在 Mainfest 和程式碼如何註冊和使用 BroadcastReceiver ? ( 一個 action 是重點 )

Mainfest 和程式碼如何註冊和使用 BroadcastReceiver

1.5.1 使用文件註冊 ( 靜態廣播 )

  • 只要 app 還在運行,那麼會一直收到廣播消息

使用文件註冊 ( 靜態廣播 )

  • 演示:
  1. 一個 app 里: 自定義一個類繼承 BroadcastReceiver 然後要求重寫 onReveiver 方法
public class MyBroadCastReceiver extends BroadcastReceiver {      @Override      public void onReceive(Context context, Intent intent) {          Log.d("MyBroadCastReceiver", "收到資訊,內容是 : " + intent.getStringExtra("info") + "");      }  }
  1. 清單文件註冊,並設置 Action , 就那麼簡單完成接收準備工作
<receiver android:name=".MyBroadCastReceiver">      <intent-filter>          <action android:name="myBroadcast.action.call"/>      </intent-filter>  </receiver>

1.5.2 程式碼註冊 ( 動態廣播 )

  • 當註冊的 Activity 或者 Service 銷毀了那麼就會接收不到廣播.

程式碼註冊 ( 動態廣播 )

  • 演示:
  1. 在和廣播接受者相同的 app 里的 MainActivity 添加一個註冊按鈕 , 用來註冊廣播接收者
  2. 設置意圖過濾,添加 Action
//onCreate創建廣播接收者對象  mReceiver = new MyBroadCastReceiver();    //註冊按鈕  public void click(View view) {      IntentFilter intentFilter = new IntentFilter();      intentFilter.addAction("myBroadcast.action.call");      registerReceiver(mReceiver, intentFilter);  }
  1. 銷毀的時候取消註冊
@Override  protected void onDestroy() {      unregisterReceiver(mReceiver);      super.onDestroy();  }

1.5.3 在另一個 app , 定義一個按鈕, 設置意圖, 意圖添加消息內容, 意圖設置 action( … ) 要匹配 , 然後發送廣播即可.

程式碼註冊 ( 動態廣播 )

public void click(View view) {      Intent intent = new Intent();      intent.putExtra("info", "消息內容");      intent.setAction("myBroadcast.action.call");      sendBroadcast(intent);  }
  • 運行兩個 app 之後:
  1. 靜態註冊的方法: 另一 app 直接發廣播就收到了
  2. 動態註冊的方法: 自己的 app 先程式碼註冊,然後另一個 app 直接發廣播即可.-

1.6 BroadcastReceiver 的實現原理是什麼?

  • Android 中的廣播使用了設計模式中的觀察者模式:基於消息的發布 / 訂閱事件模型。

BroadcastReceiver 的實現原理

  • 模型中主要有 3 個角色:
  1. 消息訂閱者( 廣播接收者 )
  2. 消息發布者( 廣播發布者 )
  3. 消息中心( AMS,即 Activity Manager Service

1.6.1 原理:

原理

  • 廣播接收者通過 Binder 機制在 AMSActivity Manager Service ) 註冊;

  • 廣播發送者通過 Binder 機制向 AMS 發送廣播;

  • AMS 根據廣播發送者要求,在已註冊列表中,尋找合適的 BroadcastReceiver ( 尋找依據:IntentFilter / Permission );

  • AMS 將廣播發送到 BroadcastReceiver 相應的消息循環隊列中;

  • 廣播接收者通過消息循環拿到此廣播,並回調 onReceive() 方法。

  • 需要注意的是:廣播的發送和接受是非同步的,發送者不會關心有無接收者或者何時收到。

  

1.7 本地廣播

本地廣播

  • 本地廣播機制使得發出的廣播只能夠在應用程式的內部進行傳遞,並且廣播接收器也只能接受來自本應用程式發出的廣播,則安全性得到了提高。
  • 本地廣播主要是使用了一個 LocalBroadcastManager 來對廣播進行管理,並提供了發送廣播和註冊廣播接收器的方法。
  • 開發者只要實現自己的 BroadcastReceiver 子類,並重寫 onReceive ( Context context, Intetn intent ) 方法即可。
  • 當其他組件通過 sendBroadcast()sendStickyBroadcast()sendOrderBroadcast() 方法發送廣播消息時,如果該 BroadcastReceiver 也對該消息「感興趣」,BroadcastReceiveronReceive ( Context context, Intetn intent ) 方法將會被觸發。

  • 使用步驟:

  1. 調用 LocalBroadcastManager.getInstance() 獲得實例
  2. 調用 registerReceiver() 方法註冊廣播
  3. 調用 sendBroadcast() 方法發送廣播
  4. 調用 unregisterReceiver() 方法取消註冊

1.7.1 注意事項:

注意事項

  1. 本地廣播無法通過靜態註冊方式來接受,相比起系統全局廣播更加高效。
  2. 在廣播中啟動 Activity 時,需要為 Intent 加入 FLAG_ACTIVITY_NEW_TASK 標記,否則會報錯,因為需要一個棧來存放新打開的 Activity
  3. 廣播中彈出 Alertdialog 時,需要設置對話框的類型為 TYPE_SYSTEM_ALERT ,否則無法彈出。
  4. 不要在 onReceiver() 方法中添加過多的邏輯或者進行任何的耗時操作,因為在廣播接收器中是不允許開啟執行緒的,當 onReceiver() 方法運行了較長時間而沒有結束時,程式就會報錯。

1.8 Sticky Broadcast 粘性廣播

Sticky Broadcast 粘性廣播

  • 如果發送者發送了某個廣播,而接收者在這個廣播發送後才註冊自己的 Receiver ,這時接收者便無法接收到剛才的廣播
  • 為此 Android 引入了 StickyBroadcast ,在廣播發送結束後會保存剛剛發送的廣播( Intent ),這樣當接收者註冊完 Receiver 後就可以繼續使用剛才的廣播。
  • 如果在接收者註冊完成前發送了多條相同 Action 的粘性廣播,註冊完成後只會收到一條該 Action 的廣播,並且消息內容是最後一次廣播內容。

  • 系統網路狀態的改變發送的廣播就是粘性廣播。
  1. 粘性廣播通過 ContextsendStickyBroadcast ( Intent ) 介面發送,需要添加許可權
  2. uses-permission android:name=」android.permission.BROADCAST_STICKY」
  3. 也可以通過 ContextremoveStickyBroadcast ( Intent intent ) 介面移除快取的粘性廣播

1.9 LocalBroadcastManager 詳解

1.9.1 特點:

LocalBroadcastManager 特點

  1. 使用它發送的廣播將只在自身APP內傳播,因此你不必擔心泄漏隱私數據;

  2. 其他 APP 無法對你的 APP 發送該廣播,因為你的APP根本就不可能接收到非自身應用發送的該廣播,因此你不必擔心有安全漏洞可以利用;

  3. 比系統的全局廣播更加高效。

1.9.2 源碼分析 :

LocalBroadcastManager 源碼分析

  1. LocalBroadcastManager 內部協作主要是靠這兩個 Map 集合:MReceiversMActions ,當然還有一個 List 集合 MPendingBroadcasts ,這個主要就是存儲待接收的廣播對象。

  2. LocalBroadcastManager 高效的原因主要是因為它內部是通過 Handler 實現的,它的 sendBroadcast() 方法含義並非和我們平時所用的一樣,它的 sendBroadcast() 方法其實是通過 handler 發送一個 Message 實現的;

  3. 既然它內部是通過 Handler 來實現廣播的發送的,那麼相比於系統廣播通過 Binder 實現那肯定是更高效了,同時使用 Handler 來實現,別的應用無法向我們的應用發送該廣播,而我們應用內發送的廣播也不會離開我們的應用;

1.9.3 BroadcastReceiver 安全問題

BroadcastReceiver 安全問題

  • BroadcastReceiver 設計的初衷是從全局考慮可以方便應用程式和系統、應用程式之間、應用程式內的通訊,所以對單個應用程式而言BroadcastReceiver 是存在安全性問題的 ( 惡意程式腳本不斷的去發送你所接收的廣播 ) 。為了解決這個問題 LocalBroadcastManager 就應運而生了。

  • LocalBroadcastManagerAndroid Support 包提供了一個工具,用於在同一個應用內的不同組件間發送 BroadcastLocalBroadcastManager 也稱為局部通知管理器,這種通知的好處是安全性高,效率也高,適合局部通訊,可以用來代替 Handler 更新 UI

1.9.4 廣播的安全性

  • Android 系統中的廣播可以跨進程直接通訊,會產生以下兩個問題:
  1. 其他 APP 可以接收到當前 APP 發送的廣播,導致數據外泄。
  2. 其他 APP 可以向當前 APP 放廣播消息,導致 APP 被非法控制。

廣播的安全性

  • 發送廣播
  1. 發送廣播時,增加相應的 permission ,用於許可權驗證。
  2. Android 4.0 及以上系統中發送廣播時,可以使用 setPackage() 方法設置接受廣播的包名。
  3. 使用局部廣播。
  • 接受廣播
  1. 註冊廣播接收器時,增加相應的 permission ,用於許可權驗證。
  2. 註冊廣播接收器時,設置 android:exported 的值為false。
  • 使用局部廣播。
  1. 發送廣播時,如果增加了 permission
  2. 那接受廣播的 APP 必須申請相應許可權,這樣才能收到對應的廣播,反之亦然。

1.9.5 使用 BroadcastReceiver 的好處

BroadcastReceiver 的好處

  1. 因廣播數據在本應用範圍內傳播,你不用擔心隱私數據泄露的問題。

  2. 不用擔心別的應用偽造廣播,造成安全隱患。

  3. 相比在系統內發送全局廣播,它更高效。

1.10 如何讓自己的廣播只讓指定的 app 接收?

讓自己的廣播只讓指定的 app 接收

  • 在發送廣播的 app 端,自定義定義許可權, 那麼想要接收的另外 app 端必須聲明許可權才能收到.
  1. 許可權, 保護層級是普通正常.
  2. 用戶許可權
<permission android:name="broad.ok.receiver" android:protectionLevel="normal"/>  <uses-permission android:name="broad.ok.receiver" />
  1. 發送廣播的時候加上許可權字元串
public void click(View view) {      Intent intent = new Intent();      intent.putExtra("info", "消息內容");      intent.setAction("myBroadcast.action.call");      sendBroadcast(intent, "broad.ok.receiver");      //sendOrderedBroadcast(intent,"broad.ok.receiver");  }
  1. 其他app接收者想好獲取廣播,必須聲明在清單文件許可權
<uses-permission android:name="broad.ok.receiver"/>

1.11 廣播的優先順序對無序廣播生效嗎?

廣播的優先順序對無序廣播生效

  • 優先順序對無序也生效.

1.12 動態註冊的廣播優先順序誰高?

動態註冊的廣播優先順序誰高

  • 誰先註冊,誰就高

1.13 如何判斷當前的 BrodcastReceiver 接收到的是有序還是無序的廣播?

判斷當前的 BrodcastReceiver 接收到的是有序還是無序

  • onReceiver 方法里,直接調用判斷方法得返回值
public void onReceive(Context context, Intent intent) {      Log.d("MyBroadCastReceiver", "收到資訊,內容是 : " + intent.getStringExtra("info") + "");      boolean isOrderBroadcast = isOrderedBroadcast();  }

1.14 BroadcastReceiver 不能執行耗時操作

BroadcastReceiver 不能執行耗時操作

  • 一方面
  1. BroadcastReceiver 一般處於主執行緒。
  2. 耗時操作會導致 ANR
  • 另一方面
  1. BroadcastReceiver 啟動時間較短。
  2. 如果一個進程裡面只存在一個 BroadcastReceiver 組件。並且在其中開啟子執行緒執行耗時任務。
  3. 系統會認為該進程是優先順序最低的空進程。很容易將其殺死。

二、ContentProvider


  • ContentProvider 應用程式間非常通用的共享數據的一種方式,也是 Android 官方推薦的方式。
  • Android 中許多系統應用都使用該方式實現數據共享,比如通訊錄、簡訊等。

ContentProvider

2.1 Android 為什麼要設計 ContentProvider 這個組件?

為什麼要設計 ContentProvider

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

2.2 如何訪問自定義 ContentProvider

如何訪問自定義 ContentProvider

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

2.3 通過 ContentResolver 獲取 ContentProvider 內容的基本步驟

ContentResolver 獲取 ContentProvider 內容的基本步驟

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

2.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[]);    } 

2.5 為什麼要用 ContentProvider ?它和 sql 的實現上有什麼差別?

為什麼要用 ContentProvider ?它和 sql 的實現上有什麼差別

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

2.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

2.7 如何訪問 asserts 資源目錄下的資料庫?

訪問 asserts 資源目錄下的資料庫

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

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

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

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

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

設計 ContentProvider 的目的

  • 隱藏數據的實現方式,對外提供統一的數據訪問介面;
  • 更好的數據訪問許可權管理。ContentProvider 可以對開發的數據進行許可權設置,不同的 URI 可以對應不同的許可權,只有符合許可權要求的組件才能訪問到 ContentProvider 的具體操作。
  • ContentProvider 封裝了跨進程共享的邏輯,我們只需要 Uri 即可訪問數據。由系統來管理 ContentProvider 的創建、生命周期及訪問的執行緒分配,簡化我們在應用間共享數據( 進程間通訊 )的方式。我們只管通過 ContentResolver 訪問 ContentProvider 所提示的數據介面,而不需要擔心它所在進程是啟動還是未啟動。

2.10 運行在主執行緒的 ContentProvider 為什麼不會影響主執行緒的UI操作?

ContentProvider 為什麼不會影響主執行緒的UI操作

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

2.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" />

2.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>

2.12 ContentProvider 介面方法運行在哪個執行緒中呢?

ContentProvider 介面方法運行在哪個執行緒中

  • ContentProvider 可以在 AndroidManifest.xml 中配置一個叫做 android:multiprocess 的屬性,默認值是 false ,表示 ContentProvider 是單例的
  • 無論哪個客戶端應用的訪問都將是同一個 ContentProvider 對象,如果設為 true ,系統會為每一個訪問該 ContentProvider 的進程創建一個實例。

2.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 的。

2.13 ContentProvider 是如何在不同應用程式之間傳輸數據的?

ContentProvider 是如何在不同應用程式之間傳輸數據

  • 一個應用進程有 16Binder 執行緒去和遠程服務進行交互,而每個執行緒可佔用的快取空間是 128KB 這樣,超過會報異常。
  • ContentResolver 雖然是通過 Binder 進程間通訊機制打通了應用程式之間共享數據的通道,但 ContentProvider 組件在不同應用程式之間傳輸數據是基於匿名共享記憶體機制來實現的。

圖解 ContentProvider

總結


  1. 本文應該是全網最全面的 BroadcastReceiverContentProvider 知識總結了,前前後後投入了大量時間來完成。希望大家通過本次閱讀都能有所收穫。
  2. 重點:關於 Android 的四大組件,到現在為止我才總結完 ActivityServiceBroadcastRecevierContentProvider等,以及事件分發、滑動衝突、新能優化等重要模組,進行全面總結,歡迎大家關注 _yuanhao 的 部落格園 ,方便及時接收更新

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


Android