27 個問題突破所有重難點,BroadcastReceiver 、ContentProvider 知多少?「建議收藏」
- 2019 年 11 月 8 日
- 筆記
前言
- 距離上次更新過去一周多了,打破了之前兩到三天一更的慣例,主要還是這部分內容太多了。
- 原先想把
BroadcastReceiver
、ContentProvider
分兩篇來總結,但的確,這兩大組件的使用不像Activity
、Service
那麼頻繁,所以還是決定一次性搞定。 - 於是這篇近
1.5 W
字的文章就誕生了。可以說這周幾乎所有時間都花在這上面,自己看了幾遍感覺已經極為全面了。 - 祝大家閱讀愉快。
最後,希望大家都能有所收穫,歡迎食用!
文章目錄
方便大家學習,我在 GitHub 上建立個 倉庫
-
倉庫內容與部落格同步更新。由於我在
稀土掘金
簡書
CSDN
部落格園
等站點,都有新內容發布。所以大家可以直接關注該倉庫,以免錯過精彩內容!
一、BroadcastReceiver
BroadcastReceiver
,顧名思義就是「廣播接收者」的意思,它是Android四大基本組件之一。- 這種組件本質上是一種全局的監聽器,用於監聽系統全局的廣播消息。
- 它可以接收來自系統和應用的的廣播。
1.1 什麼是 BroadcastReceiver
- 是四大組件之一, 主要用於接收
app
發送的廣播 - 內部通訊實現機制:通過
android
系統的Binder
機制.
1.2 廣播分為兩種
1.2.1 無序廣播
- 也叫標準廣播,是一種完全非同步執行的廣播。
- 在廣播發出之後,所有廣播接收器幾乎都會在同一時刻接收到這條廣播消息,它們之間沒有任何先後順序,廣播的效率較高。
- 優點: 完全非同步, 邏輯上可被任何接受者收到廣播,效率高
- 缺點: 接受者不能將處理結果交給下一個接受者, 且無法終止廣播.
1.2.2 有序廣播
- 是一種同步執行的廣播。
- 在廣播發出之後,同一時刻只有一個廣播接收器能夠收到這條廣播消息,當其邏輯執行完後該廣播接收器才會繼續傳遞。
- 調用
SendOrderedBroadcast()
方法來發送廣播,同時也可調用abortBroadcast()
方法攔截該廣播。可通過<intent-filter>
標籤中設置android:property
屬性來設置優先順序,未設置時按照註冊的順序接收廣播。 - 有序廣播接受器間可以互傳數據。
- 當廣播接收器收到廣播後,當前廣播也可以使用
setResultData
方法將數據傳給下一個接收器。 - 使用
getStringExtra
函數獲取廣播的原始數據,通過getResultData
方法取得上個廣播接收器自己添加的數據,並可用abortBroadcast
方法丟棄該廣播,使該廣播不再被別的接收器接收到。
- 總結
- 按被接收者的優先順序循序傳播
A > B > C
, - 每個都有權終止廣播, 下一個就得不到
- 每一個都可進行修改操作, 下一個就得到上一個修改後的結果.
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
」 進行展開補充
APP
內部的消息通訊。-
不同
APP
之間的消息通訊。 -
Android
系統在特定情況下與 APP 之間的消息通訊。 -
廣播使用了觀察者模式,基於消息的發布 / 訂閱事件模型。廣播將廣播的發送者和接受者極大程度上解耦,使得系統能夠方便集成,更易擴展。
-
BroadcastReceiver 本質是一個全局監聽器,用於監聽系統全局的廣播消息,方便實現系統中不同組件間的通訊。
-
自定義廣播接收器需要繼承基類
BroadcastReceiver
,並實現抽象方法onReceive ( context, intent )
。默認情況下,廣播接收器也是運行在主執行緒,因此onReceiver()
中不能執行太耗時的操作( 不超過10s
),否則將會產生ANR
問題。onReceiver()
方法中涉及與其他組件之間的交互時,可以使用發送Notification
、啟動Service
等方式,最好不要啟動Activity
。
1.3.2 系統廣播
-
Android
系統內置了多個系統廣播,只要涉及手機的基本操作,基本上都會發出相應的系統廣播,如開機啟動、網路狀態改變、拍照、螢幕關閉與開啟、電量不足等。在系統內部當特定時間發生時,系統廣播由系統自動發出。 -
常見系統廣播
Intent
中的Action
為如下值:
- 簡訊提醒:
android.provider.Telephony.SMS_RECEIVED
- 電量過低:
ACTION_BATIERY_LOW
- 電量發生改變:
ACTION_BATTERY_CHANGED
- 連接電源:
ACTION_POWER_CO
- 從
Android 7.0
開始,系統不會再發送廣播ACTION_NEW_PICTURE
和ACTION_NEW_VIDEO
,對於廣播CONNECTIVITY_ACTION
必須在程式碼中使用registerReceiver
方法註冊接收器,在AndroidManifest
文件中聲明接收器不起作用。 - 從
Android 8.0
開始,對於大多數隱式廣播,不能在AndroidManifest
文件中聲明接收器。
1.3.3 局部廣播
- 局部廣播的發送者和接受者都同屬於一個
APP
- 相比於全局廣播具有以下優點:
- 其他的
APP
不會受到局部廣播,不用擔心數據泄露的問題。 - 其他
APP
不可能向當前的APP
發送局部廣播,不用擔心有安全漏洞被其他APP
利用。 - 局部廣播比通過系統傳遞的全局廣播的傳遞效率更高。
Android v4
包中提供了LocalBroadcastManager
類,用於統一處理 APP 局部廣播,使用方式與全局廣播幾乎相同,只是調用註冊 / 取消註冊廣播接收器和發送廣播偶讀方法時,需要通過LocalBroadcastManager
類的getInstance()
方法獲取的實例調用。
1.4 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>
- 兩個重要屬性需要關註:
android: exported
其作用是設置此BroadcastReceiver
能否接受其他APP
發出的廣播 ,當設為false
時,只能接受同一應用的的組件或具有相同user ID
的應用發送的消息。這個屬性的默認值是由BroadcastReceiver
中有無Intent-filter
決定的,如果有Intent-filter
,默認值為true
,否則為false
。android: permission
如果設置此屬性,具有相應許可權的廣播發送方發送的廣播才能被此BroadcastReceiver
所接受;如果沒有設置,這個值賦予整個應用所申請的許可權。
1.4.2 動態註冊
- 調用
Context
的registerReceiver ( BroadcastReceiver receiver , IntentFilter filter )
方法指定。
1.5 在 Mainfest 和程式碼如何註冊和使用 BroadcastReceiver ? ( 一個 action 是重點 )
1.5.1 使用文件註冊 ( 靜態廣播 )
- 只要
app
還在運行,那麼會一直收到廣播消息
- 演示:
- 一個
app
里: 自定義一個類繼承BroadcastReceiver
然後要求重寫onReveiver
方法
public class MyBroadCastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d("MyBroadCastReceiver", "收到資訊,內容是 : " + intent.getStringExtra("info") + ""); } }
- 清單文件註冊,並設置
Action
, 就那麼簡單完成接收準備工作
<receiver android:name=".MyBroadCastReceiver"> <intent-filter> <action android:name="myBroadcast.action.call"/> </intent-filter> </receiver>
1.5.2 程式碼註冊 ( 動態廣播 )
- 當註冊的
Activity
或者Service
銷毀了那麼就會接收不到廣播.
- 演示:
- 在和廣播接受者相同的
app
里的MainActivity
添加一個註冊按鈕 , 用來註冊廣播接收者 - 設置意圖過濾,添加
Action
//onCreate創建廣播接收者對象 mReceiver = new MyBroadCastReceiver(); //註冊按鈕 public void click(View view) { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("myBroadcast.action.call"); registerReceiver(mReceiver, intentFilter); }
- 銷毀的時候取消註冊
@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
之後:
- 靜態註冊的方法: 另一
app
直接發廣播就收到了 - 動態註冊的方法: 自己的
app
先程式碼註冊,然後另一個app
直接發廣播即可.-
1.6 BroadcastReceiver 的實現原理是什麼?
Android
中的廣播使用了設計模式中的觀察者模式:基於消息的發布 / 訂閱事件模型。
- 模型中主要有
3
個角色:
- 消息訂閱者( 廣播接收者 )
- 消息發布者( 廣播發布者 )
- 消息中心(
AMS
,即Activity Manager Service
)
1.6.1 原理:
-
廣播接收者通過
Binder
機制在AMS
(Activity Manager Service
) 註冊; -
廣播發送者通過
Binder
機制向AMS
發送廣播; -
AMS
根據廣播發送者要求,在已註冊列表中,尋找合適的BroadcastReceiver
( 尋找依據:IntentFilter / Permission
); -
AMS
將廣播發送到BroadcastReceiver
相應的消息循環隊列中; -
廣播接收者通過消息循環拿到此廣播,並回調
onReceive()
方法。 -
需要注意的是:廣播的發送和接受是非同步的,發送者不會關心有無接收者或者何時收到。
1.7 本地廣播
- 本地廣播機制使得發出的廣播只能夠在應用程式的內部進行傳遞,並且廣播接收器也只能接受來自本應用程式發出的廣播,則安全性得到了提高。
- 本地廣播主要是使用了一個
LocalBroadcastManager
來對廣播進行管理,並提供了發送廣播和註冊廣播接收器的方法。 - 開發者只要實現自己的
BroadcastReceiver
子類,並重寫onReceive ( Context context, Intetn intent )
方法即可。 -
當其他組件通過
sendBroadcast()
、sendStickyBroadcast()
、sendOrderBroadcast()
方法發送廣播消息時,如果該BroadcastReceiver
也對該消息「感興趣」,BroadcastReceiver
的onReceive ( Context context, Intetn intent )
方法將會被觸發。 -
使用步驟:
- 調用 LocalBroadcastManager.getInstance() 獲得實例
- 調用 registerReceiver() 方法註冊廣播
- 調用 sendBroadcast() 方法發送廣播
- 調用 unregisterReceiver() 方法取消註冊
1.7.1 注意事項:
- 本地廣播無法通過靜態註冊方式來接受,相比起系統全局廣播更加高效。
- 在廣播中啟動
Activity
時,需要為Intent
加入FLAG_ACTIVITY_NEW_TASK
標記,否則會報錯,因為需要一個棧來存放新打開的Activity
。 - 廣播中彈出
Alertdialog
時,需要設置對話框的類型為TYPE_SYSTEM_ALERT
,否則無法彈出。 - 不要在
onReceiver()
方法中添加過多的邏輯或者進行任何的耗時操作,因為在廣播接收器中是不允許開啟執行緒的,當onReceiver()
方法運行了較長時間而沒有結束時,程式就會報錯。
1.8 Sticky Broadcast 粘性廣播
- 如果發送者發送了某個廣播,而接收者在這個廣播發送後才註冊自己的 Receiver ,這時接收者便無法接收到剛才的廣播
- 為此
Android
引入了StickyBroadcast
,在廣播發送結束後會保存剛剛發送的廣播(Intent
),這樣當接收者註冊完Receiver
後就可以繼續使用剛才的廣播。 -
如果在接收者註冊完成前發送了多條相同
Action
的粘性廣播,註冊完成後只會收到一條該Action
的廣播,並且消息內容是最後一次廣播內容。 - 系統網路狀態的改變發送的廣播就是粘性廣播。
- 粘性廣播通過
Context
的sendStickyBroadcast ( Intent )
介面發送,需要添加許可權 uses-permission android:name=」android.permission.BROADCAST_STICKY」
- 也可以通過
Context
的removeStickyBroadcast ( Intent intent )
介面移除快取的粘性廣播
1.9 LocalBroadcastManager 詳解
1.9.1 特點:
-
使用它發送的廣播將只在自身APP內傳播,因此你不必擔心泄漏隱私數據;
-
其他
APP
無法對你的APP
發送該廣播,因為你的APP根本就不可能接收到非自身應用發送的該廣播,因此你不必擔心有安全漏洞可以利用; -
比系統的全局廣播更加高效。
1.9.2 源碼分析 :
-
LocalBroadcastManager
內部協作主要是靠這兩個Map
集合:MReceivers
和MActions
,當然還有一個 List 集合MPendingBroadcasts
,這個主要就是存儲待接收的廣播對象。 -
LocalBroadcastManager
高效的原因主要是因為它內部是通過Handler
實現的,它的sendBroadcast()
方法含義並非和我們平時所用的一樣,它的sendBroadcast()
方法其實是通過handler
發送一個Message
實現的; -
既然它內部是通過
Handler
來實現廣播的發送的,那麼相比於系統廣播通過Binder
實現那肯定是更高效了,同時使用Handler
來實現,別的應用無法向我們的應用發送該廣播,而我們應用內發送的廣播也不會離開我們的應用;
1.9.3 BroadcastReceiver 安全問題
-
BroadcastReceiver
設計的初衷是從全局考慮可以方便應用程式和系統、應用程式之間、應用程式內的通訊,所以對單個應用程式而言BroadcastReceiver
是存在安全性問題的 ( 惡意程式腳本不斷的去發送你所接收的廣播 ) 。為了解決這個問題LocalBroadcastManager
就應運而生了。 -
LocalBroadcastManager
是Android Support
包提供了一個工具,用於在同一個應用內的不同組件間發送Broadcast
。LocalBroadcastManager
也稱為局部通知管理器,這種通知的好處是安全性高,效率也高,適合局部通訊,可以用來代替Handler
更新UI
1.9.4 廣播的安全性
Android
系統中的廣播可以跨進程直接通訊,會產生以下兩個問題:
- 其他
APP
可以接收到當前APP
發送的廣播,導致數據外泄。 - 其他
APP
可以向當前APP
放廣播消息,導致APP
被非法控制。
- 發送廣播
- 發送廣播時,增加相應的
permission
,用於許可權驗證。 - 在
Android 4.0
及以上系統中發送廣播時,可以使用setPackage()
方法設置接受廣播的包名。 - 使用局部廣播。
- 接受廣播
- 註冊廣播接收器時,增加相應的
permission
,用於許可權驗證。 - 註冊廣播接收器時,設置
android:exported
的值為false。
- 使用局部廣播。
- 發送廣播時,如果增加了
permission
- 那接受廣播的
APP
必須申請相應許可權,這樣才能收到對應的廣播,反之亦然。
1.9.5 使用 BroadcastReceiver 的好處
-
因廣播數據在本應用範圍內傳播,你不用擔心隱私數據泄露的問題。
-
不用擔心別的應用偽造廣播,造成安全隱患。
-
相比在系統內發送全局廣播,它更高效。
1.10 如何讓自己的廣播只讓指定的 app 接收?
- 在發送廣播的
app
端,自定義定義許可權, 那麼想要接收的另外app
端必須聲明許可權才能收到.
- 許可權, 保護層級是普通正常.
- 用戶許可權
<permission android:name="broad.ok.receiver" android:protectionLevel="normal"/> <uses-permission android:name="broad.ok.receiver" />
- 發送廣播的時候加上許可權字元串
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"); }
- 其他app接收者想好獲取廣播,必須聲明在清單文件許可權
<uses-permission android:name="broad.ok.receiver"/>
1.11 廣播的優先順序對無序廣播生效嗎?
- 優先順序對無序也生效.
1.12 動態註冊的廣播優先順序誰高?
- 誰先註冊,誰就高
1.13 如何判斷當前的 BrodcastReceiver 接收到的是有序還是無序的廣播?
- 在
onReceiver
方法里,直接調用判斷方法得返回值
public void onReceive(Context context, Intent intent) { Log.d("MyBroadCastReceiver", "收到資訊,內容是 : " + intent.getStringExtra("info") + ""); boolean isOrderBroadcast = isOrderedBroadcast(); }
1.14 BroadcastReceiver 不能執行耗時操作
- 一方面
BroadcastReceiver
一般處於主執行緒。- 耗時操作會導致
ANR
- 另一方面
BroadcastReceiver
啟動時間較短。- 如果一個進程裡面只存在一個
BroadcastReceiver
組件。並且在其中開啟子執行緒執行耗時任務。 - 系統會認為該進程是優先順序最低的空進程。很容易將其殺死。
二、ContentProvider
ContentProvider
應用程式間非常通用的共享數據的一種方式,也是Android
官方推薦的方式。Android
中許多系統應用都使用該方式實現數據共享,比如通訊錄、簡訊等。
2.1 Android 為什麼要設計 ContentProvider 這個組件?
- 很多做
Android
開發的人都不怎麼使用它,覺得直接讀取資料庫會更簡單方便。 - 那麼
Android
搞一個內容提供者在數據和應用之間,只是為了裝高大上,故弄玄虛?我認為其設計用意在於:
- 封裝。對數據進行封裝,提供統一的介面,使用者完全不必關心這些數據是在
DB
,XML
、Preferences
或者網路請求來的。當項目需求要改變數據來源時,使用我們的地方完全不需要修改。 - 提供一種跨進程數據共享的方式。
- 應用程式間的數據共享還有另外的一個重要話題,就是數據更新通知機制了。因為數據是在多個應用程式中共享的,當其中一個應用程式改變了這些共享數據的時候,它有責任通知其它應用程式,讓它們知道共享數據被修改了,這樣它們就可以作相應的處理。
2.2 如何訪問自定義 ContentProvider
ContentResolver
介面的notifyChange
函數來通知那些註冊了監控特定 URI的ContentObserver 對象,使得它們可以相應地執行一些處理。- ContentObserver 可以通過 registerContentObserver 進行註冊。
- 通過
ContentProvider
的Uri
訪問開放的數據。
ContenResolver
對象通過Context
提供的方法getContenResolver()
來獲得。ContenResolver
提供了以下方法來操作:insert
delete
update
query
這些方法分別會調用ContenProvider
中與之對應的方法並得到返回的結果。
2.3 通過 ContentResolver 獲取 ContentProvider 內容的基本步驟
- 得到
ContentResolver
類對象:ContentResolver cr = getContentResolver ( )
。 - 定義要查詢的欄位
String
數組。 - 使用
cr.query()
; 返回一個Cursor
對象。 - 使用
while
循環得到Cursor
裡面的內容。
2.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[]); }
2.5 為什麼要用 ContentProvider ?它和 sql 的實現上有什麼差別?
ContentProvider
屏蔽了數據存儲的細節 , 內部實現對用戶完全透明 , 用戶只需要關心操作數據的uri
就可以了,ContentProvider
可以實現不同app
之間 共享。Sql
也有增刪改查的方法, 但是sql
只能查詢本應用下的資料庫。- 而
ContentProvider
還可以去增刪改查本地文件.xml
文件的讀取等。
2.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
。
2.7 如何訪問 asserts 資源目錄下的資料庫?
- 把資料庫
db
複製到/data/data/packagename/databases/
目錄下, 然後直接就能訪問了。
2.8 多個進程同時調用一個 ContentProvider 的 query 獲取數據,ContentPrvoider 是如何反應的呢?
- 一個
ContentProvider
可以接受來自另外一個進程的數據請求。 - 儘管
ContentResolver
與ContentProvider
類隱藏了實現細節,但是ContentProvider
所提供的query()
,insert()
,delete()
,update()
都是在ContentProvider
進程的執行緒池中被調用執行的,而不是進程的主執行緒中。 - 這個執行緒池是有
Binder
創建和維護的,其實使用的就是每個應用進程中的Binder
執行緒池。
2.9 Android 設計 ContentProvider 的目的是什麼呢?
- 隱藏數據的實現方式,對外提供統一的數據訪問介面;
- 更好的數據訪問許可權管理。
ContentProvider
可以對開發的數據進行許可權設置,不同的URI
可以對應不同的許可權,只有符合許可權要求的組件才能訪問到ContentProvider
的具體操作。 ContentProvider
封裝了跨進程共享的邏輯,我們只需要Uri
即可訪問數據。由系統來管理ContentProvider
的創建、生命周期及訪問的執行緒分配,簡化我們在應用間共享數據( 進程間通訊 )的方式。我們只管通過ContentResolver
訪問ContentProvider
所提示的數據介面,而不需要擔心它所在進程是啟動還是未啟動。
2.10 運行在主執行緒的 ContentProvider 為什麼不會影響主執行緒的UI操作?
ContentProvider
的onCreate()
是運行在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
給其他的應用訪問呢?
- 可以參考
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>
2.12 ContentProvider 介面方法運行在哪個執行緒中呢?
ContentProvider
可以在AndroidManifest.xml
中配置一個叫做android:multiprocess
的屬性,默認值是 false ,表示 ContentProvider 是單例的- 無論哪個客戶端應用的訪問都將是同一個
ContentProvider
對象,如果設為true
,系統會為每一個訪問該ContentProvider
的進程創建一個實例。
2.12.1 這點還是比較好理解的,那如果我要問每個 ContentProvider 的操作是在哪個執行緒中運行的呢?( 其實我們關心的是 UI 執行緒和工作執行緒 )
-
比如我們在UI執行緒調用getContentResolver().query查詢數據,而當數據量很大時(或者需要進行較長時間的計算)會不會阻塞UI執行緒呢?
-
要分兩種情況回答這個問題:
ContentProvider
和調用者在同一個進程,ContentProvider
的方法(query/insert/update/delete
等 )和調用者在同一執行緒中;ContentProvider
和調用者在不同的進程,ContentProvider
的方法會運行在它自身所在進程的一個 Binder 執行緒中。
但是,注意這兩種方式在ContentProvider
的方法沒有執行完成前都會blocked
調用者。所以你應該知道這個上面這個問題的答案了吧。- 也可以看看
CursorLoader
這個類的源碼,看Google
自己是怎麼使用getContentResolver().query
的。
2.13 ContentProvider 是如何在不同應用程式之間傳輸數據的?
- 一個應用進程有
16
個Binder
執行緒去和遠程服務進行交互,而每個執行緒可佔用的快取空間是128KB
這樣,超過會報異常。 ContentResolver
雖然是通過Binder
進程間通訊機制打通了應用程式之間共享數據的通道,但ContentProvider
組件在不同應用程式之間傳輸數據是基於匿名共享記憶體機制來實現的。
總結
- 本文應該是全網最全面的
BroadcastReceiver
、ContentProvider
知識總結了,前前後後投入了大量時間來完成。希望大家通過本次閱讀都能有所收穫。 重點
:關於Android
的四大組件,到現在為止我才總結完Activity
、Service
、BroadcastRecevier
、ContentProvider
等,以及事件分發、滑動衝突、新能優化等重要模組,進行全面總結,歡迎大家關注 _yuanhao 的 部落格園 ,方便及時接收更新
碼字不易,你的點贊是我總結的最大動力!
-
由於我在「稀土掘金」「簡書」「
CSDN
」「部落格園」等站點,都有新內容發布。所以大家可以直接關注我的GitHub
倉庫,以免錯過精彩內容! -
一萬多字長文,加上精美思維導圖,記得點贊哦,歡迎關注 _yuanhao 的 部落格園 ,我們下篇文章見!