Android的IPC機制(六)—— BroadcastReceiver的使用

yangxi_001發表於2017-12-19

綜述

  在Android的四大元件中除了ContentProvider能夠用於程式間的通訊外,還有一個也能夠用於程式間的通訊,那就是BroadcastReceiver。BroadcastReceiver翻譯成中文為廣播接收器,既然作為廣播接收器,那麼必然就有Broadcast。在Android中,Broadcast是一種廣泛運用的在應用程式之間傳輸資訊的機制。而BroadcastReceiver則是對傳送出來的 Broadcast進行過濾接受並響應的一類元件。在 Android 裡面有各種各樣的廣播,比如電池的使用狀態,電話的接收和簡訊的接收都會產生一個廣播,應用程式開發者也可以監聽這些廣播並做出程式邏輯的處理。

生命週期

  對於BroadcastReceiver的生命週期也是非常的簡單。它的生命週期只存在於onReceive方法中。對於這個onReceive方法它也是執行在主執行緒中。所以在onReceive方法中不能進行的耗時的操作。否則就會出現ANR(Application Not Responding)。由於這個ANR的限制對於onReceive方法最多可執行的時間為10秒左右。並且由於BroadcastReceiver的生命週期隨著onReceive方法的結束而結束。所以我們不能再onReceive方法中去建立一個執行緒來執行任務。因為onReceive方法執行完畢,這時候這個BroadcastReceiver 也就結束了。也就無法處理非同步的結果。如果這個BroadcastReceiver在獨立的程式中,它所在程式也很容易在系統需要記憶體時被優先殺死 , 因為它屬於空程式 ( 沒有任何活動元件的程式 ). 那麼正在工作的子執行緒也會被殺死 。若是我們需要完成一個比較耗時任務的話,我們可以通過傳送Intent給Service,並且由這個Service來完成這項任務。

使用方法

註冊方式

  對於BroadcastReceiver有兩種註冊方式,一種是動態註冊,一種是靜態註冊。靜態註冊就是在AndroidManifest檔案中進行註冊。而動態註冊則是在程式中通過registerReceiver方法註冊。對於這兩種註冊方法的優先順序來說,動態註冊的優先順序要高於靜態註冊的優先順序。那麼下面我們來看一下這兩種註冊方式。 
  1. 靜態註冊

<receiver
    android:name=".MyReceiver">
    <intent-filter>
        <action android:name="com.example.ljd.BROADCASTRECEIVER" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</receiver>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  2. 動態註冊

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.ljd.BROADCASTRECEIVER");
registerReceiver(myReceiver,intentFilter);
  • 1
  • 2
  • 3

  對於動態註冊,當我們不在使用的這個broadcastreceiver的時候我們還需要對它解除註冊。

unregisterReceiver(myReceiver);
  • 1

廣播型別

  在這裡我們介紹一下其中最常用的兩種廣播:普通廣播和有序廣播。它們分別是通過sendBroadcast和sendOrderedBroadcast進行傳送。那麼現在我們就來看一下這兩種方式的用法與區別。

普通廣播(Normal Broadcast)

  我們可以通過sendBroadcast方法傳送一個普通廣播。對於普通廣播來說,BroadcastReceiver的優先順序一樣的話它們接收廣播先後順序是隨機的,並且在廣播的傳輸過程中我們無法截斷廣播。 
  下面我們做一個實驗。我們建立三個BroadcastReceiver,使其執行在三個不同的程式內。並且我們採用靜態註冊方式。 
  第一個BroadcastReceiver

package com.example.ljd.broadcastreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MyReceiver1 extends BroadcastReceiver {

    private final String TAG = "MyReceiver1";
    public MyReceiver1() {
    }
    @Override
    public void onReceive(Context context, Intent intent) {

        if (Constant.BROADCAST_ACTION.equals(intent.getAction())){
            Log.d(TAG,intent.getStringExtra(Constant.CONFERENCE_KEY));
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

  第二個BroadcastReceiver

package com.example.ljd.broadcastreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MyReceiver2 extends BroadcastReceiver {
    private final String TAG = "MyReceiver2";
    public MyReceiver2() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (Constant.BROADCAST_ACTION.equals(intent.getAction())){
            Log.d(TAG, intent.getStringExtra(Constant.CONFERENCE_KEY));
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

  第三個BroadcastReceiver

package com.example.ljd.broadcastreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MyReceiver3 extends BroadcastReceiver {
    private final String TAG = "MyReceiver3";
    public MyReceiver3() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (Constant.BROADCAST_ACTION.equals(intent.getAction())){
            Log.d(TAG,intent.getStringExtra(Constant.CONFERENCE_KEY));
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

  對三個BroadcastReceiver進行靜態註冊。

<receiver
    android:name=".MyReceiver1"
    android:process=":receiver1">
    <intent-filter>
        <action android:name="com.example.ljd.BROADCASTRECEIVER" />

        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</receiver>
<receiver
    android:name=".MyReceiver3"
    android:process=":receiver3">
    <intent-filter>
        <action android:name="com.example.ljd.BROADCASTRECEIVER" />

        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</receiver>
<receiver
    android:name=".MyReceiver2"
    android:process=":receiver2">
    <intent-filter>
        <action android:name="com.example.ljd.BROADCASTRECEIVER" />

        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</receiver>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

  我們在建立一個Activity,在這個Activity裡面我們建立一個BroadcastReceiver並且對其進行動態註冊。然後我們採用sendBroadcast進行傳送廣播。下面看一下Activity程式碼。

package com.example.ljd.broadcastreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";

    private MyReceiver myReceiver = new MyReceiver();

    class MyReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            if (Constant.BROADCAST_ACTION.equals(intent.getAction())){
                Log.d(TAG,intent.getStringExtra(Constant.CONFERENCE_KEY));
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
        ButterKnife.bind(this);
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Constant.BROADCAST_ACTION);
        registerReceiver(myReceiver,intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ButterKnife.bind(this);
        unregisterReceiver(myReceiver);
    }

    @OnClick(R.id.send_button)
    public void onClickButton(){
        Intent intent = new Intent(Constant.BROADCAST_ACTION);
        intent.putExtra(Constant.CONFERENCE_KEY,"你好,明天九點半101會議室開會。");
        sendBroadcast(intent);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

  我們傳送一條通知開會的廣播,並且接收到廣播後列印出來。由於程式碼非常簡單這裡就不在進行說明,從下面的結果我們可以看出,無論是採用靜態註冊還是動態註冊,它們接收到的先後順序是隨機的。我們傳送了三次廣播。這時候可以通過DDMS檢視一下日誌資訊。   
結果1: 
這裡寫圖片描述
結果2: 
這裡寫圖片描述
結果3: 
這裡寫圖片描述
  現在我們對MyReceiver2中的OnReceive新增一個abortBroadcast方法,這個方法是用來截斷廣播的。然後我們在看一下執行結果 
這裡寫圖片描述
  我們可以看到雖然系統給我們丟擲了了執行時異常,但是我們還是準確無誤的接收到了廣播。而這個異常是在abortBroadcast裡面的checkSynchronousHint方法中丟擲的,這個checkSynchronousHint方法是用來檢測廣播接收者是否同步接收廣播。由於我們接收廣播是隨機的,所以丟擲執行時異常。 
  現在又有一種情形,如果我們只是希望動態建立的BroadcastReceiver進行接收廣播,而靜態建立的BroadcastReceiver不進行接收。我們可以在傳送廣播的Intent中設定一個flag:FLAG_RECEIVER_REGISTERED_ONLY。

intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
  • 1

  這時候我們可以看到只有動態註冊的廣播才接收到訊息。 
這裡寫圖片描述

有序廣播(Ordered Broadcast)

  對於有序廣播而言,BroadcastReceiver是按照接收者的優先順序接收廣播 , 優先順序在 intent-filter 中的 priority 中宣告 。priority的值從-1000 到1000 之間 , 值越大 , 優先順序越高。如果我們沒有設定priority的話,那麼對於BroadcastReceiver的優先順序則是先註冊的要大於後註冊的,動態註冊優先順序大於靜態註冊的優先順序。對於剛才的例子來說在有序廣播中它們的優先順序就是MyReceiver>MReceiver1>MReceiver3>MReceiver2。並且在在有序廣播中我們可以終止廣播 ,接收者也能夠篡改內容。 
  我們可以通過sendOrderedBroadcast進行傳送一個有序廣播,他有兩個過載方法。

public void sendOrderedBroadcast(Intent intent,
            String receiverPermission)

public void sendOrderedBroadcast(
        Intent intent, String receiverPermission, BroadcastReceiver resultReceiver,
        Handler scheduler, int initialCode, String initialData,
        Bundle initialExtras)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

這裡我們對裡面的引數進行一下說明:  
1. intent:所有匹配的這個intent的BroadcastReceiver將接收廣播。 
2. receiverPermission:這個是許可權,一個接收器必須持有接收我們的廣播的許可權。如果BroadcastReceiver沒有設定任何許可權,這裡為null即可。 
3. resultReceiver :設定一個BroadcastReceiver 來當作最後的廣播接收器。 
4. initialCode:結果程式碼的初始值。通常為 Activity.RESULT_OK ,這個值是 -1 。也可以為其他 int 型,如 0,1,2 ; 
5. initialData: 一種結果資料的初始值。為String 型別 ; 
6. initialExtras:一種結果額外的初始值。為Bundle型別。 
   
  對於我們沒有設定priority屬性的BroadcastReceiver的優先順序情況很簡單這裡也就不在進行驗證。現在我們來修改上面程式碼來看一下是如何攔截廣播並且修改廣播中資料的。我們首先對動態註冊的優先順序設為0。對上面的MyReceiver1,MyReceiver2,MyReceiver3的優先順序分別設為3,2,1。然後我們通過sendOrderedBroadcast傳送一個有序廣播。

Intent intent = new Intent(Constant.BROADCAST_ACTION);
intent.putExtra(Constant.CONFERENCE_KEY, "你好,明天九點半101會議室開會。");

Bundle bundle = new Bundle();
bundle.putString(Constant.DINE_KEY, "今天晚上聚餐");
intent.putExtras(bundle);
sendOrderedBroadcast(intent,null,null,null, Activity.RESULT_OK ,null,bundle);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  在MyReceiver1的onReceive方法中修改如下:

if (Constant.BROADCAST_ACTION.equals(intent.getAction())){
     Log.d(TAG, intent.getStringExtra(Constant.CONFERENCE_KEY));
     Log.d(TAG, getResultExtras(true).getString(Constant.DINE_KEY));
     Bundle bundle = getResultExtras(true);
     bundle.putString(Constant.DINE_KEY, "後天中午一起聚餐");
     setResultExtras(bundle);
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  在MyReceiver2的onReceive方法中修改如下:

if (Constant.BROADCAST_ACTION.equals(intent.getAction())){
    Log.d(TAG, intent.getStringExtra(Constant.CONFERENCE_KEY));
    Log.d(TAG, getResultExtras(true).getString(Constant.DINE_KEY));
    abortBroadcast();
}
  • 1
  • 2
  • 3
  • 4
  • 5

  getResultExtras:優先順序低的BroadcastReceiver可以通過這個方法獲取到最新的經過處理的資訊集合。 
  下面我們來看一下執行結果: 
這裡寫圖片描述
  在這裡我們可以看到由於在MyReceiver2中的onReceive方法中設定了abortBroadcast,比MyReceiver2優先順序低的BroadcastReceiver接收不到廣播。並且在MyReceiver1中將“今天晚上聚餐”成功修改為“後天中午聚餐”。

總結

  BroadcastReceiver除了可以接收我們自己傳送的廣播以外,還能夠接收一些系統的廣播。例如對網路環境的監測,手機電量的監測,裝置重啟的監測等等。也就是說BroadcastReceiver可以方便應用程式和系統,應用程式之間,應用程式內的通訊。所以對於我們單個應用程式來說它是存在安全性的問題。不過我們我們可以在傳送廣播時指定接收者必須具備的permission。當然我們也可以通過使用LocalBroadcastManager來解決安全性問題。對於LocalBroadcastManager使用也很簡單。它是一個單例模式,可以通過LocalBroadcastManager.getInstance(context)獲取LocalBroadcastManager物件。然後通過LocalBroadcastManager裡面的registerReceiver方法進行BroadcastReceiver進行註冊。也就是說使用LocalBroadcastManager必須進行動態註冊。

原始碼下載

相關文章