BroadcastReceiver
、ContentProvider
分兩篇來總結,但的確,這兩大組件的使用不像 Activity
、Service
那麼頻繁,因此仍是決定一次性搞定。1.5 W
字的文章就誕生了。能夠說這周幾乎全部時間都花在這上面,本身看了幾遍感受已經極爲全面了。最後,但願你們都能有所收穫,歡迎食用!java
倉庫內容與博客同步更新。因爲我在 稀土掘金
簡書
CSDN
博客園
等站點,都有新內容發佈。因此你們能夠直接關注該倉庫,以避免錯過精彩內容!android
BroadcastReceiver
,顧名思義就是「廣播接收者」的意思,它是Android四大基本組件之一。app
發送的廣播android
系統的 Binder
機制.SendOrderedBroadcast()
方法來發送廣播,同時也可調用 abortBroadcast()
方法攔截該廣播。可經過 <intent-filter>
標籤中設置 android:property
屬性來設置優先級,未設置時按照註冊的順序接收廣播。setResultData
方法將數據傳給下一個接收器。getStringExtra
函數獲取廣播的原始數據,經過 getResultData
方法取得上個廣播接收器本身添加的數據,並可用 abortBroadcast
方法丟棄該廣播,使該廣播再也不被別的接收器接收到。A > B > C
,Context.sendOrderedBroadcast ( intent , receiverPermission , resultReceiver , scheduler , initialCode , initialData , initialExtras )
時咱們能夠指定 resultReceiver
爲最終廣播接收者.onReceive
會執行兩次sd
卡掛載, 低電量, 外撥電話, 鎖屏等對前一部分 「 請描述一下
BroadcastReceiver
」 進行展開補充github
APP
內部的消息通訊。不一樣 APP
之間的消息通訊。算法
Android
系統在特定狀況下與 APP 之間的消息通訊。sql
廣播使用了觀察者模式,基於消息的發佈 / 訂閱事件模型。廣播將廣播的發送者和接受者極大程度上解耦,使得系統可以方便集成,更易擴展。數據庫
BroadcastReceiver 本質是一個全局監聽器,用於監聽系統全局的廣播消息,方便實現系統中不一樣組件間的通訊。設計模式
自定義廣播接收器須要繼承基類 BroadcastReceiver
,並實現抽象方法 onReceive ( context, intent )
。默認狀況下,廣播接收器也是運行在主線程,所以 onReceiver()
中不能執行太耗時的操做( 不超過 10s
),不然將會產生 ANR
問題。onReceiver()
方法中涉及與其餘組件之間的交互時,可使用發送 Notification
、啓動 Service
等方式,最好不要啓動 Activity
。數組
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
文件中聲明接收器。APP
APP
不會受到局部廣播,不用擔憂數據泄露的問題。APP
不可能向當前的 APP
發送局部廣播,不用擔憂有安全漏洞被其餘 APP
利用。Android v4
包中提供了 LocalBroadcastManager
類,用於統一處理 APP 局部廣播,使用方式與全局廣播幾乎相同,只是調用註冊 / 取消註冊廣播接收器和發送廣播偶讀方法時,須要經過 LocalBroadcastManager
類的 getInstance()
方法獲取的實例調用。在 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
所接受;若是沒有設置,這個值賦予整個應用所申請的權限。Context
的 registerReceiver ( BroadcastReceiver receiver , IntentFilter filter )
方法指定。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>
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(); }
public void click(View view) { Intent intent = new Intent(); intent.putExtra("info", "消息內容"); intent.setAction("myBroadcast.action.call"); sendBroadcast(intent); }
app
以後:app
直接發廣播就收到了app
先代碼註冊,而後另外一個 app
直接發廣播便可.-Android
中的廣播使用了設計模式中的觀察者模式:基於消息的發佈 / 訂閱事件模型。3
個角色:AMS
,即 Activity Manager Service
)廣播接收者經過 Binder
機制在 AMS
( Activity Manager Service
) 註冊;
廣播發送者經過 Binder
機制向 AMS
發送廣播;
AMS
根據廣播發送者要求,在已註冊列表中,尋找合適的 BroadcastReceiver
( 尋找依據:IntentFilter / Permission
);
AMS
將廣播發送到 BroadcastReceiver
相應的消息循環隊列中;
廣播接收者經過消息循環拿到此廣播,並回調 onReceive()
方法。
須要注意的是:廣播的發送和接受是異步的,發送者不會關心有無接收者或者什麼時候收到。
LocalBroadcastManager
來對廣播進行管理,並提供了發送廣播和註冊廣播接收器的方法。BroadcastReceiver
子類,並重寫 onReceive ( Context context, Intetn intent )
方法便可。當其餘組件經過 sendBroadcast()
、sendStickyBroadcast()
、sendOrderBroadcast()
方法發送廣播消息時,若是該 BroadcastReceiver
也對該消息「感興趣」,BroadcastReceiver
的 onReceive ( Context context, Intetn intent )
方法將會被觸發。
使用步驟:
Activity
時,須要爲 Intent
加入 FLAG_ACTIVITY_NEW_TASK
標記,不然會報錯,由於須要一個棧來存放新打開的 Activity
。Alertdialog
時,須要設置對話框的類型爲 TYPE_SYSTEM_ALERT
,不然沒法彈出。onReceiver()
方法中添加過多的邏輯或者進行任何的耗時操做,由於在廣播接收器中是不容許開啓線程的,當 onReceiver()
方法運行了較長時間而沒有結束時,程序就會報錯。Android
引入了 StickyBroadcast
,在廣播發送結束後會保存剛剛發送的廣播( Intent
),這樣當接收者註冊完 Receiver
後就能夠繼續使用剛纔的廣播。若是在接收者註冊完成前發送了多條相同 Action
的粘性廣播,註冊完成後只會收到一條該 Action
的廣播,而且消息內容是最後一次廣播內容。
Context
的 sendStickyBroadcast ( Intent )
接口發送,須要添加權限uses-permission android:name=」android.permission.BROADCAST_STICKY」
Context
的 removeStickyBroadcast ( Intent intent )
接口移除緩存的粘性廣播使用它發送的廣播將只在自身APP內傳播,所以你沒必要擔憂泄漏隱私數據;
其餘 APP
沒法對你的 APP
發送該廣播,由於你的APP根本就不可能接收到非自身應用發送的該廣播,所以你沒必要擔憂有安全漏洞能夠利用;
比系統的全局廣播更加高效。
LocalBroadcastManager
內部協做主要是靠這兩個 Map
集合:MReceivers
和 MActions
,固然還有一個 List 集合 MPendingBroadcasts
,這個主要就是存儲待接收的廣播對象。
LocalBroadcastManager
高效的緣由主要是由於它內部是經過 Handler
實現的,它的 sendBroadcast()
方法含義並不是和咱們平時所用的同樣,它的 sendBroadcast()
方法實際上是經過 handler
發送一個 Message
實現的;
既然它內部是經過 Handler
來實現廣播的發送的,那麼相比於系統廣播經過 Binder
實現那確定是更高效了,同時使用 Handler
來實現,別的應用沒法向咱們的應用發送該廣播,而咱們應用內發送的廣播也不會離開咱們的應用;
BroadcastReceiver
設計的初衷是從全局考慮能夠方便應用程序和系統、應用程序之間、應用程序內的通訊,因此對單個應用程序而言BroadcastReceiver
是存在安全性問題的 ( 惡意程序腳本不斷的去發送你所接收的廣播 ) 。爲了解決這個問題 LocalBroadcastManager
就應運而生了。
LocalBroadcastManager
是 Android Support
包提供了一個工具,用於在同一個應用內的不一樣組件間發送 Broadcast
。LocalBroadcastManager
也稱爲局部通知管理器,這種通知的好處是安全性高,效率也高,適合局部通訊,能夠用來代替 Handler
更新 UI
Android
系統中的廣播能夠跨進程直接通訊,會產生如下兩個問題:APP
能夠接收到當前 APP
發送的廣播,致使數據外泄。APP
能夠向當前 APP
放廣播消息,致使 APP
被非法控制。permission
,用於權限驗證。Android 4.0
及以上系統中發送廣播時,可使用 setPackage()
方法設置接受廣播的包名。permission
,用於權限驗證。android:exported
的值爲false。permission
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"); }
<uses-permission android:name="broad.ok.receiver"/>
onReceiver
方法裏,直接調用判斷方法得返回值public void onReceive(Context context, Intent intent) { Log.d("MyBroadCastReceiver", "收到信息,內容是 : " + intent.getStringExtra("info") + ""); boolean isOrderBroadcast = isOrderedBroadcast(); }
BroadcastReceiver
通常處於主線程。ANR
BroadcastReceiver
啓動時間較短。BroadcastReceiver
組件。而且在其中開啓子線程執行耗時任務。ContentProvider
應用程序間很是通用的共享數據的一種方式,也是 Android
官方推薦的方式。Android
中許多系統應用都使用該方式實現數據共享,好比通信錄、短信等。Android
開發的人都不怎麼使用它,以爲直接讀取數據庫會更簡單方便。Android
搞一個內容提供者在數據和應用之間,只是爲了裝高大上,故弄玄虛?我認爲其設計用意在於:DB
,XML
、Preferences
或者網絡請求來的。當項目需求要改變數據來源時,使用咱們的地方徹底不須要修改。ContentResolver
接口的 notifyChange
函數來通知那些註冊了監控特定 URI的ContentObserver 對象,使得它們能夠相應地執行一些處理。ContentProvider
的 Uri
訪問開放的數據。ContenResolver
對象經過 Context
提供的方法 getContenResolver()
來得到。ContenResolver
提供瞭如下方法來操做:insert
delete
update
query
這些方法分別會調用 ContenProvider
中與之對應的方法並獲得返回的結果。ContentResolver
類對象:ContentResolver cr = getContentResolver ( )
。String
數組。cr.query()
; 返回一個 Cursor
對象。while
循環獲得 Cursor
裏面的內容。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[]); }
ContentProvider
屏蔽了數據存儲的細節 , 內部實現對用戶徹底透明 , 用戶只須要關心操做數據的 uri
就能夠了, ContentProvider
能夠實現不一樣 app
之間 共享。Sql
也有增刪改查的方法, 可是 sql
只能查詢本應用下的數據庫。ContentProvider
還能夠去增刪改查本地文件. xml
文件的讀取等。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
。db
複製到 /data/data/packagename/databases/
目錄下, 而後直接就能訪問了。ContentProvider
能夠接受來自另一個進程的數據請求。ContentResolver
與 ContentProvider
類隱藏了實現細節,可是 ContentProvider
所提供的 query()
,insert()
,delete()
,update()
都是在 ContentProvider
進程的線程池中被調用執行的,而不是進程的主線程中。Binder
建立和維護的,其實使用的就是每一個應用進程中的 Binder
線程池。ContentProvider
能夠對開發的數據進行權限設置,不一樣的 URI
能夠對應不一樣的權限,只有符合權限要求的組件才能訪問到 ContentProvider
的具體操做。ContentProvider
封裝了跨進程共享的邏輯,咱們只須要 Uri
便可訪問數據。由系統來管理 ContentProvider
的建立、生命週期及訪問的線程分配,簡化咱們在應用間共享數據( 進程間通訊 )的方式。咱們只管經過 ContentResolver
訪問 ContentProvider
所提示的數據接口,而不須要擔憂它所在進程是啓動仍是未啓動。ContentProvider
的 onCreate()
是運行在 UI
線程的,而 query()
,insert()
,delete()
,update()
是運行在線程池中的工做線程的ContentProvider
所在進程的主線程,但可能會阻塞調用者所在的進程的 UI
線程!ContentProvider
的操做仍然要放在子線程中去作。CRUD
的操做是在工做線程的,但系統會讓你的調用線程等待這個異步的操做完成,你才能夠繼續線程以前的工做。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" />
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>
ContentProvider
能夠在 AndroidManifest.xml
中配置一個叫作 android:multiprocess
的屬性,默認值是 false ,表示 ContentProvider 是單例的ContentProvider
對象,若是設爲 true
,系統會爲每個訪問該 ContentProvider
的進程建立一個實例。好比咱們在UI線程調用getContentResolver().query查詢數據,而當數據量很大時(或者須要進行較長時間的計算)會不會阻塞UI線程呢?
要分兩種狀況回答這個問題:
ContentProvider
和調用者在同一個進程,ContentProvider
的方法( query/insert/update/delete
等 )和調用者在同一線程中;ContentProvider
和調用者在不一樣的進程,ContentProvider
的方法會運行在它自身所在進程的一個 Binder 線程中。ContentProvider
的方法沒有執行完成前都會 blocked
調用者。因此你應該知道這個上面這個問題的答案了吧。CursorLoader
這個類的源碼,看 Google
本身是怎麼使用 getContentResolver().query
的。16
個 Binder
線程去和遠程服務進行交互,而每一個線程可佔用的緩存空間是 128KB
這樣,超過會報異常。ContentResolver
雖然是經過 Binder
進程間通訊機制打通了應用程序之間共享數據的通道,但 ContentProvider
組件在不一樣應用程序之間傳輸數據是基於匿名共享內存機制來實現的。BroadcastReceiver
、ContentProvider
知識總結了,前先後後投入了大量時間來完成。但願你們經過本次閱讀都能有所收穫。重點
:關於 Android
的四大組件,到如今爲止我才總結完 Activity
、Service
、BroadcastRecevier
、ContentProvider
等,以及事件分發、滑動衝突、新能優化等重要模塊,進行全面總結,歡迎你們關注 _yuanhao 的 博客園 ,方便及時接收更新因爲我在「稀土掘金」「簡書」「CSDN
」「博客園」等站點,都有新內容發佈。因此你們能夠直接關注個人 GitHub
倉庫,以避免錯過精彩內容!
一萬多字長文,加上精美思惟導圖,記得點贊哦,歡迎關注 _yuanhao 的 博客園 ,咱們下篇文章見!