Android入門教程 | 廣播機制 Broadcast

Android_anzi發表於2021-10-15


Android應用可以透過廣播從系統或其他App接收或傳送訊息。類似於訂閱-釋出設計模式。當某些事件發生時,可以發出廣播。 系統在某些狀態改變時會發出廣播,例如開機、充電。App也可傳送自定義廣播。廣播可用於應用間的通訊,是IPC的一種方式。

廣播的種類

廣播的種類也可以看成是廣播的屬性。

  • 標準廣播(Normal Broadcasts)
    完全非同步的廣播。廣播發出後,所有的廣播接收器幾乎同時接收到這條廣播。 不同的App可以註冊並接到標準廣播。例如系統廣播。
  • 有序廣播(Ordered Broadcasts)
    同步廣播。同一時刻只有一個廣播接收器能接收到這條廣播。這個接收器處理完後,廣播才會繼續傳遞。 有序廣播是全域性的廣播。
  • 本地廣播(Local Broaddcasts)
    只在本App傳送和接收的廣播。註冊為本地廣播的接收器無法收到標準廣播。
  • 帶許可權的廣播
    傳送廣播時可以帶上相關許可權,申請了許可權的 App 或廣播接收器才能收到相應的帶許可權的廣播。 如果在 manifest 中申請了相應許可權,接收器可以不用再申請一次許可權即可接到相應廣播。

接收廣播

建立廣播接收器,呼叫 onReceive()方法,需要一個繼承 BroadcastReceiver 的類。

註冊廣播
程式碼中註冊稱為動態註冊。在 AndroidManifest.xml中註冊稱為靜態註冊。動態註冊的剛波接收器一定要取消註冊。在 onDestroy()方法中呼叫 unregisterReceiver()方法來取消註冊。

不要在 onReceive()方法中新增過多的邏輯操作或耗時的操作。因為在廣播接收器中不允許開啟執行緒,當 onReceive()方法執行較長時間而沒結束時,程式會報錯。因此廣播接收器一般用來開啟其他元件,比如建立一條狀態列通知或啟動一個服務。

新建一個 MyExampleReceiver繼承自 BroadcastReceiver

public class MyExampleReceiver extends BroadcastReceiver {    @Override
    public void onReceive(Context context, Intent intent) {        Toast.makeText(context,"Got it",Toast.LENGTH_SHORT).show();        //abortBroadcast();
    }
}

abortBroadcast()可以截斷有序廣播

AndroidManifest.xml中註冊廣播接收器; android:name裡填接收器的名字。 可以設定廣播接收器優先順序:

<intent-filter android:priority="100"><receiver android:name=".MyExampleReceiver">
    <intent-filter>
        <action android:name="com.rust.broadcasttest.MY_BROADCAST"/>
    </intent-filter></receiver>

讓接收器接收到一條 "com.rust.broadcasttest.MY_BROADCAST"廣播。
傳送自定義廣播(標準廣播)時,要傳送這個值。例如:

Intent intent = new Intent("com.rust.broadcasttest.MY_BROADCAST");
sendBroadcast(intent);

傳送有序廣播,應當呼叫 sendOrderedBroadcast()

Intent intent = new Intent("com.rust.broadcasttest.MY_BROADCAST");
sendOrderedBroadcast(intent,null);

傳送廣播

App有3種傳送廣播的方式。傳送廣播需要使用 Intent類。

sendOrderedBroadcast(Intent, String)

傳送有序廣播。每次只有1個廣播接收器能接到廣播。 接收器接到有序廣播後,可以完全地截斷廣播,或者傳遞一些資訊給下一個接收器。 有序廣播的順序可受 android:priority標籤影響。同等級的接收器收到廣播的順序是隨機的。

sendBroadcast(Intent)

以一個未定義的順序向所有接收器傳送廣播。也稱作普通廣播。 這種方式更高效,但是接收器不能給下一個接收器傳遞訊息。這類廣播也無法截斷。

LocalBroadcastManager.sendBroadcast
廣播只能在應用程式內部進行傳遞,並且廣播接收器也只能接收到來自本應用程式發出的廣播。 這個方法比全域性廣播更高效(不需要Interprocess communication,IPC),而且不需要擔心其它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"/>

當然也可以使用自定義。在 manifest 中使用 permission 標籤

<permission android:name="custom_permission" />

新增後編譯一下。即可呼叫 Manifest.permission.custom_permission

接收帶許可權的廣播

若註冊廣播接收器時申明瞭許可權,那麼只會接收到帶著相應許可權的廣播。

在配置檔案中宣告許可權,程式才能訪問一些關鍵資訊。 例如允許查詢系統網路狀態。

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><!-- 機器開機廣播 --><uses-permission android:name="android.permission.BOOT_COMPLETED">

如果沒有申請許可權,程式可能會意外關閉。

使用示例

傳送和接收廣播。分為傳送和接收方2個App。

使用帶許可權的廣播。系統許可權與自定義許可權。 使用許可權需要在 AndroidManifest.xml中宣告。如果是自定義許可權,需要先新增自定義許可權。

<!-- 自定義的許可權  給廣播用 --><permission android:name="com.rust.permission_rust_1" /><uses-permission android:name="com.rust.permission_rust_1" />

傳送廣播時帶上許可權宣告。接收方(不論是否己方App)需要在 AndroidManifest.xml中申請許可權。 註冊接收器時也需要宣告許可權。

傳送不帶許可權的有序廣播

Intent intent = new Intent(MSG_PHONE);
sendOrderedBroadcast(intent, null);Log.d(TAG, "[RustFisher-App1] 傳送不帶許可權的有序廣播, " + intent.getAction());

傳送方App1程式碼

private static final String TAG = "rustApp";
    public static final String MSG_PHONE = "msg_phone";
    public static final String PERMISSION_RUST_1 = "com.rust.permission_rust_1";        // onCreate註冊廣播接收器
        registerReceiver(mStandardReceiver1, makeIF());
        registerReceiver(mStandardReceiver2, makeIF());
        registerReceiver(mStandardReceiver3, makeIF());
        registerReceiver(mStandardReceiverWithPermission, makeIF(),                Manifest.permission.permission_rust_1, null);  // 帶上許可權
        LocalBroadcastManager.getInstance(getApplicationContext())
                .registerReceiver(mLocalReceiver1, makeIF());        LocalBroadcastManager.getInstance(getApplicationContext())
                .registerReceiver(mLocalReceiver2, makeIF());        LocalBroadcastManager.getInstance(getApplicationContext())
                .registerReceiver(mLocalReceiver3, makeIF());        // 解除接收器
        unregisterReceiver(mStandardReceiver1);
        unregisterReceiver(mStandardReceiver2);
        unregisterReceiver(mStandardReceiver3);
        unregisterReceiver(mStandardReceiverWithPermission);        LocalBroadcastManager.getInstance(getApplicationContext())
                .unregisterReceiver(mLocalReceiver1);        LocalBroadcastManager.getInstance(getApplicationContext())
                .unregisterReceiver(mLocalReceiver2);        LocalBroadcastManager.getInstance(getApplicationContext())
                .unregisterReceiver(mLocalReceiver3);    // 傳送標準廣播
    private void sendStandardBroadcast() {
        Intent intent = new Intent(MSG_PHONE);
        sendBroadcast(intent);        Log.d(TAG, "[RustFisher-App1] Dispatcher 傳送標準廣播");
    }    // 傳送帶許可權的標準廣播
    private void sendStandardBroadcastWithPermission() {
        Intent intent = new Intent(MSG_PHONE);
        sendBroadcast(intent, PERMISSION_RUST_1);        Log.d(TAG, "[RustFisher-App1] Dispatcher 傳送帶許可權的標準廣播");
    }    // 傳送本地廣播
    private void sendAppLocalBroadcast() {
        Intent intent = new Intent(MSG_PHONE);        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);        Log.d(TAG, "[RustFisher-App1] Dispatcher 傳送本地廣播");
    }    private IntentFilter makeIF() {
        IntentFilter intentFilter = new IntentFilter(MSG_PHONE);
        intentFilter.addAction(Intent.ACTION_TIME_TICK);
        intentFilter.addAction(Intent.ACTION_TIME_CHANGED);
        return intentFilter;
    }    // 標準接收器  用context來註冊
    private BroadcastReceiver mStandardReceiver1 = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {            Log.d(TAG, "[RustFisher-App1] 標準接收器1 收到: " + intent.getAction());
        }
    };    // 標準接收器  用context來註冊
    private BroadcastReceiver mStandardReceiver2 = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {            Log.d(TAG, "[RustFisher-App1] 標準接收器2 收到: " + intent.getAction());            if (intent.getAction().endsWith(MSG_PHONE)) {
                abortBroadcast(); // 截斷有序廣播
                Log.d(TAG, "[RustFisher-App1] 標準接收器2截斷有序廣播 " + intent.getAction());
            }
        }
    };    // 標準接收器  用context來註冊
    private BroadcastReceiver mStandardReceiver3 = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {            Log.d(TAG, "[RustFisher-App1] 標準接收器3 收到: " + intent.getAction());
        }
    };    // 註冊的時候給它帶許可權  標準接收器
    private BroadcastReceiver mStandardReceiverWithPermission = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {            Log.d(TAG, "[RustFisher-App1] 帶許可權的標準接收器收到: " + intent.getAction());
        }
    };    /**
 * 用LocalBroadcastManager來註冊成為本地接收器
 * 收不到標準廣播 - 不論是本app發出的還是別的地方發出來的
 */
    private BroadcastReceiver mLocalReceiver1 = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {            Log.d(TAG, "[RustFisher-App1] 本地接收器1 收到: " + intent.getAction());
        }
    };    private BroadcastReceiver mLocalReceiver2 = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {            Log.d(TAG, "[RustFisher-App1] 本地接收器2 收到: " + intent.getAction());
        }
    };    private BroadcastReceiver mLocalReceiver3 = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {            Log.d(TAG, "[RustFisher-App1] 本地接收器3 收到: " + intent.getAction());
        }
    };

接收方App2程式碼

<!-- 自定義的許可權  給廣播用 --><permission android:name="com.rust.permission_rust_1" /><uses-permission android:name="com.rust.permission_rust_1" />
public static final String MSG_PHONE = "msg_phone";    // onCreate裡註冊接收器
    registerReceiver(mDefaultReceiver, makeIF());    LocalBroadcastManager.getInstance(getApplicationContext())
            .registerReceiver(mLocalReceiver, makeIF());
    unregisterReceiver(mDefaultReceiver);    LocalBroadcastManager.getInstance(getApplicationContext())
            .unregisterReceiver(mLocalReceiver);    private BroadcastReceiver mDefaultReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {            Log.d(TAG, "[App2] standard receive: " + intent.getAction());
        }
    };    private BroadcastReceiver mLocalReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {            Log.d(TAG, "[App2] local receive: " + intent.getAction());
        }
    };    private IntentFilter makeIF() {
        IntentFilter intentFilter = new IntentFilter(MSG_PHONE);
        intentFilter.addAction(Intent.ACTION_TIME_TICK);
        intentFilter.addAction(Intent.ACTION_TIME_CHANGED);
        return intentFilter;
    }

使用 LocalBroadcastManager發出的本地廣播,另一個App是接收不到的。 要收到本地廣播,同樣需要 LocalBroadcastManager來註冊接收器。

可以把本地廣播看成是一個區域性的,App內的廣播體系。

實驗中我們注意到, Intent.ACTION_TIME_TICK廣播是可以截斷的。

監聽螢幕亮滅

使用廣播監聽裝置螢幕亮滅狀態。這個是系統發出來的廣播。

使用的action是

  • Intent.ACTION_SCREEN_ON 亮屏
  • Intent.ACTION_SCREEN_OFF 滅屏

      private void registerScreenListener() {
          IntentFilter filter = new IntentFilter();
          filter.addAction(Intent.ACTION_SCREEN_ON);
          filter.addAction(Intent.ACTION_SCREEN_OFF);
          registerReceiver(mScreenReceiver, filter);
      }  private BroadcastReceiver mScreenReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              final String action = intent.getAction();          if (Intent.ACTION_SCREEN_ON.equals(action)) {              // 螢幕亮
              } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {              // 螢幕滅
              }
          }
      };

Broadcast 相關面試題

1. 廣播傳輸的資料是否有限制,是多少,為什麼要限制?

  • 廣播是透過Intent攜帶需要傳遞的資料的
  • Intent是透過Binder機制實現的
  • Binder對資料大小有限制,不同room不一樣,一般為1M

2. 廣播的分類?

  • 標準廣播:透過context. sendBroadcast或者context. sendBroadcastAsUser傳送給當前系統中所有註冊的接受者,也就是隻要註冊了就會接收到。應用在需要通知各個廣播接收者的情況下使用,如開機啟動。
  • 有序廣播:接收者按照優先順序處理廣播,並且前面處理廣播的接受者可以中止廣播的傳遞,一般透過context. sendOrderedBroadcast或者context.sendOrderedBroadcastAsUser,在需要有特定攔截的場景下使用,如黑名單簡訊、電話攔截。
  • 粘性廣播:可以傳送給以後註冊的接受者,意思是系統會將前面的粘性廣播儲存在AMS中,一旦註冊了與以儲存的粘性廣播符合的廣播,在註冊結束後會立即收到廣播,一般透過context. sendStickyBroadcast或context.sendStickyOrderedBroadcast來傳送,從字面上看,可以看出來粘性廣播也分為普通粘性廣播和有序粘性廣播。
  • 本地廣播:發出的廣播只能在應用程式內部進行傳遞,廣播接收器也只能接受來自本應用程式的廣播。
  • 全域性廣播:系統和廣播,發出的廣播可以被其他任何應用程式接收到,並且也可以接受到其他任何應用程式的廣播。

3. 廣播的使用場景,使用方式
廣播是一種廣泛運用的在應用程式之間傳輸資訊的機制,主要用來監聽系統或者應用發出的廣播資訊,然後根據廣播資訊作為相應的邏輯處理,也可以用來傳輸少量、頻率低的資料。

在實現開機啟動服務和網路狀態改變、電量變化、簡訊和來電時透過接收系統的廣播讓應用程式作出相應的處理。

使用:

//在AndroidManifest中靜態註冊<receiver
    android:name=".MyBroadcastReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter android:priority="100">
        <action android:name="com.example.hp.broadcasttest.MY_BROADCAST"/>
    </intent-filter>
</receiver>//動態註冊,在程式碼中註冊@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mIntentFilter = new IntentFilter();    //新增廣播想要監聽的型別,監聽網路狀態是否發生變化
    mIntentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
    mNetworkChangeReceiver = new NetworkChangeReceiver();    //註冊廣播
    registerReceiver(mNetworkChangeReceiver, mIntentFilter);
}
@Override
protected void onDestroy() {
    super.onDestroy();    //取消註冊廣播接收器
    unregisterReceiver(mNetworkChangeReceiver);
}//傳送廣播,同樣透過IntentIntent intent = new Intent("com.example.hp.broadcasttest.MY_BROADCAST");//傳送標準廣播sendBroadcast(intent);//接收廣播public class MyBroadcastReceiver extends BroadcastReceiver {
    public MyBroadcastReceiver() {
    }
    @Override
    public void onReceive(Context context, Intent intent) {        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        Toast.makeText(context, "received", Toast.LENGTH_SHORT).show();        //將這條廣播截斷//        abortBroadcast();
    }
}

4. BroadcastReceiver,LocalBroadcastReceiver 區別
廣播接收者:

(1)用於應用間的傳遞訊息
(2)由於跨應用,存在安全問題

本地廣播接收者:

(1)廣播資料在本應用範圍內傳播。  
(2)不用擔心別的應用偽造廣播。  
(3)比傳送全域性廣播更高效、安全。  
(4)無法使用靜態註冊

5. 在 manifest 和程式碼中如何註冊和使用 BroadcastReceiver
(1)在AndroidManifest中靜態註冊,然後直接使用。
(2)程式碼中,透過registerReceiver來註冊。
(3)註冊傳送後,在BroadcastReceiver(自定義一個接收器繼承自BroadcastReceiver)的onReceive中接收廣播並處理廣播。

6. 廣播引起 anr 的時間限制
前臺廣播:BROADCAST_FG_TIMEOUT = 10s
後臺廣播:BROADCAST_BG_TIMEOUT = 60s


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70008155/viewspace-2827062/,如需轉載,請註明出處,否則將追究法律責任。

相關文章