Android官方文檔翻譯-Broadcasts

原文鏈接:https://developer.android.com/guide/components/broadcasts.html

廣播

Android應用可以向Android系統和其他Android應用發送或從它們那接收廣播消息,這類似於發佈-訂閱設計模式。當需要關注的事件發生時這些廣播就會發送出去。例如Android系統在許多系統事件發生時會發送廣播,比如當系統啓動或設備開始充電時。應用也可以發送自定義廣播,例如通知其他APP他們關注的某些事(例如,下載了一些新數據)。

應用可以通過註冊接收特定的一些廣播。當廣播發送出去,系統會自動將廣播路由(routes)到訂閱了該特定類型廣播的應用程序。
一般來說,廣播可以用作應用程序和非常規用戶流之間的消息傳遞系統。但是,你必須注意不要濫用這個響應廣播的機會,在後臺運行一些會導致系統變慢的任務,正如下面這個視頻描述的(譯者注:略)。

系統廣播

系統在許多系統事件發生時會自動發送廣播,比如,當系統打開或關閉飛行模式。系統廣播會發送給所有訂閱了該事件的應用程序。
廣播消息本身封裝在一個Intent對象中,它的action字符串區分發生的事件(例如android.intent.action.AIRPLANE_MODE)。intent可能也包含其他包裹在擴展字段(extra field)中的信息。例如,飛行模式的itent包含一個boolean的擴展,它表明了飛行模式是否打開。
更多關於如何解讀intents和從intent中獲取action字符串,參見Intents and Intent Filterslink.
對於一個完整的系統廣播action列表,參見Android SDK裏的BROADCAST_ACTIONS.TXT文件。每個廣播action有一個與之關聯的常量字段。例如常量ACTION_AIRPLANE_MODE_CHANGED的值是android.intent.action.AIRPLANE_MODE。每個廣播action的文檔在其關聯的常量字段中是可用的。

系統廣播的變化

Android7.0以上不再發送以下系統廣播。這項優化影響所有應用程序,不只針對那些Android7.0.
+ ACTION_NEW_PICTURE
+ ACTION_NEW_VIDEO

面向Android7.0以上的應用程序,下面這條廣播必須使用registerReceiver(BroadcastReceiver, IntentFilter)註冊,在manifest聲明無效。
+ CONNECTIVITY_ACTION

從Android8.0(API 26)開始,系統對manifest聲明的廣播施加了其他限制。如果你的APP面向API26以上版本,對於大多數隱式廣播(不是專門針對你的應用的廣播),你不能使用manifest聲明接收者。

接收廣播

應用程序接收廣播有兩種途徑:通過manifest聲明接收者和context註冊接收者。

Manifest聲明的接收者

如果你在manifest中聲明瞭一個廣播接收者,系統會在該廣播發送時啓動你的應用(如果你的應用還沒有運行).

注意:如果你的APP面向API26以上,你不能使用manifest爲隱式廣播(不是專門針對你的應用的廣播)聲明接收,除了一些不受限制的隱式廣播。在大多數情況下,你可以計劃任務代替。

按如下步驟在manifest中聲明廣播接收者:
1. 在你的應用的manifest中定義單元.

    <receiver android:name=".MyBroadcastReceiver"  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>
  1. 定義BroadcastReceiver 的子類,並實現onReceive(Context, Intent)方法。下面這個廣播接收者實例打印並顯示廣播內容:
    public class MyBroadcastReceiver extends BroadcastReceiver {
        private static final String TAG = "MyBroadcastReceiver";
        @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)期間有效。一旦你的代碼從該方法返回,系統就認爲該組件不再活躍。

經Context註冊的接收者

按如下步驟使用context註冊接收者:
1. 創建[BroadcastReceiver](https://developer.android.com/reference/android/content
/BroadcastReceiver.html)的一個實例。

    BroadcastReceiver br = new MyBroadcastReceiver();

2. 創建一個IntentFilter,然後通過調用registerReceiver(BroadcastReceiver, IntentFilter)註冊接收者。

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

注意:要註冊本地廣播,調用LocalBroadcastManager.registerReceiver(BroadcastReceiver, IntentFilter) 代替。

Context註冊的接收者在他們註冊用的context有效期間接受廣播。例如,如果在Activity context中註冊,Activity沒有銷燬時你可以接收廣播。如果你用Application context註冊,你在整個應用運行期間可以接收廣播。

3. 要停止接收廣播,調用unregisterReceiver(android.content.BroadcastReceiver)。請確保在你不再需要它或者context不再有效的時候註銷接收者。
注意你註冊和註銷接收者的地方,例如如果你使用activity的context在onCreate(Bundle)中註冊接收者,你應該在onDestroy()中註銷以防止將接收者泄漏在activity context之外。如果你在onResume()中註冊接收者,你應該在onPause()中註銷它以防止重複註冊(如果你不想在paused時接收廣播,這可以減少不必要的系統開銷)。不要在onSaveInstanceState(Bundle)中註銷,因爲如果用戶從歷史棧中返回,該方法不會調用。

對進程狀態的影響

廣告接收者的狀態(不管他有沒有在運行)對它所在進程的狀態有影響,這反過來又會影響它被系統殺死的可能性。例如,進程執行接收者(即,正在運行onReceive()中的代碼時),它被視爲一個前臺進程。系統保持進程運行,除非在極度的內存壓力情形下。
但是一旦你的代碼重onReceive()返回,廣播接收者就不再活躍。接收者的宿主進程變得和正在其中運行的其他app組件一樣重要( The receiver’s host process becomes only as important as the other app components that are running in it).如果那個進程只持有一個manifest聲明的接收者(應用程序的常見情形,用戶從未或最近未與之交互),那麼當從onReceive()返回時,系統認爲它的進程是一個低優先級的進程,並且可能會將其殺死,以便爲其他更重要的進程提供資源。
由於這個原因,你不應該在廣播接收者中開啓長時間運行的後臺線程。onReceiver()之後,系統可能在任何時候殺死進程回收內存,並且在這過程中,他會終止運行在該進程中的線程。爲了避免這樣,你應該調用goAsync()(如果你想在後臺線程中多花點時間處理處理廣播)或者使用JobScheduler從接收者中安排一個JobService,這樣系統知道該進程在繼續執行積極的工作。更多信息請查看Processes and Application LifeCycle.

下面這個片段展示了一個使用goAsync()BroadcastReceiver.它表明在onReceive()完成之後,它需要更多時間去完成工作。這在onReceive()中你要完成的工作時間長到足以引發UI線程從框架中斷開(miss a frame >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)方法發送廣播一次給一個接收者。由於每個接收者按順序執行,它可以將一個結果傳遞給下一位接收者,或者完全廢棄這個廣播,從而不會傳給其他接收者。接收者的運行順序可以通過對應的intent-filter的android:priority屬性控制;相同優先級的接收者將以任意順序運行。
+ sendBroadcast(Intent)方法以無序的形式給所有接收者發送廣播。這稱爲普通廣播。這效率更高,但意味着接收者不能從其他接收者那讀取結果,傳遞從廣播接收的數據,或者廢棄廣播。
+ LocalBroadcastManager.sendBroadcast方法發送廣播給與發送者同一APP中的接收者。如果你不需要跨越APP發送廣播,可使用本地廣播。該實現更加高效,並且你無需擔心由於其他app可以接收或發送你的廣播而引起的安全問題。
下面的代碼片段描述了怎樣通過創建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字符串必須提供app的java包名語法,並唯一標誌廣播事件。你可以使用putExtra(String, Bundle)給intent附加其他信息。你也可以通過在intent上調用setPackage(String)將廣播限制在同一組織的app集合。

注意:雖然intents同時用於發送廣播和使用startActivity(Intent)啓動activity,但這些action完全無關。廣播接收者無法看到貨捕捉啓動一個activity的intent;同樣地,當你廣播一個intent,你無法找到或者無法啓動一個activity.

使用權限限制廣播

權限允許你將廣播限制在持有特定權限的app集合。你可以對廣播的發送者和接收者實施限制。

使用權限發送

當你調用sendBroadcast(Intent,String) 或者 sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle),你可以指定一個權限參數。只有使用該標籤在manifest中申請了該權限(如果是危險權限,還要經過允許)的接收者可以接收該廣播,例如,下面的代碼發送一條廣播:

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

要接收該廣播,接收方app必須像下面這樣申請該權限:

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

你可以指定像SEND_SMS已經存在的系統權限或者使用自定義一個權限。有關權限和一般安全,請查看System Permissions.

注意:自定義權限在app安裝時註冊。定義自定義權限的app必須在使用它的app之前安裝。

使用權限接收

如果你在註冊廣播接收者時指定了一個權限參數(registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)或manifest裏的標籤),那麼只有在manifest中使用標籤請求了該權限(如果是危險權限還要經過允許)的廣播才能發送intent到該接收者。
例如,假設你的接收者app有一個manifest聲明的接收者如下所示:

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

或者你的接收者app有一個context註冊的接收者如下所示:

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

然後,爲了能夠發送廣播給那些接收者,發送者app必須請求該權限,如下所示:

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

安全考慮和最佳實踐

這裏是一些安全考慮和發送和接收廣播最佳實踐:

  • 如果你不需要發送廣播到你的app意外的組件,那麼你可以使用LocalBroadcastManager發送和接收本地廣播,這在Support Library中也是可用的。LocalBroadcastManager效率更高(不需要跨進程通信)且讓你不必思考有關其他app可能接受和發送你的廣播的任何安全問題。在你的應用程序中,本地廣播可用作通用的pub/sub事件總線而無需任何系統範圍的廣播。
  • 如果許多app在它們的app中註冊了相同廣播的接收者,這會導致系統啓動太多app,對設備性能和用戶體驗造成實質性影響。爲了避免如此,優先使用context註冊而不是manifest聲明。某些條件下,Android系統本身會強制使用context註冊接收者。例如,CONNECTIVITY_ACTION廣播只傳遞給context註冊的接收者。
  • 不要使用隱式intent廣播敏感信息。任意註冊了接收該廣播的app都能讀到這些信息。有三個途徑可以控制誰能接收你的廣告:
    • 發送廣播時你可以指定一個權限
    • Android4.0以上,發送廣播時你可以使用setPackage(String)指定一個package.系統會將廣播限制在符合包名的一類app.
    • 你可以使用LocalBroadcastManager發送本地廣播。
  • 當你註冊了一個接收者,任意app都可能會發送惡意的廣播給你的app的接收者。有三個途徑可以限制你的app接收的廣播:
    • 註冊廣播接收者時你可以指定權限
    • 對於清單聲明的接收者,你可以在manifest中設置android:exported屬性爲“false”。這樣接收者不會接收該app意外來源的廣播。
    • 使用LocalBroadcastManager你可以限制自身只使用本地廣播。
  • 廣播action的命名空間是全局的。確保action名和其他字符串寫在一個你自己的命名空間,否則可能在無意中與其他app發生衝突。
  • 因爲接收者的onReceive(Context, Intent)方法運行在主線程中,它應該快速執行和返回。如果你需要執行耗時任務,注意多線程和啓動後臺服務,因爲當onReceive()返回時,整個進程都可能被系統殺掉。更多信息,請查看Effect on process state.要執行耗時任務,我們建議:
    • 在你的接收者的onReceive()方法中調用goAsync(),並且將BroadcastReceiver.PendingResult傳到後臺線程。這可以讓廣播從onReceive()返回之後仍然保持活躍。但是即使使用了該方法,系統仍然希望你能儘快完成廣播(10秒鐘以下)。它允許你將工作移交給另一個線程以防阻塞主線程。
    • 使用JobScheduler安排任務。更多信息,請查看Intelligent Job Scheduling.
  • 不要從廣播接收者那啓動activity,因爲這樣的用戶體驗很糟糕;如果接收者不止一個更是如此。相反,可以考慮顯示一個通知。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章