淺談Android Broadcast

前言:本文所寫的是博主的個人見解,如有錯誤或者不恰當之處,歡迎私信博主,加以改正! 原文鏈接demo鏈接

廣播簡述

Android應用程序可以發送或者接收來自 Android 系統和其他 Android 應用程序的廣播消息,類似于發布訂閱設計模式。當感興趣的事件發生時,這些廣播被發送。例如,Android 系統在各種系統事件發生時發送廣播,比如系統啟動或者設備開始充電等。應用程序還可以發送自定義的廣播,比如通知其他應用可能感興趣的內容(例如,一些新數據已被下載)。

應用程序可以注冊接收特定的廣播,當發送廣播時,系統自動將廣播路由到訂閱該特定類型廣播的應用程序。一般來說,廣播可以用作跨應用程序和正常用戶流之外的消息傳遞系統。

系統廣播

當發生各種系統事件時,系統會自動發送廣播,例如系統切換飛行模式時,系統廣播被發送到所有接收訂閱事件的應用程序。

廣播消息本身被包裹在一個 Intent 對象的動作字符串標識(例如 android.intent.action.AIRPLANE_MODE )。這個 Intent 還可以包括附加到其額外字段中的附加信息。比如,"飛行模式" 的 Intent 包括一個 boolean 額外指示是否 "飛行模式"。

系統廣播行為的完整列表,請參閱 Android SDK 中的 BROADCAST_ACTIONS.TXT 文件。每個廣播行為都有與之關聯的常量字段,例如常量 ACTION_AIRPLANE_MODE_CHANGED 的值為 android.intent.action.AIRPLANE_MODE,每個廣播動作的文檔都可以在其相關聯的常量字段中獲得。

注:系統廣播更改
Android 7 和更高的不再發送以下系統廣播。這種優化影響所有的應用程序,不僅那些針對Android 7。

  • ACTION_NEW_PICTURE
  • ACTION_NEW_VIDEO
    針對 Android 7 的應用程序(API級別24)和更高的必須登記以下的廣播代碼注冊廣播接收器(BroadcastReceiver ,
    IntentFilter)。在清單中聲明接收器不起作用。
  • CONNECTIVITY_ACTION

注冊接收廣播

應用程序可以通過兩種方式接收廣播:通過清單聲明的接收者和上下文注冊的接收者

  1. 清單聲明的接收器(靜態注冊)

在清單中聲明廣播接收者,可以通過下面的步驟:
(1)在應用程序的清單中指定 <receiver> 元素

<receiver
       android:name=".DemoBroadcastReceiver"
       android:exported="true">
     <intent-filter>
         <action android:name="android.intent.action.BOOT_COMPLETED"/>
         <action android:name="android.intent.action.INPUT_METHOD_CHANGED"/>
     </intent-filter>
         
</receiver>

intent-filter (意圖過濾器) 指定你的接收者訂閱的廣播操作。

(2)繼承 BroadcastReceiver 并實現 onReceive(Context,Intent) 方法,請看下面的示例:

public class DemoBroadcastReceiver extends BroadcastReceiver {
     private static final String TAG = "DemoBroadcastReceiver";
     
     @Override
     public void onReceive(Context context, Intent intent) {
         StringBuilder sb = new StringBuilder();
         sb.append("Action: " + intent.getAction() + "\n");
         sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
         String log = sb.toString();
         Log.d(TAG, log);
         Toast.makeText(context, log, Toast.LENGTH_LONG).show();
    }
    
}

當應用程序安裝時,系統包管理器注冊接受器,然后接收器將成為你的應用程序的單獨入口,這意味著如果應用程序當前未運行,系統可以啟動你的應用程序并發送廣播。

系統將創建一個新的 BroadcastReceiver 組件對象來處理它接收的每個廣播。這個對象僅對調用 onReceive(Context,Intent)的時候有效。當你在代碼從該方法返回,系統將認為組件不再活動。

  1. context 注冊接收者(動態注冊)

    用context注冊接收器,可以通過以下幾個步驟:

    (1) 創建 BroadcastReceiver 并實例化

     BroadcastReceiver broadcastReceiver = new DemoBroadcastReceiver();
    

    (2) 創建一個 IntentFilter 并調用 registerReceiver( BroadcastReceiver , IntentFilter ) 來注冊接收器

     IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
     intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
     this.registerReceiver(broadcastReceiver,intentFilter);
    

    注意:要注冊本地廣播,請改用 LocalBroadcastManager.registerReceiver( BroadcastReceiver , IntentFilter )。

    如果context注冊有效時,context注冊的接收者就可以接收廣播。例如你在 Activity 的 context 中注冊,只要 Activity 沒有被銷毀,就會收到廣播。如果是使用 ApplicationContext 進行注冊,只要程序在運行就可以收到廣播。

    (3)停止接收廣播時,可以調用 unregisterReceiver( android.content.BroadcastReceiver ) 進行注銷。當你不需要這個廣播時或者 context 不再有效時,一定要注銷接收器。·

    注意你在哪里注冊和注銷的接收者,比如,你在 Activity 的 onCreate( Bundle ) 中注冊一個接收者,則應該在 onDestory() 中注銷它,防止接收器泄露;如果你在 onResume() 中注冊一個接收器,你應該在 onPause() 中注銷,以防止多次注冊( 如果你不想在 Activity 暫停時接收廣播,可以做減少不必要的系統開銷 )。不要在 onSaveInstanceState( Bundle ) 中注銷,如果用戶在歷史棧中移回,則不會調用這個方法。

  2. 進程狀態的影響

BroadcastReceiver 的狀態(無論是否在運行)會影響其包含進程的狀態,這也可能反過來影響其被系統殺死的可能性。例如,當進程執行一個接收器時(在其 onReceive() 方法中運行代碼),它被認為是一個前臺進程,除非內存壓力極度大的時候,否則系統將保持其進程的運行。

然而,一旦你的代碼從 onREceive() 方法中返回, BroadcastReceiver 就不在活動了。接收器的宿主進程變得和運行在這個進程中的其它應用程序組件一樣重要。如果該進程只承載一個 manifest-declared 接收器(應用程序從來沒有或者最近沒有跟用戶進行交互),然后從 onReceive() 返回時,系統將認為它的進程是一個低優先級的進程,很可能會殺死它,從而施放資源,用于其它優先級高的進程。

因此,不應該在廣播接收器中執行耗時的后臺線程,在執行 onReceive() 后,系統隨時可以殺死進程以回收內存,這樣做會終止在該進程中運行生成的線程。為了避免這種情況的發生,應該調用 goAsync() 方法(如果你需要更多的時間來處理后臺線程中的廣播)或使用 JobScheduler 從接收器調度 JobService ,則系統會知道該進程繼續執行工作。

下面的這段代碼顯示了一個 BroadcastReceiver 使用 goAsync() 來標記,需要更多的時間來完成當前的操作,并在完成后再 onReceiver() 。如果你在 onReceive() 中需要的時間很長,導致 UI 線程錯過了一幀( 16 ms ),可以使用上面的方法,在后臺線程繼續工作。

public class DemoBroadcastReceiver extends BroadcastReceiver {

    private static final String TAG = "DemoBroadcastReceiver";

    @Override
    public void onReceive(Context context, final Intent intent) {
        final PendingResult pendingResult = goAsync();

        AsyncTask<String, Integer, String> asyncTask = new AsyncTask<String, Integer, String>() {
            @Override
            protected String doInBackground(String... params) {
                StringBuilder sb = new StringBuilder();
                sb.append("Action: " + intent.getAction() + "\n");
                sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
                String log = sb.toString();
                Log.d(TAG, log);

                // 必須調用 finish() ,以便 BroadcastReceiver 可以被回收。
                pendingResult.finish();

                return log;
            }
        };

        asyncTask.execute();
    }

}

發送廣播

Android 提供了三種方法供應用發送廣播:

  • sendOrderedBroadcast( Intent , String ) 方法將廣播發送到一個接收器。隨著每個接收器依次執行,它可以將結果傳遞到下一個接收器,也可以完全中止廣播,使它不會傳給其他接收器。Android 可以控制運行的命令接收器:匹配 intent-filter 的優先級屬性;具備相同優先級的接收器,可以任意順序運行

  • sendBroadcast( Intent ) 方法向所有接收器發送未確定順序的廣播,這被成為普通廣播。這種廣播更有效率,但是意味著接收器不能讀取來自其他接收器的結果,傳播從廣播接收的數據,或者中止廣播

  • LocalBroadcastManager.sendBroadcast 方法將廣播發送到同個應用程序中的接收者。如果你不需要發送跨應用廣播,請使用本地廣播。實施 效率更高(無需進行跨進程通信),不需要擔心其他可以接收或者發送廣播的應用程序之間的任何安全問題

下面的代碼片段演示了如何通過創建 Intent 并 調用sendBroadcast( Intent ) 來發送廣播:

 Intent intent = new Intent();
 intent.setAction("com.passershowe.broadcast.NOTIFICATION_DEMO");
 intent.putExtra("data","Send a notice");
 sendBroadcast(intent);

廣播消息被包裝在 Intent 對象中,Intent 動作字符串必須提供應用程序的 java 包名和唯一的廣播事件標識。你可以使用 putExtra( String , Bundle ) 附加信息到 Intent 中,還可以通過 Intent 調用 setPackage( String ) 將廣播限制在同一組織中的一組應用程序。

注意:雖然 Intent 用于發送廣播和使用 startActivity( Intent ) 啟動 Activity ,但這些行為是完全無關的。廣播接收器無法看到或者捕獲用于開始 Activity 的 Intent ;同樣,當你是廣播 Intent 時,是無法找到或啟動 Activity 。

廣播權限約束

Permissions(權限) 允許你將廣播限制為持有一定權限的應用程序集。可以對廣播的發送方或接收方強制執行限制。

  1. 發送權限
    當你調用 sendBroadcast( Intent , String ) 或 sendOrderedBroadcast( Intent , String , BroadcastReceiver , Handle , int , String , Bundle ) 時,可以指定權限參數。只有通過其清單中的標簽請求許可的接收器才能接收廣播。例如,以下代碼發送廣播:
    sendBroadcast(new Intent("com.passershowe.broadcast.NOTIFY"),      Manifest.permission.SEND_SMS);
    

要接收廣播,接收應用程序必須請求如下所示的權限:

<uses-permission android:name="android.permission.SEND_SMS"/>

你可以指定一個現有的系統權限(如SEND_SMS)或使用 <permission>元素定義自定義權限

注意:當你的應用程序注冊了自定義權限,你必須在使用這個權限前,安裝它

  1. 接收權限
    如果你在注冊廣播接收器(或者使用 registerReceiver( BroadcastReceiver , IntentFilter , String , Handle ) 或清單中的 <receiver> 標簽)中指定權限參數,則只有使用 <uses-permission> 標簽在其清單中(隨后被授予許可是危險的)可以向接收者發送 Intent

    例如,假設你的接收應用程序具有清單聲明的接收器,如下所示:

    <receiver android:name=".DemoBroadcastReceiver"
                   android:permission="android.permission.SEND_SMS">
            <intent-filter>
                <action android:name="android.intent.action.AIRPLANE_MODE"/>
            </intent-filter>
    </receiver>
    

    或者你的接收應用程序具有 context 注冊的接收器,如下所示:

    IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );
    

    然后,為了能夠向這些接收者發送廣播,發送應用程序必須如下所示請求許可:

    <uses-permission android:name="android.permission.SEND_SMS"/>
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容