【朝花夕拾】四大組件之(一)Broadcast篇

前言html

       筆者最近在探究ANR及源碼的過程當中,發現對Broadcast的一些應用層面上的知識有的感受比較生疏,有的記憶不許確,有的認識不完整。所謂「基礎不牢,地動山搖」,因而就梳理了一下Broadcast的一些知識點,查漏補缺,加深對它的全面認識。該篇文章是基於源碼、官網、工做經驗以及實驗結果完成的,閱讀本文須要必定的基礎,若是是初學者,理解起來可能有必定的難度,須要必定的耐心。java

       本文主要包含以下內容:android

 

 

1、總體認識程序員

       廣播是一個全局的監聽器,能夠監聽者整個系統,也能夠監聽者整個app。通常咱們說「廣播是Android的四大組件之一」,但準確點說應該是「廣播接收者(Broadcast Receiver)是Android的四大組件之一」,它也是四大組件中最簡單的一個。但「麻雀雖小,五臟俱全」,廣播有着本身的生命週期,有着豐富的類型,對性能有着巨大的影響,能用於跨進程通訊和進程內組件間通訊,是系統ANR發送的根源之一......編程

       Android開發者官網中對廣播的介紹以及開發者幫助文檔以下:【Broadcasts overview】【BroadcastReceiver開發幫助文檔】。設計模式

 

2、基本原理安全

       廣播的實現使用了設計模式中的觀察者模式,基於消息的發佈/訂閱事件模型,這其中有3個角色:(1)消息發佈者(廣播發送者);(2)消息中心(AMS:Activity Manager Service);(3)消息訂閱者(廣播接收者)。這使得廣播的發送者和接收者高度解耦,使用很是方便。如下序列圖(不是嚴格意義上的序列圖,勿噴)顯示了廣播實現的基本流程及原理,其中第一步,第二步,第四步須要開發者手動來完成,其餘的由系統自動完成。廣播的發送和接收是須要消息,廣播發送後,不肯定必定有接收者,也不肯定接收者何時會接收到。網絡

 

圖2.1 廣播實現及原理流程圖併發

 

3、廣播的註冊app

       廣播的註冊方式有靜態註冊和動態註冊之分,靜態註冊是指在AndroidManifest.xml中進行註冊,動態註冊是指在代碼中進行註冊。

  一、靜態註冊

      從Android8.0開始,系統對靜態註冊增長了很大的限制,不少以往版本可以正常使用的靜態註冊的廣播,從該版本開始極可能就會失效。這一點在後文中「不一樣Android系統版本中廣播機制的重要變遷」這一節中會詳細介紹,這裏不贅述。

  (1)靜態註冊中的屬性簡介

       Android開發者官網【receiver屬性】【intent-filter屬性】中對這些屬性作了詳細的說明,我們這裏作一些翻譯及補充。

 1 <receiver
 2       android:directBootAware=["true" | "false"]
 3       android:enabled=["true" | "false"]
 4       android:exported=["true" | "false"]
 5       android:icon="drawable resource"
 6       android:label="string resource"
 7       android:name=".MBroadcastReceiver"
 8       android:permission="string"
 9       android:process="string" >
10      <intent-filter android:icon="drawable resource"
11                android:label="string resource"
12                android:priority="integer">
13            <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
14      </intent-filter>
15  </receiver>
  • android:directBootAware

       是否能夠直接啓動屬性。這個屬性應該是Android 7.0(Android N)開始才添加上去的,由於從Android7.0開始採用文件加密系統FBE。該屬性不只僅在<receiver>標籤中有,在其它組件和<application>中也能夠設置,只是影響的範圍不同。在<application>中該屬性爲true的狀況下:若是<receiver>中這個屬性值爲false,那麼手機重啓後,在屏幕沒有解鎖的狀況下,該廣播所能訪問到的數據都是加密的,將不能啓動;若是爲true,則能夠被啓動。其默認值爲false,也就是說手機重啓後在沒有解鎖的狀況下,該廣播不能被啓動。

       對於文件加密系統FBE,比較重要,內容也有點複雜,這裏推薦兩份官網的資料:【直接啓動】、【文件級加密】。

  • android:enabled

       定義系統是否可以實例化這個廣播接收器。若是設置爲true,表示可以被實例化;若是爲false,表示不能被實例化;默認值爲true。<application>中也有該屬性,用於設置設置全部組件是否能實例化,因此只有當<application>和<receiver>中這個屬性值都爲true,該廣播接收器纔可以被啓動,只要有一個爲false,那麼都會被禁止實例化。

  • android:exported

       這個屬性用於指示該廣播接收器是否能夠接收來其餘App或不一樣userID App發送的廣播。若是設置爲true,表示能夠接收;若是設置爲false,表示只能接收同一個app或有相同userID的App發出的廣播。它的默認值比較特殊,依賴因而否有intent-filter屬性,若是有則默認值爲true,不然爲false。若是爲false,該接收器只能由指定了明確類名的Intent對象來調用,這就意味着該接收器只能在應用程序內部使用,由於一般在應用程序外部並不知道這個類名,通常來講某個廣播對外都是以action的方式提供入口的。另外一方面,intent-filter屬性的做用就是用於接收外部App或系統廣播的,天然而然默認值就是true了。

      除了這個屬性外,還有後面的「android:permission」屬性,也能夠用於限制哪些intent實體能夠向該接收器發送廣播。

  • android:icon

       該屬性定義表明該廣播接收器的圖標,其值爲圖片資源。<application>中也有icon屬性,並且是全部組件中該屬性的默認值。也就是說,包括廣播接收者在內的全部組件在沒有本身獨立設置該屬性的狀況下,都用<application>中icon屬性的值,若是組件本身設置了,就以本身設置的爲準。

       後面每一個<intent-filter>中也有icon屬性,會以這裏<receiver>中icon的屬性值做爲默認值。

  • android:label

       該屬性給該接收器設定了一個用戶可讀的文本標籤,其值爲string類型。它的默認值特性以及對後面<intent-filter>的影響都和icon相似,這裏就不贅述了。

  • android:name

       該屬性指定了廣播接收器,是BroadcastReceiver的子類,其值爲自定義的廣播接收器類的全名,如「com.example.songwei.MBroadcastReceiver」,或者爲了便捷能夠省略掉包名,如「.MBroadcastReceiver」(假設在<manifest>中定義的包名爲"com.example.songwei」)。這個類就是真正處理廣播事件的地方,沒有默認值,必需要指定,且不能隨便填寫,不然編譯不過。若是類名或路徑有修改,該屬性值必定要同步,若是沒有填寫錯誤的話,在開發工具中,點擊該值能夠直接跳轉到該類。

  • android:permission

       該屬性用於指定該接收器能接受到指定廣播所必須擁有的權限,該權限在廣播發送者處定義。若是此處沒有設置,就以<application>中的permission值爲默認值。若是<application>中也沒有設置,就認爲該接收器不授權限保護。固然,若是兩處都設置了該值,以此處爲準。

  • android:process

       該屬性指定了該接收器運行的進程名。通常來講,全部組件都運行在app默認建立的進程中,該進程名與包名相同。若是這裏沒有設值,就以<application>中該屬性值爲默認值;若是<application>中也沒定義,就運行在App的默認進程中;若是兩處都設值了,就以此處爲準。

       若是該值以「:」打頭,當收到廣播後,就會建立一個當前App私有的進程,這個進程會以該值爲進程名,且廣播接收器會運行在這個進程中。若是該值以小寫字母打頭,接收器就會運行在以該值爲進程名的全局進程中,這樣可讓不一樣app中的不一樣組件能夠共享一個進程,從而下降資源的損耗。

  • intent-filter

       這個元素主要用於根據action值來過濾知足條件的廣播。一個<receiver>中能夠定義多個<intent-filter>,每一個<intent-filter>中又有icon,lable等屬性來表徵本身。

  • android:priority

       該屬性用於設置優先級。有序廣播OrderedBroadcast的接收者們將按照該值的大小依次接收,若是大小相同則誰先註冊誰先接收。取值範圍爲-1000~10000,數值越大優先級越高。

  • action

       該屬性用於匹配是否爲須要接收的廣播。

    (2)靜態註冊與AMS

  二、動態註冊

    (1)動態註冊與AMS

       registerReceiver的方法有以下4個(來源於AndroidStudio的提示),這裏選取第一個做爲例子來分析一下:

咱們知道,不管是Activity仍是Service,都是Context的子類,其繼承鏈以下所示:

    

ContextWrapper.java中重寫了registerReceiver方法,因此按住Ctrl鍵並在AndroidStudio中點擊該方法,會跳轉到以下代碼中:

ContextWrapper.java(extends Context.java)

1
Context mBase; 2 public ContextWrapper(Context base) { 3 mBase = base; 4 } 5 ...... 6 @Override 7 public Intent registerReceiver( 8 BroadcastReceiver receiver, IntentFilter filter) { 9 return mBase.registerReceiver(receiver, filter); 10 }

Context.java是一個抽象類,registerReceiver方法也是在這個抽象類中定義的抽象方法。

Context.java
1 @Nullable
2 public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,IntentFilter filter);

咱們知道面向接口編程,是在接口或抽象類中定義,在具體實現類或子類中具體實現。該抽象方法,實際是在ContextImpl中實現的:

ContextImpl.java(extends Context.java)

1
@Override 2 public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { 3 return registerReceiver(receiver, filter, null, null); 4 } 5 ...... 6 @Override 7 public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, 8 String broadcastPermission, Handler scheduler) { 9 return registerReceiverInternal(receiver, getUserId(), 10 filter, broadcastPermission, scheduler, getOuterContext(), 0); 11 } 12 ...... 13 private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, 14 IntentFilter filter, String broadcastPermission, 15 Handler scheduler, Context context, int flags) { 16 ...... 17 18 final Intent intent = ActivityManager.getService().registerReceiver( 19 mMainThread.getApplicationThread(), mBasePackageName, rd, filter, 20 broadcastPermission, userId, flags); 21 ...... 22 }

ActivityManager中getService()其實是一個Binder:

ActivityManager.java

1
public static IActivityManager getService() { 2 return IActivityManagerSingleton.get(); 3 } 4 5 private static final Singleton<IActivityManager> IActivityManagerSingleton = 6 new Singleton<IActivityManager>() { 7 @Override 8 protected IActivityManager create() { 9 final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE); 10 final IActivityManager am = IActivityManager.Stub.asInterface(b); 11 return am; 12 } 13 };

經過Binder方式,實際上就調用AMS中的registerReceiver方法了:

ActivityManagerService.java

1
public Intent registerReceiver(IApplicationThread caller, String callerPackage, 2 IIntentReceiver receiver, IntentFilter filter, String permission, int userId, 3 int flags) { 4 ...... 5 }

一步步跟蹤過來能夠看到,咱們應用層的廣播註冊代碼registerReceiver最終轉移到了AMS中,這就對應上了「圖2.1 廣播實現及原理流程圖」中的第2步了。其它的幾個註冊廣播函數也最終調用了AMS中的registerReceiver方法,只是傳進來的參數值不同而已。

    (2)取消註冊

       若是是動態註冊,須要在相應的生命週期中取消註冊(有的地方也稱爲反註冊)。取消註冊的函數只有若有一個:

咱們按照上文註冊廣播的方式追蹤代碼,能夠發現最後也是到了AMS中來實現的。

ActivityManagerService.java
1  public void unregisterReceiver(IIntentReceiver receiver) {
2      ......
3  }

 

4、廣播的發送

       發送廣播的函數比較多,以下圖所示,咱們能夠看到Sticky(粘性)廣播已經被設置爲過期方法了。這裏選取最簡單的sendBroadcast(Intent intent)來分析。

咱們參照前面動態廣播註冊的方法追蹤源碼,關鍵流程以下

Context.java

1
public abstract void sendBroadcast(@RequiresPermission Intent intent);

 ContextWrapper.java

 1 ContextWrapper.java(extends Context.java)
 2 Context mBase;
 3 public ContextWrapper(Context base) {
 4     mBase = base;
 5 }
 6 ......
 7 @Override
 8 public Intent registerReceiver(
 9         BroadcastReceiver receiver, IntentFilter filter) {
10     return mBase.registerReceiver(receiver, filter);
11 }
View Code

 ContextImpl.java

 1 ContextImpl.java(extents Context.java)
 2     @Override
 3     public void sendBroadcast(Intent intent) {
 4         warnIfCallingFromSystemProcess();
 5         String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
 6         try {
 7             intent.prepareToLeaveProcess(this);
 8             ActivityManager.getService().broadcastIntent(
 9                     mMainThread.getApplicationThread(), intent, resolvedType, null,
10                     Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
11                     getUserId());
12         } catch (RemoteException e) {
13             throw e.rethrowFromSystemServer();
14         }
15     }
View Code

 ActivityManagerService.java

 1 public final int broadcastIntent(IApplicationThread caller,
 2             Intent intent, String resolvedType, IIntentReceiver resultTo,
 3             int resultCode, String resultData, Bundle resultExtras,
 4             String[] requiredPermissions, int appOp, Bundle bOptions,
 5             boolean serialized, boolean sticky, int userId) {
 6         enforceNotIsolatedCaller("broadcastIntent");
 7         synchronized(this) {
 8             intent = verifyBroadcastLocked(intent);
 9 
10             final ProcessRecord callerApp = getRecordForAppLocked(caller);
11             final int callingPid = Binder.getCallingPid();
12             final int callingUid = Binder.getCallingUid();
13             final long origId = Binder.clearCallingIdentity();
14             int res = broadcastIntentLocked(callerApp,
15                     callerApp != null ? callerApp.info.packageName : null,
16                     intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
17                     requiredPermissions, appOp, bOptions, serialized, sticky,
18                     callingPid, callingUid, userId);
19             Binder.restoreCallingIdentity(origId);
20             return res;
21         }
22     }
View Code

        這裏就對應上了「圖2.1 廣播實現及原理流程圖」中的第5步了。其餘的幾個發送廣播的方法,也都最終調用到了如上的broadcastIntentLocke()方法中,也只是其中的參數值不一樣而已。因此真正發送廣播的邏輯實現,是在AMS中來完成的。

 

5、廣播的類型

      根據不一樣的角度,廣播能夠分爲不一樣的類型,這些分類基本都是根據廣播發送者來決定的。下面我們來詳細探討一下這些類型。

  一、自定義廣播和系統廣播

       根據發送的廣播是用戶本身定義的仍是由系統定義的,可將廣播分爲自定義廣播和系統廣播。這個比較簡單,容易理解,不過多說明,這裏能夠了解一下Android系統爲咱們定義了哪些廣播【Android系統廣播大全】。

  二、普通廣播和有序廣播

       根據廣播發送者發送的是否爲有序廣播,能夠將廣播分爲普通廣播(無序廣播)和有序廣播。

    (1)普通廣播

       普通廣播,有的文章中稱爲標準廣播,以異步的方式發送廣播給接收者。Android系統AMS(ActivityManagerService)發出廣播後,全部知足條件的廣播幾乎能夠同時受到廣播,沒有順序之分,也無需向AMS返回處理結果。這種方式下,各個接收者之間不會相互干擾,效率較高。大體形式以下圖所示:

       

    (2)有序廣播

       有序廣播是以一種同步的方式向接收者發送廣播的。廣播接收者會根據開發者設定的優先級進行排序,AMS會先給最高優先級接收者發送廣播,該接收器處理完廣播後須要返回處理結果給AMS,而後再向次優先級接收者發送廣播,依此類推。廣播接收者能夠把處理結果傳遞給是下一個接收者,也能夠終止廣播的傳遞。若是某個接收者超時或者終止了廣播,那麼後面的接收者也將沒法再收到廣播,因此效率會比較低。短信攔截功能,就是根據這個原理來實現的。有序廣播傳遞的大體情形以下圖所示:

       有序廣播的使用能夠參考一下【Android中廣播接收者BroadcastReceiver詳解】,其中對比普通廣播,有幾個關鍵的地方須要注意:1)android:priority用於設置接收器優先級。2)getResultData()獲取廣播數據。3)setResultData()向下一接收者傳遞數據。4)abortBroadcast()用於終止廣播的傳遞。

  三、全局廣播和局部廣播

       根據發送的廣播但願被接收的範圍,能夠將廣播分爲全局廣播和局部廣播。從機制上看,若是發送廣播的能夠被其餘app(以app進程爲限)接收,那麼該廣播爲全局廣播;若是隻能被app內部接收,那麼該廣播爲局部廣播。

    (1)全局廣播

       咱們平時使用的Broadcast就是全局廣播,由於這些廣播能夠在整個系統中進行傳播。全局廣播在安全和性能方面存在一些問題,無疑增長了系統的負擔,好比發送的廣播攜帶的一些數據信息能夠被其餘app接收到;app外部發送的一些垃圾廣播也會被app意外接收併產生一些響應;多個知足匹配要求的廣播接收器可能同時收到一個廣播等。爲了解決這方面的問題,能夠經過設置「android:exported」,permission,setPackage等各類方式來限定接收範圍,從而在安全和性能方面進行優化。

    (2)局部廣播簡介

       Android也提供了另一種更爲簡單且效率更高的方式——局部廣播。局部廣播,也叫作本地廣播,Android v4 兼容包提供android.support.v4.content.LocalBroadcastManager工具類用於實現局部廣播。谷歌官方文檔的介紹以下【LocalBroadcastManager官方文檔】:

Helper to register for and send broadcasts of Intents to local objects within your process. This has a number of advantages over sending global broadcasts with sendBroadcast(Intent): ● You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data. ● It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit. ● It is more efficient than sending a global broadcast through the system.

這裏獻醜大體翻譯一下:

(LocalBroadcastManager)用於在你的(當前app)進程中幫助你註冊和發送Intent的廣播I到本地對象。相比於經過sendBroadcast(Intent)的方式發送全局廣播,這種方式有很多優點:
● 你所傳播的數據不會離開當前你的app,因此無需擔憂會泄漏私人數據。 ● 其餘app沒法發送廣播到你的app,因此你無需擔憂這些廣播致使的安全漏洞。 ● 比起經過系統來實現發送的全局廣播,這種方式更高效(不須要發送給整個系統)。

    (3)局部廣播的使用

       局部廣播的使用也比較簡單,基本使用以下:

 1 /**
 2      * 一、自定義廣播action
 3      */
 4     public static final String MY_ACTION = "com.songwei.action.MY_ACTION";
 5 
 6     /**
 7      * 二、發送局部廣播
 8      */
 9     private void sendBroadcast() {
10         LocalBroadcastManager.getInstance(mContext).sendBroadcast(
11                 new Intent(MY_ACTION)
12         );
13     }
14     
15     /**
16      * 三、自定義廣播接收器
17      */
18     private class MyBroadcastReceiver extends BroadcastReceiver {
19 
20         @Override
21         public void onReceive(Context context, Intent intent) {
22             //處理具體的邏輯
23         }
24     }
25     
26     /**
27      * 四、註冊/取消註冊廣播
28      */
29     private MyBroadcastReceiver mReceiver = new MyBroadcastReceiver();
30 
31     private void registerLoginBroadcast() {
32         IntentFilter intentFilter = new IntentFilter(MY_ACTION);
33         LocalBroadcastManager.getInstance(mContext).registerReceiver(mReceiver, intentFilter);
34     }
35 
36     private void unRegisterLoginBroadcast() {
37         LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mReceiver);
38     }
View Code

       從上面的代碼能夠發現,與全局廣播的使用相比,僅僅就是多了粗斜體部分的代碼,就是在sendBroadcast/registerReceiver/unRegisterReceiver這幾個方法前都加上了LocalBroadcastManager.getInstance(mContext)。

    (4)局部廣播注意事項

       相比於全局廣播,有幾點須要特別注意:

        1)該方式只能經過代碼動態註冊,不能在AndroidManifest.xml文件中靜態註冊。

        2)必定不要忘記前面提到的三個方法前加上LocalBroadcastManager.getInstance(mContext),不然可能接收不到廣播或者沒法實現局部廣播的效果。

        3)其餘方面如在對應的地方要取消註冊,onReceive()方法中不進行耗時操做等,和全局廣播一致,這裏不贅述。

  四、前臺廣播和後臺廣播

       在閱讀關於ANR的文章的時候,咱們常常會看到相似這樣的描述:對於BroadcastReceiver事件中onReceive()方法,在規定時間內沒有執行完成會致使ANR,前臺廣播的規定時間是10s,後臺廣播是60s。根據發送的廣播被接收的優先級,能夠將廣播分爲前臺廣播和後臺廣播。

    (1)前臺廣播

       添加Intent.FLAG_RECEIVER_FOREGROUND這個flag,能夠將廣播設置爲前臺廣播。咱們知道,廣播發出後,廣播接收器會有很多的延遲後纔會收到廣播,這樣對於一些緊急的事件確定是不利的。爲了減小這個延時,能夠將該廣播設置爲前臺廣播,當發送該前臺廣播時,會容許接收者之前臺的優先級運行,優先接受並處理該廣播事件。固然這也就要求廣播接收器在更短的時間內(10s) 完成廣播事件,咱們知道,在沒有特別處理的狀況下,onReceive方法是運行在UI線程的,若是不盡快處理完,前臺廣播這個擁有特權的廣播就會對整個系統產生較大的干擾。前臺廣播的設置代碼以下所示:

1 Intent mIntent = new Intent(string action);                                 
2 mIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 
3 mContext.sendBroadcast(mIntent);

    (2)後臺廣播

       若是不添加Intent.FLAG_RECEIVER_FOREGROUND這個flag,系統默認該廣播爲後臺廣播,或者將flag設置爲Intent.FLAG_FROM_BACKGROUND,也能夠將廣播設置爲後臺廣播,此時對應的廣播接收者的優先級爲後臺優先級。

       這裏咱們看出,這裏的前臺廣播和後臺廣播之分,是針對廣播的接收優先級而言的,而不是取決因而否與用戶有交互行爲,更不是onReceiver方法處於UI線程仍是後臺工做線程。對於這兩個flag的含義能夠看一下參考資料【Android Intent的FLAG詳解】。

  五、併發廣播和串行廣播

       根據接收和處理廣播事件的方式是並行的仍是串行的,能夠把廣播分爲併發廣播和串行廣播。

    (1)並行廣播

       並行廣播在有的文章中也被稱爲平行廣播、並行廣播等,它是指在接收並處理廣播時,全部接收者是無序的,它們之間相互獨立,沒有相互依賴的關係。在不少程序員的認知中,普通廣播都是並行廣播,由於它們的廣播接收器不像有序廣播那樣,須要設置優先級。事實上這種認知是錯誤的,在AndroidManifest.xml中靜態註冊的普通廣播實際上是串行廣播,而只有在代碼中動態註冊的普通廣播纔是真正的並行廣播。

    (2)串行廣播

       串行廣播是指廣播接收者會根據優先級或註冊時間等因素進行排序,來一個接着一個地處理廣播事件。前面提到的有序廣播,就是串行廣播,靜態註冊的普通廣播也是串行廣播。

    (3)總結

       爲了加深印象和糾正錯誤的認識,能夠對這兩類廣播簡單作一點概括和總結:

    (4)參考資料

       對於併發廣播和串行廣播的分類,以下資料從源碼的角度對此進行了細緻的分析,有興趣的讀者能夠深刻探索:

      【[Android]Ams 廣播發送原理(三)】 

      【[Android]BroadcastQueue如何分發廣播(四)

      【說說Android的廣播(2) - 併發隊列和串行隊列

      【說說Android的廣播(3) - 什麼樣的廣播是併發的?】  

  六、粘性廣播和非粘性廣播

       根據發送廣播的方法中是否有sticky字樣,能夠分爲粘性廣播和非粘性廣播。粘性廣播即Sticky Broadcast,在Android5.0及之後,粘性廣播已經deprecated,和它相關的粘性有序廣播,也一樣deprecated,這裏就不深刻了。

 

6、廣播接收器生命週期

       做爲Android的四大組件之一,廣播接收器天然也有本身的生命週期。只是它的生命週期很是簡單且很是短,只有onReceive一個回調方法,當收到廣播產生onReceive回調開始,到onReceive方法執行完並return,廣播接收器的生命週期便宣告結束。

 

7、onReceive方法所在上下文

      根據廣播不一樣的做用範圍和註冊方式,廣播接收器的onReceive(Context context,Intent intent)方法所持有的上下文context存在一些差別。

  一、局部廣播(LocalBroadcastManager方式),只有動態動態註冊方式,context的返回值爲:Application Context實例。演示結果以下(注:這裏沒有自定義Application,用的系統默認的):

02-14 11:36:03.903 8698-8698/com.example.demos I/BroadcastDemo: context=android.app.Application@d6e8e9d

  二、全局廣播靜態註冊,這種狀況下context的返回值爲:ReceiverRestrictedContext實例。演示結果以下(注:這是在Android6.0設備上測試的結果,Android8.0開始對隱性靜態註冊作了很大的限制):

02-14 11:29:51.175 8159-8159/com.example.demos I/BroadcastDemo: context=android.app.ReceiverRestrictedContext@b8e7e15

  三、全局廣播動態註冊,這種狀況下和當前所在組件有關,若是是Activity中,那麼context的返回值爲:當前Activity Context實例;若是是Service,那麼context的返回值爲:當前Service Context實例。演示結果以下(注:當前註冊的廣播所在組件爲分別爲BroadcastDemoActivity和MyService):

1 02-14 11:09:35.692 6322-6322/com.example.demos I/BroadcastDemo: context=com.example.demos.BroadcastDemoActivity@3fee6c4
2 ...
3 02-14 11:52:04.427 9854-9854/com.example.demos I/BroadcastDemo: context=com.example.demos.MyService@59ce1f7

 

 

8、onReceive方法所在線程

       有時候會碰到有人問到這樣的問題「廣播的onReceive方法必定運行在UI線程嗎?」,看過一些非權威的博客文章,有的說是通常狀況是在UI線程,但有些狀況例外;有的說是早期版本官網裏面說的是通常狀況下是UI線程,後來版本中說法改成必定是在UI線程中。稗官野史不足爲信,因此筆者在當前最新的官網上查找了很長時間,這方面資料不多,但在【Broadcast overview—Security considerations and best practices】文章倒數第二段中有以下說明:

Because a receiver's onReceive(Context, Intent) method runs on the main thread, it should execute and return quickly.

既然官網上都這樣說了,那麼我們也就按照這裏說的來,認定「廣播的onReceive方法必定運行在UI線程」吧。

 

9、onReceive方法處理耗時操做

       咱們知道,通常狀況,廣播接收機的onReceive方法是執行在UI線程的,這就決定了在該方法中不容許直接處理耗時操做,不然會報ANR。通常若是在Activity和Service中處理耗時操做,能夠經過new Thread開啓子線程來完成,可是在廣播接收器中不能夠這樣作。

  一、緣由

       由於若是在onReceive方法中開啓了子線程後,onReceive方法執行完後,該廣播接收器生命週期便結束了,系統便認爲該組件消亡了。若是當前進程中沒有其餘組件處於活動狀態,那麼整個app進程就成爲了一個空進程,當系統內存緊張時,空進程就是首先會被系統殺死並收回內存的,這樣一來,在onReceive方法中開啓的子線程就沒法完成耗時的任務了。而Activity和Service生命週期都比較長,通常狀況下會有充足的時間完成耗時操做,不太容易被系統殺死。官方文檔【Processes and Application Lifecycle:https://developer.android.google.cn/guide/components/activities/process-lifecycle】 第四段對此有明確的說明:

A common example of a process life-cycle bug is a BroadcastReceiver that starts a thread when it receives an Intent in its BroadcastReceiver.onReceive() method ......

官方文檔【Broadcasts overview—Effects on process state:https://developer.android.google.cn/guide/components/broadcasts#effects-process-state】也作了詳細的講解:

For this reason, you should not start long running background threads from a broadcast receiver ......

  二、解決辦法

      爲了解決該問題,上述兩個官方網站中明確給出了兩種方法:JobService和goAsync()。另外還有被廣大程序員們使用的startService方法,下面簡單介紹一下這些方法。

    (1)JobService

       這種方法在上面兩個官網中都提到了,惋惜筆者沒有具體研究過這種方法,這裏就很少說了,有興趣的讀者能夠自行研究。

    (2)goAsync()

       這種方法是在文檔【Broadcasts overview—Effects on process state】中提到的,且明確給出了示例,實例很簡單,是在AsyncTask中結合PendingResult來實現的,以下所示:

 1 public class MyBroadcastReceiver extends BroadcastReceiver {
 2     private static final String TAG = "MyBroadcastReceiver";
 3 
 4     @Override
 5     public void onReceive(Context context, Intent intent) {
 6         final PendingResult pendingResult = goAsync();
 7         Task asyncTask = new Task(pendingResult, intent);
 8         asyncTask.execute();
 9     }
10 
11     private static class Task extends AsyncTask {
12 
13         private final PendingResult pendingResult;
14         private final Intent intent;
15 
16         private Task(PendingResult pendingResult, Intent intent) {
17             this.pendingResult = pendingResult;
18             this.intent = intent;
19         }
20 
21         @Override
22         protected String doInBackground(String... strings) {
23             StringBuilder sb = new StringBuilder();
24             sb.append("Action: " + intent.getAction() + "\n");
25             sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
26             String log = sb.toString();
27             Log.d(TAG, log);
28             return log;
29         }
30 
31         @Override
32         protected void onPostExecute(String s) {
33             super.onPostExecute(s);
34             // Must call finish() so the BroadcastReceiver can be recycled.
35             pendingResult.finish();
36         }
37     }
38 }
View Code

    (3)startService

       這個方法見得比較多,在onReceive中啓動Servcie,Service生命週期長,能在後臺運行,在其中開啓子線程來完成耗時操做。不要使用bindService,由於咱們知道,經過bind方式啓動Service,Service的生命週期會和調用者廣播接收器綁定,當廣播接收器消亡後,Service也會被銷燬,仍然起不到效果。

       經過前面對不能使用子線程的緣由說明,咱們能夠得知,主要是避免app進程被系統收回致使子線程沒法完成。若是是在系統app或系統進程等這樣常駐內存的狀況下,就不用擔憂進程被輕易殺死了,那麼此時在onReceive方法中使用子進程,應該是沒有問題的。

 

10、不一樣Android系統版本中廣播機制的重要變遷

       隨着Android版本的升級,系統對app的性能、安全、用戶體驗等不少方法都作有提高。天然而然,廣播機制也在一步一步地完善之中,以下就對不一樣版本的重要變遷作一些盤點。

  一、Android3.1中廣播機制的變遷

      在3.1版本以前,app靜態註冊廣播後,在收到對應廣播時,及時該app已經退出,也能收到廣播。從3.1開始,狀況有所變化:系統在廣播Intent的flag增長了兩個參數FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES,命名能夠看出,前者表示包含已經中止的包(即已經退出的app),後者表示不包含已經中止的包。若是廣播的flag被設置爲後者,那麼即便是靜態註冊了廣播,只要該app進程已經退出了,就沒法再接收到廣播。這一點咱們很容易理解,由於這樣作能夠對系統的安全和性能大有裨益。

       從3.1開始,系統對廣播的flag默認設置爲了FLAG_EXCLUDE_STOPPED_PACKAGES,對於系統廣播而言,由系統內部發出,開發者沒法修改intent中flag的值,因此若是App進程已經退出了,將沒法再收到系統廣播。而對於自定義廣播而言,能夠經過以下的方式覆蓋掉系統默認的方式。

1 Intent intent = new Intent();
2  ...
3 intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
4 sendBroadcast(intent);

       這一點能夠查看Android官方文檔【Android3.1版本變動—廣播機制變動:https://developer.android.google.cn/about/versions/android-3.1#launchcontrols】的相關章節說明。

  二、Android5.0中廣播機制的變動

       Android5.0中的廣播機制的變動主要是在粘滯廣播上,從該版本開始,普通粘滯廣播和有序粘滯廣播都被置爲過時功能,之後再也不建議使用。

  三、Android7.0中廣播機制的變動

       從該版本開始,系統移除了兩項隱式廣播:ACTION_NEW_PICTURE和ACTION_NEW_VIDEO(分別用於監聽拍照和視頻),也就是說系統將不會再發送這兩個隱式廣播了。還有一項隱式廣播CONNECTIVITY_ACTION(用於監聽網絡變化),從該版本開始,只能經過動態註冊接收,靜態註冊將失效。

更詳細的說明,能夠參考Android官方文檔【Android7.0版本變動—廣播機制變動:https://developer.android.google.cn/about/versions/nougat/android-7.0-changes#bg-opt】。

  四、Android8.0中廣播機制變動

       Android8.0主要對隱式廣播的靜態註冊作增長了限制,這個變動的影響比較大。如下從Android開發者官方文檔和實驗驗證入手,對該版本中廣播的變動進行探究。

    (1)「Broadcaset overview」中的描述

        Android開發者官網對廣播概述【廣播概述https://developer.android.google.cn/guide/components/broadcasts】中,有以下說明:

Beginning with Android 8.0 (API level 26), the system imposes additional restrictions on manifest-declared receivers.
If your app targets Android 8.0 or higher, you cannot use the manifest to declare a receiver for most implicit broadcasts (broadcasts that don't target your app specifically). You can still use a context-registered receiver when the user is actively using your app.

這裏獻醜翻譯一下:

從Android8.0開始(API級別26),系統對manifest中靜態註冊廣播增強了額外的限制條件。
若是你的應用targets在Android8.0或以上,你將不能在manifest中爲大部分的隱式廣播(那些目標不是專門針對你的應用的廣播)進行聲明。可是當用戶主動使用你的app時,你仍然可使用context-registered(即動態註冊)的方式註冊

從上述文字能夠獲得以下信息:

       1)文中說的是「manifest中不能對大部分隱式廣播進行聲明」,不是說全部隱式廣播都不能註冊。事實上,有不少隱式系統廣播仍然可使用靜態註冊,官方文檔【隱式廣播靜態註冊豁免清單https://developer.android.google.cn/guide/components/broadcast-exceptions

中列出了不受此限制的系統廣播,但同時也特別作了以下說明,以建議開發者避免使用靜態註冊:

Note: Even though these implicit broadcasts still work in the background, you should avoid registering listeners for them.
  2)該限制針對的是manifest中註冊的隱式廣播,顯示廣播不在此列。所謂隱式廣播,就是以intent-filter中的「action」屬性來匹配的廣播;而顯式廣播,是經過廣播接收器包名和類名來直接匹配的廣播。

顯示廣播的註冊代碼,「name」中不須要必須是完整的廣播接收器類路徑。
1 <receiver
2     android:name=".MyReceiver"
3     ......
4 </receiver>
顯示廣播的發送代碼,ComponentName的兩個參數分別是包名和廣播接收器完整路徑。
1 Intent intent = new Intent();
2 intent.setComponent(new ComponentName("com.example.demos","com.example.demos.MyReceiver"));
3 sendBroadcast(intent);
   3)動態註冊廣播不受該限制。
   4)從該版本開始,全部自定義的隱式廣播,靜態註冊後都將無效。這一點,筆者測試時,跨app發送了一段自定義隱式廣播,在Android6.0設備上能夠收到,而在Android8.0設備上卻沒法收到。

    (2)「後臺執行限制」文檔中的說明

        在官方文檔【Android後臺執行限制https://developer.android.google.cn/about/versions/oreo/background#broadcasts】中也作了更詳細的描述,內容比較多且已經翻譯爲了中文,這裏就不整段摘抄了,讀者能夠進入該連接細讀。如下僅提取部分信息:

應用能夠繼續在它們的清單中註冊顯式廣播。 
應用能夠在運行時使用 Context.registerReceiver() 爲任意廣播(無論是隱式仍是顯式)註冊接收器。
須要簽名權限的廣播不受此限制所限。 
在許多狀況下,以前註冊隱式廣播的應用使用 JobScheduler 做業能夠得到相似的功能。

    (3)小結

       以上對Android8.0中廣播的變動描述比較多,爲了方便記憶,這裏總結一下關鍵點:

       1)靜態註冊的隱式廣播,除了「豁免清單」中聲明的系統廣播外,其餘的將再也不生效,包括自定義的廣播。

       2)靜態註冊的顯示廣播、動態註冊的全部廣播、須要簽名權限的廣播,均不受影響。

       3)爲了兼容,之前靜態註冊的隱式廣播可使用 JobScheduler 替代。

       4)最重要的一點:正如官網中所說,儘可能避免使用靜態註冊。我們開發者在後續開發中,直接使用動態註冊吧。

  五、Android9.0中廣播機制變動

      該版本中主要對網絡廣播NETWORK_STATE_CHANGED_ACTION和WIFI相關的廣播所攜帶的隱私數據作了限制,一些重要的隱私數據將沒法經過廣播來獲取,須要經過API中的相關函數來獲得。 詳細能夠參考以下文章:

     【廣播概述https://developer.android.google.cn/guide/components/broadcasts

     【Android Pie 針對全部應用的行爲變動:https://developer.android.google.cn/about/versions/pie/android-9.0-changes-all#privacy-changes-all

 

結語

       本文參考了很多官方文檔的內容,事實上最好的幫助文檔和學習資料就是這套google的官方文檔了。本文沒有深刻研究廣播實現的源碼,只對應用層面以及機制上常遇到的疑問進行了梳理。對源碼的分析,會在日後對廣播的深度使用和分析後再完善。限於筆者的經驗和水平,可能不少地方表述不穩當或者不免有誤的地方,請讀者不吝賜教,謝謝。

相關文章
相關標籤/搜索