1. 程式人生 > >【Android】 BroadcastReceiver詳解

【Android】 BroadcastReceiver詳解

1.Android廣播機制概述

Android廣播分為兩個方面:廣播發送者和廣播接收者,通常情況下,BroadcastReceiver指的就是廣播接收者(廣播接收器)。廣播作為Android元件間的通訊方式,可以使用的場景如下:
1)同一app內部的同一組件內的訊息通訊(單個或多個執行緒之間);

2)同一app內部的不同元件之間的訊息通訊(單個程序);

3)同一app具有多個程序的不同元件之間的訊息通訊;

4)不同app之間的元件之間訊息通訊;

5)Android系統在特定情況下與App之間的訊息通訊。

從實現原理看上,Android中的廣播使用了觀察者模式,基於訊息的釋出/訂閱事件模型。因此,從實現的角度來看,Android中的廣播將廣播的傳送者和接受者極大程度上解耦,使得系統能夠方便整合,更易擴充套件。具體實現流程要點粗略概括如下:

1)廣播接收者BroadcastReceiver通過Binder機制向AMS(Activity Manager Service)進行註冊;

2)廣播發送者通過binder機制向AMS傳送廣播;

3)AMS查詢符合相應條件(IntentFilter/Permission等)的BroadcastReceiver,將廣播發送到BroadcastReceiver(一般情況下是Activity)相應的訊息迴圈佇列中;

4)訊息迴圈執行拿到此廣播,回撥BroadcastReceiver中的onReceive()方法。

 對於不同的廣播型別,以及不同的BroadcastReceiver註冊方式,具體實現上會有不同。但總體流程大致如上。

由此看來,廣播發送者和廣播接收者分別屬於觀察者模式中的訊息釋出和訂閱兩端,AMS屬於中間的處理中心。廣播發送者和廣播接收者的執行是非同步的,發出去的廣播不會關心有無接收者接收,也不確定接收者到底是何時才能接收到。顯然,整體流程與EventBus非常類似。

2.BroadcastReceiver

自定義BroadcastReceiver

自定義廣播接收器需要繼承基類BroadcastReceivre,並實現抽象方法onReceive(context, intent)方法。廣播接收器接收到相應廣播後,會自動回到onReceive(..)方法。預設情況下,廣播接收器也是執行在UI執行緒,因此,onReceive方法中不能執行太耗時的操作。否則將因此ANR。一般情況下,根據實際業務需求,onReceive方法中都會涉及到與其他元件之間的互動,如傳送Notification、啟動service等。
下面程式碼片段是一個簡單的廣播接收器的自定義:

複製程式碼
 1 public class MyBroadcastReceiver extends BroadcastReceiver {
 2     public static final String TAG = "MyBroadcastReceiver";
 3     public static int m = 1;
 4 
 5     @Override
 6     public void onReceive(Context context, Intent intent) {
 7         Log.w(TAG, "intent:" + intent);
 8         String name = intent.getStringExtra("name");
 9         Log.w(TAG, "name:" + name + " m=" + m);
10         m++;
11         
12         Bundle bundle = intent.getExtras();
13         
14     }
15 }
複製程式碼

 

BroadcastReceiver註冊型別

BroadcastReceiver總體上可以分為兩種註冊型別:靜態註冊和動態註冊。

1).靜態註冊:
直接在AndroidManifest.xml檔案中進行註冊。規則如下:

複製程式碼
<receiver android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</receiver>
複製程式碼

其中,需要注意的屬性
android:exported  ——此broadcastReceiver能否接收其他App的發出的廣播,這個屬性預設值有點意思,其預設值是由receiver中有無intent-filter決定的,如果有intent-filter,預設值為true,否則為false。(同樣的,activity/service中的此屬性預設值一樣遵循此規則)同時,需要注意的是,這個值的設定是以application或者application user id為界的,而非程序為界(一個應用中可能含有多個程序);
android:name  —— 此broadcastReceiver類名;
android:permission  ——如果設定,具有相應許可權的廣播發送方傳送的廣播才能被此broadcastReceiver所接收;
android:process  ——broadcastReceiver執行所處的程序。預設為app的程序。可以指定獨立的程序(Android四大基本元件都可以通過此屬性指定自己的獨立程序)

常見的註冊形式有:

複製程式碼
<receiver android:name=".MyBroadcastReceiver" >
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>
複製程式碼

其中,intent-filter由於指定此廣播接收器將用於接收特定的廣播型別。本示例中給出的是用於接收網路狀態改變或開啟啟動時系統自身所發出的廣播。當此App首次啟動時,系統會自動例項化MyBroadcastReceiver,並註冊到系統中。

之前常說:靜態註冊的廣播接收器即使app已經退出,主要有相應的廣播發出,依然可以接收到,但此種描述自Android 3.1開始有可能不再成立,具體分析詳見本文後面部分。

 

2).動態註冊:
動態註冊時,無須在AndroidManifest中註冊<receiver/>元件。直接在程式碼中通過呼叫Context的registerReceiver函式,可以在程式中動態註冊BroadcastReceiver。registerReceiver的定義形式如下:

1 registerReceiver(BroadcastReceiver receiver, IntentFilter filter)
2 registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)

典型的寫法示例如下:

複製程式碼
 1 public class MainActivity extends Activity {
 2     public static final String BROADCAST_ACTION = "com.example.corn";
 3     private BroadcastReceiver mBroadcastReceiver;
 4 
 5     @Override
 6     protected void onCreate(Bundle savedInstanceState) {
 7         super.onCreate(savedInstanceState);
 8         setContentView(R.layout.activity_main);
 9 
10         mBroadcastReceiver = new MyBroadcastReceiver();
11         IntentFilter intentFilter = new IntentFilter();
12         intentFilter.addAction(BROADCAST_ACTION);
13         registerReceiver(mBroadcastReceiver, intentFilter);
14     }
15     
16     @Override
17     protected void onDestroy() {
18         super.onDestroy();
19         unregisterReceiver(mBroadcastReceiver);
20     }
21 
22 }
複製程式碼

注:Android中所有與觀察者模式有關的設計中,一旦涉及到register,必定在相應的時機需要unregister。因此,上例在onDestroy()回到中需要unregisterReceiver(mBroadcastReceiver)。

當此Activity例項化時,會動態將MyBroadcastReceiver註冊到系統中。當此Activity銷燬時,動態註冊的MyBroadcastReceiver將不再接收到相應的廣播。

 

3.廣播型別:

1).Normal Broadcast:普通廣播

此處將普通廣播界定為:開發者自己定義的intent,以context.sendBroadcast_"AsUser"(intent, ...)形式。具體可以使用的方法有:
sendBroadcast(intent)/sendBroadcast(intent, receiverPermission)/sendBroadcastAsUser(intent, userHandler)/sendBroadcastAsUser(intent, userHandler,receiverPermission)。
普通廣播會被註冊了的相應的感興趣(intent-filter匹配)接收,且順序是無序的。如果傳送廣播時有相應的許可權要求,BroadCastReceiver如果想要接收此廣播,也需要有相應的許可權。

2).System Broadcast: 系統廣播

Android系統中內建了多個系統廣播,只要涉及到手機的基本操作,基本上都會發出相應的系統廣播。如:開啟啟動,網路狀態改變,拍照,螢幕關閉與開啟,點亮不足等等。每個系統廣播都具有特定的intent-filter,其中主要包括具體的action,系統廣播發出後,將被相應的BroadcastReceiver接收。系統廣播在系統內部當特定事件發生時,有系統自動發出。

3).Ordered broadcast:有序廣播

有序廣播的有序廣播中的“有序”是針對廣播接收者而言的,指的是傳送出去的廣播被BroadcastReceiver按照先後循序接收。有序廣播的定義過程與普通廣播無異,只是其的主要傳送方式變為:sendOrderedBroadcast(intent, receiverPermission, ...)。

對於有序廣播,其主要特點總結如下:

1>多個具當前已經註冊且有效的BroadcastReceiver接收有序廣播時,是按照先後順序接收的,先後順序判定標準遵循為:將當前系統中所有有效的動態註冊和靜態註冊的BroadcastReceiver按照priority屬性值從大到小排序,對於具有相同的priority的動態廣播和靜態廣播,動態廣播會排在前面。

2>先接收的BroadcastReceiver可以對此有序廣播進行截斷,使後面的BroadcastReceiver不再接收到此廣播,也可以對廣播進行修改,使後面的BroadcastReceiver接收到廣播後解析得到錯誤的引數值。當然,一般情況下,不建議對有序廣播進行此類操作,尤其是針對系統中的有序廣播。

3>另外,接受者可以將處理結果存入資料(可通過setResultExtras(Bundle)方法將資料存入Broadcast),當做Broadcast再傳遞給下一級接收者(可通過程式碼Bundle bundle = getResultExtras(true)獲取上一級傳遞過來的資料)。

4).Sticky Broadcast:粘性廣播(在 android 5.0/api 21中deprecated,不再推薦使用,相應的還有粘性有序廣播,同樣已經deprecated)。

既然已經deprecated,此處不再多做總結。

5).Local Broadcast:App應用內廣播(此處的App應用以App應用程序為界)

 

4.舉個例子(有序廣播):

  1).首先建立兩個BroadcastReceiver。讓第一個receive接收到廣播後中斷。

  MyReceiver.java

複製程式碼
1 public class MyReceiver extends BroadcastReceiver {
2 
3     @Override
4     public void onReceive(Context context, Intent intent) {
5         System.out.println("MyReceiver接受到訊息");
6         abortBroadcast(); //中斷廣播,不會再響比它有優先順序低得廣播再傳播下去了
7     }
8 }
複製程式碼

  MyReceiver1.java

複製程式碼
1 public class MyReceiver1 extends BroadcastReceiver {
2 
3     @Override
4     public void onReceive(Context context, Intent intent) {
5         System.out.println("MyReceiver1接受到訊息");
6     }
7 }
複製程式碼

  2).然後將兩個receiver的action在AndroidManifest.xml檔案中配置成一樣的,並且設定成不同的優先順序,程式碼如下:

複製程式碼
 1      <receiver android:name=".MyReceiver">
 2             <!-- priority優先順序:數字越高優先順序越高 -->
 3             <intent-filter android:priority="5" >
 4                 <action android:name="com.codingblock.myreceiver.intent.action.MyReceiver"/>
 5             </intent-filter>
 6         </receiver>
 7         <receiver android:name=".MyReceiver1">
 8             <intent-filter android:priority="4">
 9                 <action android:name="com.codingblock.myreceiver.intent.action.MyReceiver"/>
10             </intent-filter>
11         </receiver>
複製程式碼

  3).最後在MainActivity中傳送廣播

複製程式碼
 1 public class MainActivity extends Activity {
 2 
 3     Button btn_send_receiver;
 4     @Override
 5     protected void onCreate(Bundle savedInstanceState) {
 6         super.onCreate(savedInstanceState);
 7         setContentView(R.layout.activity_main);
 8         btn_send_receiver = (Button)findViewById(R.id.btn_send_receiver);
 9         btn_send_receiver.setOnClickListener(new OnClickListener() {
10             
11             @Override
12             public void onClick(View v) {
13                 Intent intent = new Intent();
14                 intent.setAction("com.codingblock.myreceiver.intent.action.MyReceiver");
15                 sendOrderedBroadcast(intent, null);//有序廣播需要用sendOrderedBroadcast()方法傳送
16             }
17         });
18     }
19 }
複製程式碼

  佈局檔案只有一個Button,比較簡單,在此就不貼出了。

  通過執行測試就會發現,點擊發送訊息按鈕後只有MyReceiver接收到了訊息,廣播就被中斷了。日誌如下:

 

5.由前文闡述可知,Android中的廣播可以跨程序甚至跨App直接通訊,且註冊是exported對於有intent-filter的情況下預設值是true,由此將可能出現安全隱患如下:

1).其他App可能會針對性的發出與當前App intent-filter相匹配的廣播,由此導致當前App不斷接收到廣播並處理;

2).其他App可以註冊與當前App一致的intent-filter用於接收廣播,獲取廣播具體資訊。

 

無論哪種情形,這些安全隱患都確實是存在的。由此,最常見的增加安全性的方案是:

>>對於同一App內部發送和接收廣播,將exported屬性人為設定成false,使得非本App內部發出的此廣播不被接收;

>>在廣播發送和接收時,都增加上相應的permission,用於許可權驗證;

>>傳送廣播時,指定特定廣播接收器所在的包名,具體是通過intent.setPackage(packageName)指定在,這樣此廣播將只會傳送到此包中的App內與之相匹配的有效廣播接收器中。

 

App應用內廣播可以理解成一種區域性廣播的形式,廣播的傳送者和接收者都同屬於一個App。實際的業務需求中,App應用內廣播確實可能需要用到。同時,之所以使用應用內廣播時,而不是使用全域性廣播的形式,更多的考慮到的是Android廣播機制中的安全性問題。

相比於全域性廣播,App應用內廣播優勢體現在:

1.安全性更高;

2.更加高效。

為此,Android v4相容包中給出了封裝好的LocalBroadcastManager類,用於統一處理App應用內的廣播問題,使用方式上與通常的全域性廣播幾乎相同,只是註冊/取消註冊廣播接收器和傳送廣播時將主調context變成了LocalBroadcastManager的單一例項。

 

程式碼片段如下:

複製程式碼
 1 //registerReceiver(mBroadcastReceiver, intentFilter);
 2 //註冊應用內廣播接收器
 3 localBroadcastManager = LocalBroadcastManager.getInstance(this);
 4 localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
 5         
 6 //unregisterReceiver(mBroadcastReceiver);
 7 //取消註冊應用內廣播接收器
 8 localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
 9 
10 Intent intent = new Intent();
11 intent.setAction(BROADCAST_ACTION);
12 intent.putExtra("name", "qqyumidi");
13 //sendBroadcast(intent);
14 //傳送應用內廣播
15 localBroadcastManager.sendBroadcast(intent);
複製程式碼

6. 不同註冊方式的廣播接收器回撥onReceive(context, intent)中的context具體型別

1).對於靜態註冊的ContextReceiver,回撥onReceive(context, intent)中的context具體指的是ReceiverRestrictedContext;

2).對於全域性廣播的動態註冊的ContextReceiver,回撥onReceive(context, intent)中的context具體指的是Activity Context;

3).對於通過LocalBroadcastManager動態註冊的ContextReceiver,回撥onReceive(context, intent)中的context具體指的是Application Context。

注:對於LocalBroadcastManager方式傳送的應用內廣播,只能通過LocalBroadcastManager動態註冊的ContextReceiver才有可能接收到(靜態註冊或其他方式動態註冊的ContextReceiver是接收不到的)。

 

7.不同Android API版本中廣播機制相關API重要變遷

1).Android5.0/API level 21開始粘滯廣播和有序粘滯廣播過期,以後不再建議使用;

2).”靜態註冊的廣播接收器即使app已經退出,主要有相應的廣播發出,依然可以接收到,但此種描述自Android 3.1開始有可能不再成立“

Android 3.1開始系統在Intent與廣播相關的flag增加了引數,分別是FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES。

FLAG_INCLUDE_STOPPED_PACKAGES:包含已經停止的包(停止:即包所在的程序已經退出)

FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已經停止的包

主要原因如下:

自Android3.1開始,系統本身則增加了對所有app當前是否處於執行狀態的跟蹤。在傳送廣播時,不管是什麼廣播型別,系統預設直接增加了值為FLAG_EXCLUDE_STOPPED_PACKAGES的flag,導致即使是靜態註冊的廣播接收器,對於其所在程序已經退出的app,同樣無法接收到廣播。

詳情參加Android官方文件:http://developer.android.com/about/versions/android-3.1.html#launchcontrols

由此,對於系統廣播,由於是系統內部直接發出,無法更改此intent flag值,因此,3.1開始對於靜態註冊的接收系統廣播的BroadcastReceiver,如果App程序已經退出,將不能接收到廣播。

但是對於自定義的廣播,可以通過複寫此flag為FLAG_INCLUDE_STOPPED_PACKAGES,使得靜態註冊的BroadcastReceiver,即使所在App程序已經退出,也能能接收到廣播,並會啟動應用程序,但此時的BroadcastReceiver是重新新建的。

1 Intent intent = new Intent();
2 intent.setAction(BROADCAST_ACTION);
3 intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
4 intent.putExtra("name", "qqyumidi");
5 sendBroadcast(intent);

注1:對於動態註冊型別的BroadcastReceiver,由於此註冊和取消註冊實在其他元件(如Activity)中進行,因此,不受此改變影響。

注2:在3.1以前,相信不少app可能通過靜態註冊方式監聽各種系統廣播,以此進行一些業務上的處理(如即時app已經退出,仍然能接收到,可以啟動service等..),3.1後,靜態註冊接受廣播方式的改變,將直接導致此類方案不再可行。於是,通過將Service與App本身設定成不同的程序已經成為實現此類需求的可行替代方案。

參考連結:

https://www.cnblogs.com/lwbqqyumidi/p/4168017.html

http://www.cnblogs.com/lwbqqyumidi/p/4041455.html