Android中的廣播使用小結

1.廣播的分類

(1)按照發(fā)送的方式分類

  • 標準廣播
    是一種異步的方式來進行傳播的,廣播發(fā)出去之后,所有的廣播接收者幾乎是同一時間收到消息的。他們之間沒有先后順序可言,而且這種廣播是沒法被截斷的。
  • 有序廣播
    是一種同步執(zhí)行的廣播,在廣播發(fā)出去之后,同一時刻只有一個廣播接收器可以收到消息。當廣播中的邏輯執(zhí)行完成后,廣播才會繼續(xù)傳播。

(2)按照注冊的方式分類

  • 動態(tài)注冊廣播
    顧名思義,就是在代碼中注冊的。
  • 靜態(tài)注冊廣播
    動態(tài)注冊要求程序必須在運行時才能進行,有一定的局限性,如果我們需要在程序還沒啟動的時候就可以接收到注冊的廣播,就需要靜態(tài)注冊了。主要是在AndroidManifest中進行注冊。

(3)按照定義的方式分類

  • 系統(tǒng)廣播
    Android系統(tǒng)中內置了多個系統(tǒng)廣播,每個系統(tǒng)廣播都具有特定的intent-filter,其中主要包括具體的action,系統(tǒng)廣播發(fā)出后,將被相應的BroadcastReceiver接收。系統(tǒng)廣播在系統(tǒng)內部當特定事件發(fā)生時,由系統(tǒng)自動發(fā)出。
  • 自定義廣播
    由應用程序開發(fā)者自己定義的廣播

2.動態(tài)注冊廣播的實現(xiàn)

一段比較典型的實現(xiàn)代碼為:

(1)實現(xiàn)一個廣播接收器

public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
        abortBroadcast();
    }
}

主要就是繼承一個BroadcastReceiver,實現(xiàn)onReceive方法,在其中實現(xiàn)自己的業(yè)務邏輯就可以了。

(2)注冊廣播

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;
    private MyBroadcastReceiver myBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        myBroadcastReceiver = new MyBroadcastReceiver();
        registerReceiver(myBroadcastReceiver, intentFilter);
        
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("android.net.conn.CONNECTIVITY_CHANGE");
                sendBroadcast(intent); // 發(fā)送廣播
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(myBroadcastReceiver);
    }
}

這樣MyBroadcastReceiver就可以收到相應的廣播消息了。


3.靜態(tài)注冊廣播的實現(xiàn)

還是用上面的按個MyBroadcastReceiver,只不過這次采用靜態(tài)注冊的方式
在manifest文件中增加如下的代碼:

        <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
        </receiver>

幾個相關解釋:

  • android:exported
    此BroadcastReceiver能否接收其他App發(fā)出的廣播(其默認值是由receiver中有無intent-filter決定的,如果有intent-filter,默認值為true,否則為false);
  • android:name
    此broadcastReceiver類名;
  • android:permission
    如果設置,具有相應權限的廣播發(fā)送方發(fā)送的廣播才能被此broadcastReceiver所接收;
  • android:process
    broadcastReceiver運行所處的進程。默認為App的進程??梢灾付í毩⒌倪M程(Android四大組件都可以通過此屬性指定自己的獨立進程);

4.靜態(tài)注冊廣播與動態(tài)注冊廣播的區(qū)別

  • 靜態(tài)注冊即使App退出,仍然能接收到廣播
  • 動態(tài)注冊時,當Activity退出,就接收不到廣播了
  • 但是靜態(tài)注冊即使App退出,仍然能接收到廣播這種說法自Android 3.1開始有可能不再成立

說明:
Android 3.1開始系統(tǒng)在Intent與廣播相關的flag增加了參數(shù):
A. FLAG_INCLUDE_STOPPED_PACKAGES:包含已經停止的包(停止:即包所在的進程已經退出)
B. FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已經停止的包
自Android3.1開始,系統(tǒng)本身增加了對所有App當前是否處于運行狀態(tài)的跟蹤。在發(fā)送廣播時,不管是什么廣播類型,系統(tǒng)默認直接增加了值為FLAG_EXCLUDE_STOPPED_PACKAGES的flag,導致即使是靜態(tài)注冊的廣播接收器,對于其所在進程已經退出的App,同樣無法接收到廣播。
因此對于系統(tǒng)廣播,由于是系統(tǒng)內部直接發(fā)出的,無法更改此intent的flag值,因此,從3.1開始對于靜態(tài)注冊的接收系統(tǒng)廣播的BroadcastReceiver,如果App進程已經退出,將不能接收到廣播。
但是對于自定義的廣播,可以通過覆寫此flag為FLAG_INCLUDE_STOPPED_PACKAGES,使得靜態(tài)注冊的BroadcastReceiver,即使所在App進程已經退出,也能接收到廣播,并會啟動應用進程,但此時的BroadcastReceiver是新建的。
實現(xiàn)代碼為:

Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
sendBroadcast(intent);

在3.1以前,不少App可能通過靜態(tài)注冊方式監(jiān)聽各種系統(tǒng)廣播,以此進行一些業(yè)務上的處理(如即使App已經退出,仍然能接收到,可以啟動service等..),3.1后,靜態(tài)注冊接受廣播方式的改變,將直接導致此類方案不再可行。于是,通過將Service與App本身設置成不同的進程已經成為實現(xiàn)此類需求的可行替代方案。

API變化詳情參見Android官方文檔


5.有序廣播

前面介紹過,有序廣播是異步方式傳播的。指的是發(fā)送出去的廣播被BroadcastReceiver按照先后循序接收。有序廣播的定義過程與普通廣播無異,只是其的主要發(fā)送方式變?yōu)椋?/p>

    /**
     * Broadcast the given intent to all interested BroadcastReceivers, delivering
     * them one at a time to allow more preferred receivers to consume the
     * broadcast before it is delivered to less preferred receivers.  This
     * call is asynchronous; it returns immediately, and you will continue
     * executing while the receivers are run.
     * @param intent The Intent to broadcast; all receivers matching this
     *               Intent will receive the broadcast.
     * @param receiverPermission (optional) String naming a permissions that
     *               a receiver must hold in order to receive your broadcast.
     *               If null, no permission is required.
     */
    public abstract void sendOrderedBroadcast(Intent intent,
            @Nullable String receiverPermission);

其他的幾種重載的方法可以參見官方文檔。
有序廣播的主要特點:

  • 同級別接收是隨機的(結合下一條)
  • 同級別動態(tài)注冊(代碼中注冊)高于靜態(tài)注冊(AndroidManifest中注冊)
  • 排序規(guī)則為:將當前系統(tǒng)中所有有效的動態(tài)注冊和靜態(tài)注冊的BroadcastReceiver按照priority屬性值從大到小排序
  • 先接收的BroadcastReceiver可以對此有序廣播進行截斷,使后面的BroadcastReceiver不再接收到此廣播,也可以對廣播進行修改,使后面的BroadcastReceiver接收到廣播后解析得到錯誤的參數(shù)值。當然,一般情況下,不建議對有序廣播進行此類操作,尤其是針對系統(tǒng)中的有序廣播。實現(xiàn)截斷的代碼為:
abortBroadcast();

6.標準廣播

標準廣播的主要特點為:

  • 同級別接收先后是隨機的(無序的)
  • 級別低的后接收到廣播
  • 接收器不能截斷廣播的繼續(xù)傳播,也不能處理廣播
  • 同級別動態(tài)注冊(代碼中注冊)高于靜態(tài)注冊(AndroidManifest中注冊)

7.廣播的安全性問題

由前文闡述可知,Android中的廣播可以跨進程甚至跨App直接通信,且exported屬性在有intent-filter的情況下默認值是true,由此將可能出現(xiàn)的安全隱患如下:

  • 其他App可能會針對性的發(fā)出與當前App intent-filter相匹配的廣播,由此導致當前App不斷接收到廣播并處理;
  • 其他App可以注冊與當前App一致的intent-filter用于接收廣播,獲取廣播具體信息。

無論哪種情形,這些安全隱患都確實是存在的。由此,業(yè)界常見的一些增加安全性的方案包括:

  • 對于同一App內部發(fā)送和接收廣播,將exported屬性人為設置成false,使得非本App內部發(fā)出的此廣播不被接收;
  • 在廣播發(fā)送和接收時,都增加上相應的permission,用于權限驗證;
  • 發(fā)送廣播時,指定特定廣播接收器所在的包名,具體是通過intent.setPackage(packageName)指定,這樣此廣播將只會發(fā)送到此包中的App內與之相匹配的有效廣播接收器中。
  • 采用LocalBroadcastManager的方式

下文分析幾種常見的處理方案。


8.本地廣播LocalBroadcastManager

(1)LocalBroadcastManager的概念

為了解決安全性問題,Android在android.support.v4.content包中引入了LocalBroadcastManager。按照官方文檔的描述,使用LocalBroadcastManager有如下的優(yōu)勢:

Helper to register for and send broadcasts of Intents to local objects within your process. This has a number of advantages over sending global broadcasts with sendBroadcast(Intent):
(1)You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data.
(2)It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit.
(3)It is more efficient than sending a global broadcast through the system.

也就是說,使用該機制發(fā)出的廣播只能夠在應用程序內部進行傳遞,并且廣播接收器也只能接收來自本地應用程序發(fā)出的廣播,這樣所有的安全性問題都不存在了。

(2)LocalBroadcastManager使用范例

public class MainActivity extends AppCompatActivity {
    private IntentFilter intentFilter;
    private LocalReceiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        localBroadcastManager = LocalBroadcastManager.getInstance(this); // 獲取實例
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent); // 發(fā)送本地廣播
            }
        });
        intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
        localReceiver = new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver, intentFilter); // 注冊本地廣播監(jiān)聽器
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        localBroadcastManager.unregisterReceiver(localReceiver);
    }

    class LocalReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
        }
    }
}

LocalBroadcastManager的官方文檔鏈接


9.自定義廣播權限

(1)一個自定義權限的示例

   <permission
        android:name="com.self.define.permission.test"
        android:label="BroadcastReceiverPermission"
        android:protectionLevel="signature">
    </permission>

在自定義權限時,通常會指定protectionLevel屬性,常用的如下:

  • normal:默認的,應用安裝前,用戶可以看到相應的權限,但無需用戶主動授權。
  • dangerous:normal安全級別控制以外的任何危險操作。需要dangerous級別權限時,Android會明確要求用戶進行授權。常見的如:網絡使用權限,相機使用權限及聯(lián)系人信息使用權限等。
  • signature:它要求權限聲明應用和權限使用應用使用相同的keystore進行簽名。如果使用同一keystore,則該權限由系統(tǒng)授予,否則系統(tǒng)會拒絕。并且權限授予時,不會通知用戶。它常用于應用內部。把protectionLevel聲明為signature。如果別的應用使用的不是同一個簽名文件,就沒辦法使用該權限,從而保護了自己的接收者。

(2)廣播接收者

如果采用靜態(tài)注冊的方式:

         <receiver
            android:name=".common.MyBroadcastReceiver"
            android:exported="false"
            android:permission="com.self.define.permission.test">
            <intent-filter>
                <action android:name="action.name"/>
            </intent-filter>
        </receiver>

如果采用動態(tài)注冊的方式
相應的API有:

(1)registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)
Register to receive intent broadcasts, to run in the context of scheduler.
(2)sendBroadcast(Intent intent, String receiverPermission)
Broadcast the given intent to all interested BroadcastReceivers, allowing an optional required permission to be enforced.
(3)sendOrderedBroadcast(Intent intent, String receiverPermission)
Broadcast the given intent to all interested BroadcastReceivers, delivering them one at a time to allow more preferred receivers to consume the broadcast before it is delivered to less preferred receivers.

receiver是動態(tài)注冊時,需要創(chuàng)建自己的使用權限,并且將protectionLevel設置為signature。這樣,當別的應用和receiver所在的應用使用的簽名不一樣時,便不會啟動該receiver。例如:

MyBroadcastReceiver receiver = new MyBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcast.test");
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
//注冊receiver時,直接指定發(fā)送者應該具有的權限。不然外部應用依舊可以觸及到receiver
registerReceiver(receiver, intentFilter, "com.self.define.permission.test", null);

在注冊的時候,最關鍵的一點是用registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)進行注冊,而不是平常用的是registerReceiver(BroadcastReceiver, IntentFilter)。相較于后者,前者在注冊的時候要求了發(fā)送者必須具有的權限。如果發(fā)送者沒有該權限,那么發(fā)送者發(fā)送的廣播即使經過IntentFilter的過濾,也不會被receiver接收。此時如果再自定義一個權限,并且將權限的protectionLevel設置為signature,那么外部應用便無法使用該權限,也就無法觸及到該receiver。
發(fā)送廣播的代碼為:

Intent intent = new Intent("com.example.broadcast.test.permission");
sendBroadcast(intent,"com.self.define.permission.test");

另外需要在manifest文件中定義權限并聲明

<permission
    android:name="com.self.define.permission.test"
    android:label="BroadcastReceiverPermission"
    android:protectionLevel="signature">
</permission>
<uses-permission android:name="com.self.define.permission.test"/>

切記要在<application>同級的位置配置使用到的權限


10.其他幾點補充

(1)不同注冊方式onReceive(context, intent)中的context具體類型

兩個參數(shù)的官方定義為:

Context: The Context in which the receiver is running.
Intent: The Intent being received.

BroadcastReceiver本身不是Context,其內部也不含有Context,但在onReceive(Context context, Intent intent)中有context參數(shù)。這個context隨著receiver的注冊方式的不同而不同:
靜態(tài)注冊:context為ReceiverRestrictedContext
動態(tài)注冊:context為Activity的context
LocalBroadcastManager的動態(tài)注冊:context為Application的context

(2)ANR問題

官方文檔的描述:
When it runs on the main thread you should never perform long-running operations in it (there is a timeout of 10 seconds that the system allows before considering the receiver to be blocked and a candidate to be killed). You cannot launch a popup dialog in your implementation of onReceive().


參考

BroadcastReceiver文檔鏈接
四大組件之廣播(Broadcast)
BroadcastReceiver與自定義權限
Android總結篇系列:Android廣播機制
面試題-BroadcastReceiver
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容