Android廣播的那些事兒

Android App可以接收來自系統和其他App的廣播消息,也可以向它們發送廣播消息,比較類似於“發佈-訂閱”的設計模式,本文主要介紹廣播的類型,如何註冊廣播,如何發送廣播以及使用廣播需要注意的一些事兒。

I. 廣播的分類

  1. 無序廣播
    沒有順序的廣播,廣播的接收方沒有嚴格的順序可言,不可中斷。

  2. 有序廣播
    在註冊時可指定優先級,優先級高的廣播接收者優先收到廣播,優先級以一個整數來標識,數值越大優先級越高。可中斷,可再修飾。

  3. 粘滯廣播
    發出的廣播會滯留,註冊時間可晚於發送時間,其他功能與無序廣播相同。

II. 註冊廣播

編寫一個廣播接收者,通常是繼承BroadcastReceiver並重寫onReceive(Context,Intent)方法。

public class MyReceiver extends BroadcastReceiver {

    private static final String TAG = "MyReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: do somethings
        // 這個方法運行在主線程下
        String action = intent.getAction();
        Log.d(TAG, "onReceive: " + action);
    }
}

編寫完廣播接收者後,就需要進行註冊(訂閱),告訴系統這個廣播接收者對哪些廣播感興趣。註冊的方式有靜態註冊和動態註冊兩種:

  • 在AndroidManifest.xml文件中聲明(靜態註冊)
<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="jdqm.intent.action.TEST"/>
    </intent-filter>
</receiver>
  • 通過Java代碼註冊(動態註冊)
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.BOOT_COMPLETED");
intentFilter.addAction("jdqm.intent.action.TEST");
registerReceiver(myReceiver, intentFilter);

通過動態註冊的廣播接收者,在宿主(註冊時所使用的Context)的生命週期期間都是有效的。當然你也可以在適當的時間調用unregisterReceiver(BroadcastReceiver)來解除註冊,這個“適當”取決於具體的業務需求。例如使用Activity的Context在onCreate(Bundle) 中註冊的一個廣播接收者,可以在onDestory()方法回調時解除註冊來防止廣播接收者泄漏。原則:不重複註冊,不泄露。

以上註冊的廣播接收者對 android.intent.action.BOOT_COMPLETED 和 jdqm.intent.action.TEST 這兩種action的廣播感興趣,後者是自定義的廣播,前者是開機完成時由系統發出(通常自啓動的應用會註冊這個廣播),但註冊這個廣播須要以下權限:

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

III. 發送廣播

  1. 發送無序廣播
Intent intent = new Intent("jdqm.intent.action.TEST");
sendBroadcast(intent);
  1. 發送有序廣播
Intent intent = new Intent("jdqm.intent.action.TEST");
//第二個參數是權限
sendOrderedBroadcast(intent, null);
  1. 發送本地廣播
Intent intent = new Intent("jdqm.intent.action.TEST");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

本地廣播只有本應用內通過LocalBroadcastManager.getInstance(this).registerReceiver方法註冊的廣播接收者能收到,具有更高的安全性,效率也更高(不用跨進程通信)。

  1. 發送粘滯廣播
Intent intent = new Intent("jdqm.intent.action.TEST");
sendStickyBroadcast(intent);

這種類型廣播在Android6.0中已經被標記被過時, 它有不安全(任何App都能訪問), 沒有保護 (任何App都能修改)等問題。另外發送這種廣播需要以下權限

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

IV. 接收順序

  1. 對於無序廣播,動態註冊的廣播接收者會先收到,可以從源碼中得到理論支撐。在BroadcastQueue類中有兩個集合
//存儲所有動態註冊的無序廣播接受者
final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();

//存儲所有靜態註冊和動態註冊的有序廣播接收者
final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();

然後在其processNextBroadcast(boolean fromMsg)方法中,首先是處理了mParallelBroadcasts集合。
2. 對於有序廣播,優先級高的接受者先收到,如果優先級相同,順序就是不確定的。先收到的接收者可以調用abortBroadcast()來中斷此廣播,後續優先級較低的接受者將無法收到。除了中斷還可以調用setResultXxx()方法來往廣播添加數據,後續的接收者可以讀取這些數據。

IV. 安全性與實踐

  1. 如果你的廣播不需要發送給本應用以外的組件,使用LocalBroadcastManager來發送廣播,這樣安全性和效率都比較高
  2. 靜態註冊有可能造成大量的App啓動,這將會影響系統的性能,所以儘量使用動態註冊來替代靜態註冊。這一點Android系統就做出了很好的示範,比如 CONNECTIVITY_ACTION 這個廣播只發送給動態註冊的廣播接收者。

  3. 不在廣播的Intent中包含敏感的信息,因爲只要註冊了這個廣播就能讀取到這些信息。你可以通過以下3中方式來獲得一定的安全性。

    • 通過使用權限來發送廣播,這樣只有聲明瞭該權限的應用才能收到廣播。但是你很難確保你的權限不被泄漏。
    • Android4.0及以上版本,在發送廣播的時候可以通過setPackage來指定package(可以指定多個),這樣只有匹配的package能接受到。
    • 使用LocalBroadcastManager來發送本地廣播。
  4. 當你註冊了一個廣播,意味着任何App都可以給你發送廣播,以下有三點可以限制接收者:

    • 註冊的時候增加權限。
    • 在AndroidManifest.xml註冊receivers時,將android:exported屬性設爲false。
    • 使用LocalBroadcastManager來註冊。
  5. action的命名空間是全局的,這意味着action有可能會與其他App衝突,所以最好是有一個自己的命名空間。

  6. 因爲廣播接收者是運行在主線程,它應該快速地被執行並且return,所以不要在onReveive方法中做比較耗時的操作。

  7. 不要在廣播接收者中啓動activitys,這違背了用戶的使用習慣,特別是不止一個接收者時。這種情況下可以考慮展示一個notification來替代。

V. 其他

  1. 系統廣播的action的完整列表在Android SDK下的 BROADCAST_ACTIONS.TXT,路徑爲: Android/sdk/platforms/android-26/data/BROADCAST_ACTIONS.TXT

  2. Android3.1開始,系統的package manager將記錄處於停止狀態的應用。默認情況下,靜態註冊了廣播的處於“停止”狀態的應用,是不會被啓動的,即不會收到廣播。當然你也可以爲Intent指定flag來該變這個行爲:

    • FLAG_INCLUDE_STOPPED_PACKAGES:包含處於“停止”狀態的應用;
    • FLAG_EXCLUDE_STOPPED_PACKAGES:不包含處於“停止”狀態的應用;

如果Intent不包含(或都包含)這兩個flag,則表現形式是包含處於“停止”轉態的應用,但是系統默認添加了FLAG_EXCLUDE_STOPPED_PACKAGES這個flag,這一點在源碼中有所體現:

ActivityManagerService#broadcastIntentLocked

final int broadcastIntentLocked(ProcessRecord callerApp,
    String callerPackage, Intent intent, String resolvedType,
    IIntentReceiver resultTo, int resultCode, String resultData,
    Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
    boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {

    intent = new Intent(intent);

    // By default broadcasts do not go to stopped apps.
    intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
    ...
}

這就意味着如果你想啓動處於“停止”狀態的應用,必須添加FLAG_INCLUDE_STOPPED_PACKAGES這個flag。那麼一個應用在什麼情況下會處於停止狀態?①應用首次安裝並且沒有啓動過;②被人爲地強制停止。開機完成的廣播就是FLAG_EXCLUDE_STOPPED_PACKAGES這種類型的Intent,這意味着如果你的應用被停止了,開機自啓就會失效。下一篇文章將從源碼的角度來分析廣播的工作流程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章