天天看點

【Android】Broadcasts詳解系統廣播接收廣播發送廣播通過權限限制廣播的收發安全性和最佳使用方案

大家好,又見面了,我是你們的朋友全棧君。

Android應用程式可以發送廣播,也可以接收Android系統或者其它應用發出的廣播,這跟釋出-訂閱設計模式很相似。當一些受到關心的事件發生後,廣播會被自動發送。舉例來說,當一些系統事件(如開機,裝置開始充電等)發生,Android系統會發送廣播。應用程式也可以發送自定義的廣播,比如當某個應用關注的事件(如資料更新等)發生後可以發送廣播提醒它。

系統廣播

當一系列系統事件發生的時候,系統會自動發送廣播,比如飛行模式的切換。系統廣播會發送給所有注冊監聽廣播的應用。

廣播消息封裝在一個Intent對象中,其中的action屬性辨別的事件的類型(比如android.intent.action.AIRPLANE_MODE),可能在intent的附件字段還包含了附加的資訊。比如,用于表示飛行模式的intent包含一個附加的布爾字段來表示飛行模式的狀态是開啟還是關閉。

如果想要具體了解如何如何讀取一個intent并且擷取附加字段,參閱Intents and Intent Filters

參閱Android SDK中的BROADCAST_ACTIONS.TXT來了解所有系統廣播的action。每一個系統廣播都有一個常量與其綁定。比如,常量ACTION_AIRPLANE_MODE_CHANGED表示android.intent.action.AIRPLANE_MODE。每一個廣播的action的文檔都在與其關聯的常量域中。

系統廣播的變化

Android 7.0或更高版本不再發送下列系統廣播,這項優化會影響所有的應用程式,而不隻是那些針對Android 7.0開發的程式。

  • ACTION_NEW_PICTURE
  • ACTION_NEW_VIDEO

針對Android 7.0(API level 24)或更新版本開發的應用必須在程式中使用

ACTION_NEW_PICTURE

ACTION_NEW_VIDEO

注冊監聽下列的廣播,在程式清單中聲明不再有效。

  • CONNECTIVITY_ACTION

接收廣播

應用程式可以使用兩種方式接收廣播:在應用清單中定義一個廣播接收器;在程式中注冊一個廣播接收器。

靜态廣播接收器

要定義一個靜态廣播接收器,執行下面的步驟:

當應用程式安裝的時候,軟體包管理器會在系統中注冊廣播接收器。之後這個廣播接收器就變成了你的應用程式中一個獨立的入口,這就意味着如果你的應用程式不在運作,系統可以啟動你的程式并傳遞廣播。

系統會建立一個新的BroadcastReceiver元件對象來處理接收到的廣播。這個對象隻在調用onReceive(Context, Intent)方法期間有效。一旦從該方法傳回,系統就認為這個元件對象已經失效。

動态廣播接收器

要注冊一個上下文相關的動态廣播接收器,執行以下步驟:

對程序狀态的影響

你的廣播接收器的狀态會影響它所在的程序的狀态,轉而會影響程序被系統殺死的可能性。比如,當一個程序執行一個廣播接收器(執行onReceive()方法中的代碼),它會被當作一個前台程序。除非記憶體極度匮乏,否則系統會一直讓該程序運作。

然而,一旦從onReceive()傳回,廣播接收器就不再處于激活狀态,它的宿主程序也就跟其它的普通程序具有相同的優先級。如果那個程序隻擁有一個在應用清單中定義的接收器,那麼當從onReceive()傳回後,系統會把它當作一個低優先級的程序,當其它優先級更高的程序需要更多記憶體的時候,它就可能被殺掉。

鑒于這個原因,你不應該在一個廣播接收器中啟動一個長時間在背景運作的線程。當從onReceive()傳回後,系統可能會殺掉程序來回收記憶體,這會結束所有運作在這個程序中的線程。為了避免這種情況,你要麼調用goAsync()(如果你希望能夠長時間在背景線程中運作廣播接收器),要麼在接收器中使用JobScheduler排程一個JobService。這樣系統就直到你的程序還在繼續執行任務。參閱

Processes and Application Life Cycle來擷取更多資訊。

下面的代碼片段展示了使用goAsync()來辨別程序需要更多時間來完成任務。如果你要執行的任務會造成UI阻塞(>16ms),這種方式非常有效。

```
public class MyBroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "MyBroadcastReceiver";

    @Override
    public void onReceive(final 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");
                Log.d(TAG, log);
                // Must call finish() so the BroadcastReceiver can be recycled.
                pendingResult.finish();
                return data;
            }
        };
        asyncTask.execute();
    }
}
```           

複制

發送廣播

Android提供了三種發送廣播的方式:

  • sendOrderedBroadcast(Intent, String)方法一次向一個receiver發送廣播。因為每個receiver輪流執行,是以receiver可以将結果向下個receiver轉發。receiver接收的順序可以通過intent-filter中的android:priority屬性控制,具有相同接收優先級的receiver的接收順序是随機的。
  • LocalBroadcastManager.sendBroadcast方法隻會向本應用中的receiver發送廣播。如果你不想在應用之間發送廣播,可以使用本地廣播。這種實作方式更加高效(無需程序間通信),并且你無須考慮由于其它應用接收你的廣播而帶來的安全問題。

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

Intent intent = new Intent();
intent.setAction("com.example.broadcast.MY_NOTIFICATION");
intent.putExtra("data","Notice me senpai!");
sendBroadcast(intent);           

複制

廣播消息被封裝在一個Intent對象中。intent的action屬性必須提供應用的包名并且能夠唯一地辨別一個廣播事件。你可以通過調用putExtra(String, Bundle)來附加額外的xinxi。你也可以通過調用intent的setPackage(String)方法來将廣播範圍限定在某個組織的一系列應用的範圍之内。

注意:雖然intents同時被用來發送廣播和啟動Activity,但是這些行為之間并沒有任何關聯。廣播接收器無法捕捉到用來啟動Activity的intent;同樣地,當你廣播一個intent,你也無法啟動一個Activity。

通過權限限制廣播的收發

權限機制可以讓你将廣播的範圍限制在一系列擁有特定權限的應用之間。你既可以限制發送發,也可以限制接收方。

帶權限發送

當你調用sendBroadcast(Intent, String)或者 sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)時,你可以指定一個權限參數。隻有應用在應用清單中申請了那個權限,其中的receiver才能接收到廣播。比如下面的代碼發送了一個帶權限的廣播:

sendBroadcast(new Intent("com.example.NOTIFY"),
              Manifest.permission.SEND_SMS);           

複制

要接收這個廣播,應用必須申請下面的權限:

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

複制

你既可以指定一個系統中已經存在的權限,比如SEND_SMS,也可以用自定義一個權限。關于權限的詳情請參考System Permissions。

帶權限接收

如果你在注冊receiver的時候指定了一個權限參數,那麼隻有申請了相應權限的應用才能夠向你的receiver發送廣播。

比如,假設你的receiver在應用清單中這樣定義:

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

複制

或者在代碼中這樣定義一個上下文相關的receiver:

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

複制

那麼,如果你要向這些receiver發送廣播,發送方必須申請如下的權限:

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

複制

安全性和最佳使用方案

下面是關于發送和接收廣播的安全性考慮和最佳使用方案:

  • 如果你不需要向其它應用發送廣播,那麼可以使用LocalBroadcastManager發送和接收本地廣播。本地廣播更加高效(無需程序間通信),并且你無須考慮由于其它應用接收你的廣播而帶來的安全問題。本地廣播可以在不增加系統範圍内廣播數量的前提下實作一個應用内部的釋出/訂閱事件通道。
  • 如果許多應用都在應用清單中注冊接收同一個廣播,會造成系統啟動大量應用,對硬體性能和使用者體驗造成影響。為了避免這種情況,優先考慮上下文相關的廣播接收器,而不是在應用清單中定義。有時,Android系統會強制要求使用上下文相關的廣播接收器。比如CONNECTIVITY_ACTION這個廣播隻會發送給上下文相關的廣播接收器。
  • 不要使用隐式intent發送敏感資訊。這個資訊可能會被其它任何注冊該廣播的應用監聽。有三種方法來限定廣播的接收方:
    • 發送廣播的時候你可以指定一個權限
    • 在Android 4.0或更高版本,你可以通過setPackage(String)來指定一個包名。系統會将廣播發送到比對該包名的應用中。
    • 你可以通過LocalBroadcastManager發送本地廣播。
  • 當你注冊一個receiver後,任何應用都可以向你發送具有潛在惡意資訊的廣播。有三種方式來限制廣播的發送發:
    • 注冊receiver的時候可以指定一個權限。
    • 對于在應用清單中定義的receiver,可以将android:exported屬性設為false,這樣receiver就不會接收其它應用發來的廣播。
    • 你可以通過LocalBroadcastManager僅接收本地廣播。
  • 廣播的action标志是全局的,確定action的值和其它字元串的值是在你自己的命名空間中,否則你可能會不小心與其它應用發生沖突。
  • 因為receiver的onReceive(Context, Intent)方法運作在主線程中,是以它必須能夠很快地執行并傳回。如果你需要執行一個耗時的操作,要小心使用子線程或者背景服務,因為當onReceive(Context, Intent)函數傳回之後,系統随時會殺死你的程序。要了解更多資訊,參考對程序的影響小節,要執行耗時的操作,我們建議:
    • 在receiver的onReceive()中調用goAsync(),然後将BroadcastReceiver.PendingResult傳遞給背景線程。這樣可以讓receiver在onReceive()傳回之後保持活躍。但即使這樣,系統也期望你快速結束這個任務(10s以内)。它确實可以讓你把任務放到背景線程進而不影響主線程。
    • 通過JobScheduler排程一個任務。詳情參考Intelligent Job Scheduling。
  • 不要在receiver中啟動Activity,因為這嚴重影響使用者體驗,尤其是當存在多個receiver。可以通過顯示一個通知來代替。

釋出者:全棧程式員棧長,轉載請注明出處:https://javaforall.cn/148542.html原文連結:https://javaforall.cn