淺談Android Broadcast

前言:本文所寫(xiě)的是博主的個(gè)人見(jiàn)解,如有錯(cuò)誤或者不恰當(dāng)之處,歡迎私信博主,加以改正! 原文鏈接,demo鏈接

廣播簡(jiǎn)述

Android應(yīng)用程序可以發(fā)送或者接收來(lái)自 Android 系統(tǒng)和其他 Android 應(yīng)用程序的廣播消息,類(lèi)似于發(fā)布訂閱設(shè)計(jì)模式。當(dāng)感興趣的事件發(fā)生時(shí),這些廣播被發(fā)送。例如,Android 系統(tǒng)在各種系統(tǒng)事件發(fā)生時(shí)發(fā)送廣播,比如系統(tǒng)啟動(dòng)或者設(shè)備開(kāi)始充電等。應(yīng)用程序還可以發(fā)送自定義的廣播,比如通知其他應(yīng)用可能感興趣的內(nèi)容(例如,一些新數(shù)據(jù)已被下載)。

應(yīng)用程序可以注冊(cè)接收特定的廣播,當(dāng)發(fā)送廣播時(shí),系統(tǒng)自動(dòng)將廣播路由到訂閱該特定類(lèi)型廣播的應(yīng)用程序。一般來(lái)說(shuō),廣播可以用作跨應(yīng)用程序和正常用戶(hù)流之外的消息傳遞系統(tǒng)。

系統(tǒng)廣播

當(dāng)發(fā)生各種系統(tǒng)事件時(shí),系統(tǒng)會(huì)自動(dòng)發(fā)送廣播,例如系統(tǒng)切換飛行模式時(shí),系統(tǒng)廣播被發(fā)送到所有接收訂閱事件的應(yīng)用程序。

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

系統(tǒng)廣播行為的完整列表,請(qǐng)參閱 Android SDK 中的 BROADCAST_ACTIONS.TXT 文件。每個(gè)廣播行為都有與之關(guān)聯(lián)的常量字段,例如常量 ACTION_AIRPLANE_MODE_CHANGED 的值為 android.intent.action.AIRPLANE_MODE,每個(gè)廣播動(dòng)作的文檔都可以在其相關(guān)聯(lián)的常量字段中獲得。

注:系統(tǒng)廣播更改
Android 7 和更高的不再發(fā)送以下系統(tǒng)廣播。這種優(yōu)化影響所有的應(yīng)用程序,不僅那些針對(duì)Android 7。

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

注冊(cè)接收廣播

應(yīng)用程序可以通過(guò)兩種方式接收廣播:通過(guò)清單聲明的接收者和上下文注冊(cè)的接收者

  1. 清單聲明的接收器(靜態(tài)注冊(cè))

在清單中聲明廣播接收者,可以通過(guò)下面的步驟:
(1)在應(yīng)用程序的清單中指定 <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 (意圖過(guò)濾器) 指定你的接收者訂閱的廣播操作。

(2)繼承 BroadcastReceiver 并實(shí)現(xiàn) onReceive(Context,Intent) 方法,請(qǐng)看下面的示例:

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();
    }
    
}

當(dāng)應(yīng)用程序安裝時(shí),系統(tǒng)包管理器注冊(cè)接受器,然后接收器將成為你的應(yīng)用程序的單獨(dú)入口,這意味著如果應(yīng)用程序當(dāng)前未運(yùn)行,系統(tǒng)可以啟動(dòng)你的應(yīng)用程序并發(fā)送廣播。

系統(tǒng)將創(chuàng)建一個(gè)新的 BroadcastReceiver 組件對(duì)象來(lái)處理它接收的每個(gè)廣播。這個(gè)對(duì)象僅對(duì)調(diào)用 onReceive(Context,Intent)的時(shí)候有效。當(dāng)你在代碼從該方法返回,系統(tǒng)將認(rèn)為組件不再活動(dòng)。

  1. context 注冊(cè)接收者(動(dòng)態(tài)注冊(cè))

    用context注冊(cè)接收器,可以通過(guò)以下幾個(gè)步驟:

    (1) 創(chuàng)建 BroadcastReceiver 并實(shí)例化

     BroadcastReceiver broadcastReceiver = new DemoBroadcastReceiver();
    

    (2) 創(chuàng)建一個(gè) IntentFilter 并調(diào)用 registerReceiver( BroadcastReceiver , IntentFilter ) 來(lái)注冊(cè)接收器

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

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

    如果context注冊(cè)有效時(shí),context注冊(cè)的接收者就可以接收廣播。例如你在 Activity 的 context 中注冊(cè),只要 Activity 沒(méi)有被銷(xiāo)毀,就會(huì)收到廣播。如果是使用 ApplicationContext 進(jìn)行注冊(cè),只要程序在運(yùn)行就可以收到廣播。

    (3)停止接收廣播時(shí),可以調(diào)用 unregisterReceiver( android.content.BroadcastReceiver ) 進(jìn)行注銷(xiāo)。當(dāng)你不需要這個(gè)廣播時(shí)或者 context 不再有效時(shí),一定要注銷(xiāo)接收器?!?/p>

    注意你在哪里注冊(cè)和注銷(xiāo)的接收者,比如,你在 Activity 的 onCreate( Bundle ) 中注冊(cè)一個(gè)接收者,則應(yīng)該在 onDestory() 中注銷(xiāo)它,防止接收器泄露;如果你在 onResume() 中注冊(cè)一個(gè)接收器,你應(yīng)該在 onPause() 中注銷(xiāo),以防止多次注冊(cè)( 如果你不想在 Activity 暫停時(shí)接收廣播,可以做減少不必要的系統(tǒng)開(kāi)銷(xiāo) )。不要在 onSaveInstanceState( Bundle ) 中注銷(xiāo),如果用戶(hù)在歷史棧中移回,則不會(huì)調(diào)用這個(gè)方法。

  2. 進(jìn)程狀態(tài)的影響

BroadcastReceiver 的狀態(tài)(無(wú)論是否在運(yùn)行)會(huì)影響其包含進(jìn)程的狀態(tài),這也可能反過(guò)來(lái)影響其被系統(tǒng)殺死的可能性。例如,當(dāng)進(jìn)程執(zhí)行一個(gè)接收器時(shí)(在其 onReceive() 方法中運(yùn)行代碼),它被認(rèn)為是一個(gè)前臺(tái)進(jìn)程,除非內(nèi)存壓力極度大的時(shí)候,否則系統(tǒng)將保持其進(jìn)程的運(yùn)行。

然而,一旦你的代碼從 onREceive() 方法中返回, BroadcastReceiver 就不在活動(dòng)了。接收器的宿主進(jìn)程變得和運(yùn)行在這個(gè)進(jìn)程中的其它應(yīng)用程序組件一樣重要。如果該進(jìn)程只承載一個(gè) manifest-declared 接收器(應(yīng)用程序從來(lái)沒(méi)有或者最近沒(méi)有跟用戶(hù)進(jìn)行交互),然后從 onReceive() 返回時(shí),系統(tǒng)將認(rèn)為它的進(jìn)程是一個(gè)低優(yōu)先級(jí)的進(jìn)程,很可能會(huì)殺死它,從而施放資源,用于其它優(yōu)先級(jí)高的進(jìn)程。

因此,不應(yīng)該在廣播接收器中執(zhí)行耗時(shí)的后臺(tái)線(xiàn)程,在執(zhí)行 onReceive() 后,系統(tǒng)隨時(shí)可以殺死進(jìn)程以回收內(nèi)存,這樣做會(huì)終止在該進(jìn)程中運(yùn)行生成的線(xiàn)程。為了避免這種情況的發(fā)生,應(yīng)該調(diào)用 goAsync() 方法(如果你需要更多的時(shí)間來(lái)處理后臺(tái)線(xiàn)程中的廣播)或使用 JobScheduler 從接收器調(diào)度 JobService ,則系統(tǒng)會(huì)知道該進(jìn)程繼續(xù)執(zhí)行工作。

下面的這段代碼顯示了一個(gè) BroadcastReceiver 使用 goAsync() 來(lái)標(biāo)記,需要更多的時(shí)間來(lái)完成當(dāng)前的操作,并在完成后再 onReceiver() 。如果你在 onReceive() 中需要的時(shí)間很長(zhǎng),導(dǎo)致 UI 線(xiàn)程錯(cuò)過(guò)了一幀( 16 ms ),可以使用上面的方法,在后臺(tái)線(xiàn)程繼續(xù)工作。

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);

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

                return log;
            }
        };

        asyncTask.execute();
    }

}

發(fā)送廣播

Android 提供了三種方法供應(yīng)用發(fā)送廣播:

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

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

  • LocalBroadcastManager.sendBroadcast 方法將廣播發(fā)送到同個(gè)應(yīng)用程序中的接收者。如果你不需要發(fā)送跨應(yīng)用廣播,請(qǐng)使用本地廣播。實(shí)施 效率更高(無(wú)需進(jìn)行跨進(jìn)程通信),不需要擔(dān)心其他可以接收或者發(fā)送廣播的應(yīng)用程序之間的任何安全問(wèn)題

下面的代碼片段演示了如何通過(guò)創(chuàng)建 Intent 并 調(diào)用sendBroadcast( Intent ) 來(lái)發(fā)送廣播:

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

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

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

廣播權(quán)限約束

Permissions(權(quán)限) 允許你將廣播限制為持有一定權(quán)限的應(yīng)用程序集。可以對(duì)廣播的發(fā)送方或接收方強(qiáng)制執(zhí)行限制。

  1. 發(fā)送權(quán)限
    當(dāng)你調(diào)用 sendBroadcast( Intent , String ) 或 sendOrderedBroadcast( Intent , String , BroadcastReceiver , Handle , int , String , Bundle ) 時(shí),可以指定權(quán)限參數(shù)。只有通過(guò)其清單中的標(biāo)簽請(qǐng)求許可的接收器才能接收廣播。例如,以下代碼發(fā)送廣播:
    sendBroadcast(new Intent("com.passershowe.broadcast.NOTIFY"),      Manifest.permission.SEND_SMS);
    

要接收廣播,接收應(yīng)用程序必須請(qǐng)求如下所示的權(quán)限:

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

你可以指定一個(gè)現(xiàn)有的系統(tǒng)權(quán)限(如SEND_SMS)或使用 <permission>元素定義自定義權(quán)限

注意:當(dāng)你的應(yīng)用程序注冊(cè)了自定義權(quán)限,你必須在使用這個(gè)權(quán)限前,安裝它

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

    例如,假設(shè)你的接收應(yīng)用程序具有清單聲明的接收器,如下所示:

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

    或者你的接收應(yīng)用程序具有 context 注冊(cè)的接收器,如下所示:

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

    然后,為了能夠向這些接收者發(fā)送廣播,發(fā)送應(yīng)用程序必須如下所示請(qǐng)求許可:

    <uses-permission android:name="android.permission.SEND_SMS"/>
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容