Android之BroadcastReceiver

lvxiangan發表於2018-12-05

廣播接收器用於響應來自其他應用程式或者系統的廣播訊息。這些訊息有時被稱為事件或者意圖(Intent)
使用了觀察者模式:基於訊息的釋出 / 訂閱事件模型


一、概述 

broadcastReceiver  顧名思義,廣播接收者,他是用來接收來自系統和應用中的廣播。
在android系統中,廣播體現在方方面面,例如

  • 開機完成後,系統會產生一條廣播,接收到這條廣播就能實現開機啟動服務的功能;
  • 當網路狀態改變時,系統會產生一條廣播,接收到這條廣播就能及時的作出提示和儲存資料等操作;
  • 當電池電量改變時,系統會產生一條廣播,接收到這條廣播就能在電量低時告知使用者及時儲存進度,等等。

廣播之間資訊的傳遞是通過Intent物件來傳遞的;簡單地說,Intent呼叫分為顯示呼叫和隱式呼叫,由於這裡能通知到所有的接收者,肯定不能利用顯式呼叫,只有利用隱式呼叫Intent物件了。這裡說的隱式呼叫並不是真正意義上的Intent隱式呼叫,因為Intent隱式呼叫,當出現很多匹配應用時,會以列表的形式提示使用者選擇一個啟動,而這裡不同的地方在於,當有很多匹配項時,會給所有的匹配項都發一個訊息,我說隱式呼叫,只是方便理解。
 

生命週期:

  • 生命週期只有十秒左右,如果在onReceive()方法內做超過十秒內的事情,會彈出ANR(application no response)的對話方塊。所以在BroadcastReceiver中不能做一些比較耗時的操作。
  • 每次廣播到來時,會重建BroadcastReceiver物件,並且呼叫onReceive()方法,執行完以後,該物件即被銷燬。
  • 如果需要完成一項比較耗時的工作,應該通過傳送Intent給service,由service來完成。這裡不能使用子執行緒來解決。因為生命週期太短,子執行緒可能還沒有結束,BroadcastReceiver就先結束了。BR一旦結束,裡面的所有程式很容易在系統需要記憶體的時候優先殺死。因為它屬於空程式(沒有任何活動元件的程式)。

 

二、註冊相關

1、靜態註冊例項程式 
首先,我們演示一下建立BroadcastReceiver,並讓這個BroadcastReceiver能夠根據我們的需要來執行。

1·構造一個接收器
 

自定義廣播接收者BroadcastReceiver

  • 繼承BroadcastReceivre基類
  • 必須複寫抽象方法onReceive()方法
  1. 廣播接收器接收到相應廣播後,會自動回撥 onReceive()方法
  2. 一般情況下,onReceive方法會涉及 與 其他元件之間的互動,如傳送Notification、啟動Service
  3. 預設情況下,廣播接收器執行在 UI執行緒,因此,onReceive()方法不能執行耗時操作,否則將導致ANR

建立一個BroadcastReceiver物件,我們需要繼承android.content.BroadcastReceiver,並實現其onReceive方法,在這個函式中進行處理即可。下面我們建立的名為MyReceiver的廣播接收者。
    
public class MyReceiver extends BroadcastReceiver {  
    private static final String TAG = "MyReceiver";  
      
    @Override  
    public void onReceive(Context context, Intent intent) {  
        String msg = intent.getStringExtra("msg");  
        Log.i(TAG, msg);  
    }  
}  

我們前面說了,廣播的傳遞是靠Intent的,OnReceive的第二個引數,就是廣播傳過來的Intent,我們在傳送廣播的時候,會利用putStringExtra放進去一個標識為msg的字串,所以我們在這裡可以將這個字串取出來。

 

廣播接收器註冊

註冊的方式分為兩種:靜態註冊、動態註冊
靜態註冊:就是利用xml來註冊。
動態註冊:利用程式碼來註冊。

兩者區別主要體現的接收:
靜態註冊的程式,無論該程式是否啟動,當廣播到來時都會接收並處理。
動態註冊的程式,只有在程式執行時才會收到廣播訊息,程式不執行它就收不到了。

5.2.1 靜態註冊

  • 註冊方式:在AndroidManifest.xml裡通過<receive>標籤宣告
  • 屬性說明:
<receiver 
    android:enabled=["true" | "false"]
    //此broadcastReceiver能否接收其他App的發出的廣播
  
    //預設值是由receiver中有無intent-filter決定的:有intent-filter,值為true,否則為false
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:label="string resource"
    
    //繼承BroadcastReceiver子類的類名
    android:name=".mBroadcastReceiver"

    //具有相應許可權的廣播傳送者傳送的廣播才能被此BroadcastReceiver所接收;
    android:permission="string"

    //BroadcastReceiver執行所處的程式
    //預設為app的程式,可以指定獨立的程式
    //注:Android四大基本元件都可以通過此屬性指定自己的獨立程式
    android:process="string" >

    //用於指定此廣播接收器將接收的廣播型別
    //本示例中給出的是用於接收網路狀態改變時發出的廣播
    <intent-filter>
         <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>
  • 註冊示例
<receiver 
    //此廣播接收者類是mBroadcastReceiver
    android:name=".mBroadcastReceiver" >
    //用於接收網路狀態改變時發出的廣播
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>

當此 App首次啟動時,系統會自動例項化mBroadcastReceiver類,並註冊到系統中。

5.2.2 動態註冊

  • 註冊方式:在程式碼中呼叫Context.registerReceiver()方法,具體程式碼如下:

// 選擇在Activity生命週期方法中的onResume()中註冊
@Override
  protected void onResume(){
      super.onResume();

    // 1. 例項化BroadcastReceiver子類 &  IntentFilter
     mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
     IntentFilter intentFilter = new IntentFilter();

    // 2. 設定接收廣播的型別
    intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);

    // 3. 動態註冊:呼叫Context的registerReceiver()方法
     registerReceiver(mBroadcastReceiver, intentFilter);
 }


// 註冊廣播後,要在相應位置記得銷燬廣播
// 即在onPause() 中unregisterReceiver(mBroadcastReceiver)
// 當此Activity例項化時,會動態將MyBroadcastReceiver註冊到系統中
// 當此Activity銷燬時,動態註冊的MyBroadcastReceiver將不再接收到相應的廣播。
 @Override
 protected void onPause() {
     super.onPause();
      //銷燬在onResume()方法中的廣播
     unregisterReceiver(mBroadcastReceiver);
     }
}

特別注意

  • 動態廣播最好在Activity的 onResume()註冊、onPause()登出。
  • 原因:Activity生命週期的方法是成對出現的:
    onCreate() & onDestory()
    onStart() & onStop()
    onResume() & onPause()

因為onPause()在Activity關閉前一定會被執行,從而保證廣播一定會被登出,從而防止記憶體洩露。

  1. 不在其他週期註冊、登出是因為:
    當系統因為記憶體不足(優先順序更高的應用需要記憶體)要回收Activity佔用的資源時,Activity在執行完onPause()方法後就會被銷燬,有些生命週期方法onStop(),onDestory()就不會執行。當再回到此Activity時,是從onCreate方法開始執行。
  2. 假設我們將廣播的登出放在onStop(),onDestory()方法裡的話,有可能在Activity被銷燬後還未執行onStop(),onDestory()方法,即廣播仍還未登出,從而導致記憶體洩露。
  3. 但是,onPause()一定會被執行,從而保證了廣播在App死亡前一定會被登出,從而防止記憶體洩露

5.2.3 兩種註冊方式的區別




5.3 廣播傳送者向AMS傳送廣播

5.3.1 廣播的傳送

  • 廣播 是 用”意圖(Intent“標識
  • 定義廣播的本質 = 定義廣播所具備的“意圖(Intent)”
  • 廣播傳送 = 廣播傳送者 將此廣播的“意圖(Intent)”通過sendBroadcast()方法傳送出去

5.3.2 廣播的型別

廣播的型別主要分為5類:

  • 普通廣播(Normal Broadcast
  • 系統廣播(System Broadcast
  • 有序廣播(Ordered Broadcast
  • 粘性廣播(Sticky Broadcast)// 擯棄
  • App應用內廣播(Local Broadcast

具體說明如下:
1. 普通廣播(Normal Broadcast)
即 開發者自身定義 intent的廣播(最常用)。傳送廣播使用如下:

Intent intent = new Intent();
//對應BroadcastReceiver中intentFilter的action
intent.setAction(BROADCAST_ACTION);
//傳送廣播
sendBroadcast(intent);
  • 若被註冊了的廣播接收者中註冊時intentFilteraction與上述匹配,則會接收此廣播(即進行回撥onReceive())。如下mBroadcastReceiver則會接收上述廣播
<receiver 
    //此廣播接收者類是mBroadcastReceiver
    android:name=".mBroadcastReceiver" >
    //用於接收網路狀態改變時發出的廣播
    <intent-filter>
        <action android:name="BROADCAST_ACTION" />
    </intent-filter>
</receiver>
  • 若傳送廣播有相應許可權,那麼廣播接收者也需要相應許可權

2. 系統廣播(System Broadcast)

  • Android中內建了多個系統廣播:只要涉及到手機的基本操作(如開機、網路狀態變化、拍照等等),都會發出相應的廣播
  • 每個廣播都有特定的Intent - Filter(包括具體的action),Android系統廣播action如下:
系統操作 action
監聽網路變化 android.net.conn.CONNECTIVITY_CHANGE
關閉或開啟飛航模式 Intent.ACTION_AIRPLANE_MODE_CHANGED
充電時或電量發生變化 Intent.ACTION_BATTERY_CHANGED
電池電量低 Intent.ACTION_BATTERY_LOW
電池電量充足(即從電量低變化到飽滿時會發出廣播 Intent.ACTION_BATTERY_OKAY
系統啟動完成後(僅廣播一次) Intent.ACTION_BOOT_COMPLETED
按下照相時的拍照按鍵(硬體按鍵)時 Intent.ACTION_CAMERA_BUTTON
螢幕鎖屏 Intent.ACTION_CLOSE_SYSTEM_DIALOGS
裝置當前設定被改變時(介面語言、裝置方向等) Intent.ACTION_CONFIGURATION_CHANGED
插入耳機時 Intent.ACTION_HEADSET_PLUG
未正確移除SD卡但已取出來時(正確移除方法:設定--SD卡和裝置記憶體--解除安裝SD卡) Intent.ACTION_MEDIA_BAD_REMOVAL
插入外部儲存裝置(如SD卡) Intent.ACTION_MEDIA_CHECKING
成功安裝APK Intent.ACTION_PACKAGE_ADDED
成功刪除APK Intent.ACTION_PACKAGE_REMOVED
重啟裝置 Intent.ACTION_REBOOT
螢幕被關閉 Intent.ACTION_SCREEN_OFF
螢幕被開啟 Intent.ACTION_SCREEN_ON
關閉系統時 Intent.ACTION_SHUTDOWN
重啟裝置 Intent.ACTION_REBOOT

注:當使用系統廣播時,只需要在註冊廣播接收者時定義相關的action即可,並不需要手動傳送廣播,當系統有相關操作時會自動進行系統廣播

3. 有序廣播(Ordered Broadcast)

  • 定義
    傳送出去的廣播被廣播接收者按照先後順序接收

有序是針對廣播接收者而言的

  • 廣播接受者接收廣播的順序規則(同時面向靜態和動態註冊的廣播接受者)

    1. 按照Priority屬性值從大-小排序;
    2. Priority屬性相同者,動態註冊的廣播優先;
  • 特點

    1. 接收廣播按順序接收
    2. 先接收的廣播接收者可以對廣播進行截斷,即後接收的廣播接收者不再接收到此廣播;
    3. 先接收的廣播接收者可以對廣播進行修改,那麼後接收的廣播接收者將接收到被修改後的廣播
  • 具體使用
    有序廣播的使用過程與普通廣播非常類似,差異僅在於廣播的傳送方式:

sendOrderedBroadcast(intent);

4. App應用內廣播(Local Broadcast)

  • 背景
    Android中的廣播可以跨App直接通訊(exported對於有intent-filter情況下預設值為true)

  • 衝突
    可能出現的問題:

    • 其他App針對性發出與當前App intent-filter相匹配的廣播,由此導致當前App不斷接收廣播並處理;
    • 其他App註冊與當前App一致的intent-filter用於接收廣播,獲取廣播具體資訊;
      即會出現安全性 & 效率性的問題。
  • 解決方案
    使用App應用內廣播(Local Broadcast)

  1. App應用內廣播可理解為一種區域性廣播,廣播的傳送者和接收者都同屬於一個App
  2. 相比於全域性廣播(普通廣播),App應用內廣播優勢體現在:安全性高 & 效率高
  • 具體使用1 - 將全域性廣播設定成區域性廣播

    1. 註冊廣播時將exported屬性設定為false,使得非本App內部發出的此廣播不被接收;
    2. 在廣播傳送和接收時,增設相應許可權permission,用於許可權驗證;
    3. 傳送廣播時指定該廣播接收器所在的包名,此廣播將只會傳送到此包中的App內與之相匹配的有效廣播接收器中。

    通過intent.setPackage(packageName)指定報名

  • 具體使用2 - 使用封裝好的LocalBroadcastManager類
    使用方式上與全域性廣播幾乎相同,只是註冊/取消註冊廣播接收器和傳送廣播時將引數的context變成了LocalBroadcastManager的單一例項

注:對於LocalBroadcastManager方式傳送的應用內廣播,只能通過LocalBroadcastManager動態註冊,不能靜態註冊

//註冊應用內廣播接收器
//步驟1:例項化BroadcastReceiver子類 & IntentFilter mBroadcastReceiver 
mBroadcastReceiver = new mBroadcastReceiver(); 
IntentFilter intentFilter = new IntentFilter(); 

//步驟2:例項化LocalBroadcastManager的例項
localBroadcastManager = LocalBroadcastManager.getInstance(this);

//步驟3:設定接收廣播的型別 
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);

//步驟4:呼叫LocalBroadcastManager單一例項的registerReceiver()方法進行動態註冊 
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);

//取消註冊應用內廣播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);

//傳送應用內廣播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);

5. 粘性廣播(Sticky Broadcast)
由於在Android5.0 & API 21中已經失效,所以不建議使用,在這裡也不作過多的總結。


6. 特別注意

對於不同註冊方式的廣播接收器回撥OnReceive(Context context,Intent intent)中的context返回值是不一樣的:

  • 對於靜態註冊(全域性+應用內廣播),回撥onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
  • 對於全域性廣播的動態註冊,回撥onReceive(context, intent)中的context返回值是:Activity Context;
  • 對於應用內廣播的動態註冊(LocalBroadcastManager方式),回撥onReceive(context, intent)中的context返回值是:Application Context。
  • 對於應用內廣播的動態註冊(非LocalBroadcastManager方式),回撥onReceive(context, intent)中的context返回值是:Activity Context;


 

 

三、普通廣播與有序廣播

普通廣播 是指大家等級都是一樣的,當廣播到來時,都能一塊接收到,並沒有接受的先後順序。對於多個接收者來說是完全非同步的,通常每個接收者都無需等待即可以接收到廣播,接收者相互之間不會有影響。所以一個接收者是沒有辦法通過在onReceive方法新增程式碼abortBroadcast();  試圖終止廣播。接收者無法終止廣播,即無法阻止其他接收者的接收動作。

 

 

 

LocalBroadcastManager

特點

  1. 廣播資料在本應用範圍內傳播。
  2. 不用擔心別的應用偽造廣播。
  3. 比傳送全域性廣播更高效。

用法

  1. 加入 Android v4 的相容包
android.support.v4.content.LocalBroadcastManager
  1. 寫一個 Receiver
BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override    
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("test")) {
            String message = intent.getStringExtra("message");
            Log.d("receiver", "Got message: " + message);
        }
    }
};
  1. 註冊 Receiver
IntentFilter filter = new IntentFilter(MyIntentService.ACTION);
filter.addAction("test");
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
  1. 傳送 Broadcast
findViewById(R.id.button_send).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent("test");
        intent.putExtra("message", "This is a test!");
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }
});
  1. 登出 Receiver
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);

 



連結:https://www.jianshu.com/p/ca3d87a4cdf3
連結:https://www.jianshu.com/p/b18b2d781a0b

相關文章