第一章 四大組件 之 BroadcastReceiver(四)

第一章 四大組件

第四組件 BroadcastReceiver

(1)定義

廣播,是一個全局的監聽器,屬於Android四大組件之一,Android 廣播分爲兩個角色:廣播發送者、廣播接收者

(2)作用

監聽 / 接收 應用 App 發出的廣播消息,並 做出響應

(3)應用場景

3.1)Android不同組件間的通信(含 :應用內 / 不同應用之間)
3.2)多線程通信
3.3)與 Android 系統在特定情況下的通信。如:電話呼入時、網絡可用時

(4)實現原理

Android中的廣播使用了設計模式中的觀察者模式:基於消息的發佈 / 訂閱事件模型
Android將廣播的發送者 和 接收者 解耦,使得系統方便集成,更易擴展
模型中有3個角色:
1、消息訂閱者(廣播接收者)
2、消息發佈者(廣播發布者)
3、消息中心(AMS,即Activity Manager Service)
在這裏插入圖片描述
觀察者模式:
觀察者設計模式定義了對象間的一種一對多的組合關係,以便一個對象的狀態發生變化時,所有依賴於它的對象都得到通知並自動刷新。

(5)使用流程

【附錄2:Boardcast Receiver使用流程】

5.1 自定義廣播接受者BroadcastReceiver

繼承BroadcastReceivre基類,必須複寫抽象方法onReceive()方法
1.廣播接收器接收到相應廣播後,會自動回調 onReceive() 方法
2.一般情況下,onReceive方法會涉及 與 其他組件之間的交互,如發送Notification、啓動Service等
3.默認情況下,廣播接收器運行在 UI 線程,因此,onReceive()方法不能執行耗時操作,否則將導致ANR

// 繼承BroadcastReceivre基類
public class mBroadcastReceiver extends BroadcastReceiver {
    // 複寫onReceive()方法
    // 接收到廣播後,則自動調用該方法
    @Override
    public void onReceive(Context context, Intent intent) {
        //寫入接收廣播後的操作
    }
}

如下爲監聽網絡狀態變化的廣播接收器

    class NetworkChangeReceiver extends BroadcastReceiver{//廣播接收器類

        @Override
        public void onReceiver(Context context,Intent intent){

            //這裏需要權限,需要在AndroidManifest.xml中進行網絡訪問權限申請:
            //<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
            ConnectivityManager connectionManager = (ConnectivityManager)
            getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo();

            if (networkInfo != null && networkInfo.isAvailable()) {

                  //有網
                  Toast.makeText(context, "network is available",Toast.LENGTH_SHORT).show();

            } else {

                  //無網
                  Toast.makeText(context, "network is unavailable",
                  Toast.LENGTH_SHORT).show();
            }
        }

    }

5.2 廣播接收器註冊

註冊的方式分爲兩種:靜態註冊、動態註冊

(1)靜態註冊

使用:在AndroidManifest.xml裏通過標籤聲明
特點:常駐、不受任何組件的生命週期影響(應用程序關閉後,如果有信息廣播,程序依舊會被系統調用),耗電,佔內存
應用場景:需要時刻監聽廣播

<receiver
    android:enabled=["true" | "false"]
//此broadcastReceiver能否接收其他App的發出的廣播
    //默認值是由receiver中有無intent-filter決定的:如果有intent-filter,默認值爲true,否則爲false
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:label="string resource"
    //繼承BroadcastReceiver子類的類名(廣播具體標識)
    android:name=".mBroadcastReceiver"
    //具有相應權限的廣播發送者發送的廣播才能被此BroadcastReceiver所接收;
    android:permission="string"
    //BroadcastReceiver運行所處的進程
    //默認爲app的進程,可以指定獨立的進程
    //注:Android四大基本組件都可以通過此屬性指定自己的獨立進程
    android:process="string" >

    //用於指定此廣播接收器將接收的廣播類型
// IntentFilter翻譯成中文就是“意圖過濾器”,主要用來過濾隱式意圖。當用戶進行一項操作的時候,Android系統會根據配置的 “意圖過濾器” 來尋找可以響應該操作的組件,服務。
    //本示例中給出的是用於接收網絡狀態改變時發出的廣播
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>

當此 App首次啓動時,系統會自動實例化mBroadcastReceiver類,並註冊到系統中。

(2)動態註冊

使用:在代碼中調用Context.registerReceiver()方法
特點:非常駐、靈活、跟隨組件的生命週期變化(組件結束,廣播結束。故在組件結束前,必須移除廣播接收器)
應用場景
2.1)源碼

    // 選擇在Activity生命週期方法中的onResume()中註冊
    @Override
    protected void onResume(){
        super.onResume();
        // 1. 實例化BroadcastReceiver子類 &  IntentFilter(意圖過濾器,接受廣播類型)
        mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
        // 2. 設置接收廣播的類型(網絡變化廣播)
        intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
        // 3. 動態註冊:調用Context的registerReceiver()方法
        registerReceiver(mBroadcastReceiver, intentFilter);
    }
// 註冊廣播後,要在相應位置記得銷燬廣播
// 即在onPause() 中unregisterReceiver(mBroadcastReceiver)
// 當此Activity實例化時,會動態將MyBroadcastReceiver註冊到系統中
// 當此Activity銷燬時,動態註冊的MyBroadcastReceiver將不再接收到相應的廣播。
    @Override
    protected void onPause() {
        super.onPause();
        //銷燬在onResume()方法中的廣播
        unregisterReceiver(mBroadcastReceiver);
    }
}

2.2)動態廣播最好在Activity 的 onResume()註冊、onPause()註銷。

  1. 動態廣播有註冊必須有註銷,否則會導致內存泄露;重複註冊,註銷也不允許。
  2. 在onResume()註冊、onPause()註銷是因爲onPause()在App死亡前一定會被執行,從而保證廣播在App死亡前一定會被註銷,從而防止內存泄露。

5.3 廣播發送者向AMS發送廣播

(1)廣播的發送

廣播 是 用”意圖(Intent)“標識,定義廣播的本質 = 定義廣播所具備的“意圖(Intent)”
廣播發送 = 廣播發送者 將此廣播的“意圖(Intent)”通過sendBroadcast()方法發送出去

(2)廣播類型
a)普通廣播(Normal Broadcast)

開發者自身定義 intent的廣播(最常用)。
1、發送者發送自定義廣播

Intent intent = new Intent();
//對應BroadcastReceiver中intentFilter的action
intent.setAction(BROADCAST_ACTION);
//發送廣播
sendBroadcast(intent);

2、接受者註冊廣播

    <receiver
    //此廣播接收者類是mBroadcastReceiver
    android:name=".mBroadcastReceiver" >
    //用於接收網絡狀態改變時發出的廣播
    <intent-filter>
        <action android:name="BROADCAST_ACTION" />
    </intent-filter>
</receiver>

若被註冊了的廣播接收者中註冊時intentFilter的action與上述匹配,則會接收此廣播(即進行回調onReceive())。

b)系統廣播(System Broadcast)

Android中內置了多個系統廣播:只要涉及到手機的基本操作(如開機、網絡狀態變化、拍照等等),都會發出相應的廣播
每個廣播都有特定的Intent - Filter(包括具體的action),Android系統廣播action如下:(部分)

系統操作 Action
監聽網絡變化 android.net.conn.CONNECTIVITY_CHANGE
電池電量低 Intent.ACTION_BATTERY_LOW
屏幕鎖屏 Intent.ACTION_CLOSE_SYSTEM_DIALOGS
屏幕被關閉 Intent.ACTION_SCREEN_OFF
屏幕被打開 Intent.ACTION_SCREEN_ON

注:當使用系統廣播時,只需要在註冊廣播接收者時定義相關的action即可,並不需要手動發送廣播,當系統有相關操作時會自動進行系統廣播

c)有序廣播(Ordered Broadcast)

發送出去的廣播被廣播接收者按照先後順序接收(按照Priority屬性值從大-小排序)

sendOrderedBroadcast(intent);

有序廣播與無序廣播區別:

  • 無序廣播
    context.sendBroadcast(Intent)方法發送的廣播,不可被攔截,當然發送的數據,接收者是不能進行修改的。
  • 有序廣播
    context.sendOrderBroadcast(Intent)方法發送的廣播,可被攔截,而且接收者是可以修改其中要發送的數據,修改和添加都是可以的,這就意味着優先接收者對數據修改之後,下一個接收者接受的數據是上一個接收者已經修改了的。
d)本地廣播(Local Broadcast)

Android中的廣播可以跨App直接通信(exported對於有intent-filter情況下默認值爲true),App應用內廣播可理解爲一種局部廣播,廣播的發送者和接收者都同屬於一個App。
(1)將全局廣播設置爲局部廣播
(a)註冊廣播時將exported屬性設置爲false,使得非本App內部發出的此廣播不被接收;
(b)在廣播發送和接收時,增設相應權限permission,用於權限驗證;
(c)發送廣播時指定該廣播接收器所在的包名,此廣播將只會發送到此包中的App內與之相匹配的有效廣播接收器中。(通過intent.setPackage(packageName)指定報名)
(2) 使用封裝好的LocalBroadcastManager類
使用方式上與全局廣播幾乎相同,只是註冊/取消註冊廣播接收器和發送廣播時將參數的context變成了LocalBroadcastManager的單一實例

//註冊應用內廣播接收器
//步驟1:實例化BroadcastReceiver子類 & IntentFilter mBroadcastReceiver 
mBroadcastReceiver = new mBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
//步驟2:實例化LocalBroadcastManager的實例
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
//步驟3:設置接收廣播的類型 
        intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
//步驟4:調用LocalBroadcastManager單一實例的registerReceiver()方法進行動態註冊 
        localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
//取消註冊應用內廣播接收器
        localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
//發送應用內廣播
        Intent intent = new Intent();
        intent.setAction(BROADCAST_ACTION);
        localBroadcastManager.sendBroadcast(intent);

注:對於不同註冊方式的廣播接收器回調OnReceive(Context context,Intent intent)中的context返回值是不一樣的
(3) 本地廣播與全局廣播的差別
(3.1)定義
全局廣播BroadcastReceiver是針對應用間、應用與系統間、應用內部進行通信的一種方式
本地廣播LocalBroadcastReceiver僅在自己的應用內發送接收廣播,也就是隻有自己的應用能收到,數據更加安全廣播只在這個程序裏,而且效率更高。
(3.2)使用
全局廣播:
1)製作intent(可以攜帶參數)
2)使用sendBroadcast()傳入intent;
3)製作廣播接收器類繼承BroadcastReceiver重寫onReceive方法(或者可以匿名內部類啥的)
4)在java中(動態註冊)或者直接在Manifest中註冊廣播接收器(靜態註冊)使用registerReceiver()傳入接收器和intentFilter
5)取消註冊可以在OnDestroy()函數中,unregisterReceiver()傳入接收器
本地廣播:
1)LocalBroadcastReceiver不能靜態註冊,只能採用動態註冊的方式。
2)在發送和註冊的時候採用,LocalBroadcastManager的sendBroadcast方法和registerReceiver方法

(6)源碼角度分析廣播機制

(6.1)系統廣播(BoardcastReceiver)源碼分析

  • 廣播接受者BoardcastReceiver,並重寫onReceive()方法,通過Binder 機制在AMS註冊
  • 廣播發送者 通過Binder 機制向AMS發送廣播
  • AMS根據廣播發送者要求,在已註冊列表中,尋找合適的廣播接收器(尋找依據:IntentFilter)並將廣播發送到合適的廣播接受者相應的消息循環隊列中
  • 廣播接受者通過消息循環,拿到此廣播,並回調onReceive()方法。
    其中廣播發送者與廣播接受者的執行是異步的,即廣播發送者不會關心有無接受者接收&也不確定接受者何時才能接收到。

(6.2)本地廣播(LocalBoardcastManager)源碼分析

1、LocalBroadcastManager源碼

(1)構造函數

public static LocalBroadcastManager getInstance(Context context) {
    synchronized (mLock) {
        if (mInstance == null) {
            mInstance = new LocalBroadcastManager(context.getApplicationContext());
        }
        return mInstance;
    }
}
 
private LocalBroadcastManager(Context context) {
    mAppContext = context;
    mHandler = new Handler(context.getMainLooper()) {
 
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_EXEC_PENDING_BROADCASTS:
                    executePendingBroadcasts();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };
}

先看構造函數,單例實現因而私有化構造函數。
注意的是基於主線程的 Looper 新建了一個 Handler,handleMessage中會調用接收器對廣播的消息進行處理,也是 LocalBroadcastManager 的核心部分,具體見後面executePendingBroadcasts()介紹。
(2)註冊接收器

HashMap<BroadcastReceiver, ArrayList<IntentFilter>> mReceivers
            = new HashMap<BroadcastReceiver, ArrayList<IntentFilter>>();
HashMap<String, ArrayList<ReceiverRecord>> mActions
            = new HashMap<String, ArrayList<ReceiverRecord>>();
 
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    synchronized (mReceivers) {
        ReceiverRecord entry = new ReceiverRecord(filter, receiver);
        ArrayList<IntentFilter> filters = mReceivers.get(receiver);
        if (filters == null) {
            filters = new ArrayList<IntentFilter>(1);
            mReceivers.put(receiver, filters);
        }
        filters.add(filter);
        for (int i=0; i<filter.countActions(); i++) {
            String action = filter.getAction(i);
            ArrayList<ReceiverRecord> entries = mActions.get(action);
            if (entries == null) {
                entries = new ArrayList<ReceiverRecord>(1);
                mActions.put(action, entries);
            }
            entries.add(entry);
        }
    }
}  

mReceivers 存儲廣播和過濾器信息,以BroadcastReceiver作爲 key,IntentFilter鏈表作爲 value。mReceivers 是接收器和IntentFilter的對應表,主要作用是方便在unregisterReceiver(…)取消註冊,同時作爲對象鎖限制註冊接收器、發送廣播、取消接收器註冊等幾個過程的併發訪問。
mActions 以Action爲 key,註冊這個Action的BroadcastReceiver鏈表爲 value。mActions 的主要作用是方便在廣播發送後快速得到可以接收它的BroadcastReceiver。
(3)發送廣播

public boolean sendBroadcast(Intent intent) {
    synchronized (mReceivers) {
        final String action = intent.getAction();
        final String type = intent.resolveTypeIfNeeded(mAppContext.getContentResolver());
        final Uri data = intent.getData();
        final String scheme = intent.getScheme();
        final Set<String> categories = intent.getCategories();
        ……
        ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
        if (entries != null) {
            if (debug) Log.v(TAG, "Action list: " + entries);
 
            ArrayList<ReceiverRecord> receivers = null;
            for (int i=0; i<entries.size(); i++) {
                ReceiverRecord receiver = entries.get(i);
                if (receiver.broadcasting) {
                    if (debug) {
                        Log.v(TAG, "  Filter's target already added");
                    }
                    continue;
                }
 
                int match = receiver.filter.match(action, type, scheme, data,
                        categories, "LocalBroadcastManager");
                if (match >= 0) {
                    if (debug) Log.v(TAG, "  Filter matched!  match=0x" +
                            Integer.toHexString(match));
                    if (receivers == null) {
                        receivers = new ArrayList<ReceiverRecord>();
                    }
                    receivers.add(receiver);
                    receiver.broadcasting = true;
                } else {
                    ……
                }
            }
 
            if (receivers != null) {
                for (int i=0; i<receivers.size(); i++) {
                    receivers.get(i).broadcasting = false;
                }
                mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
                if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
                    mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                }
                return true;
            }
        }
    }
    return false;
}

先根據Action從mActions中取出ReceiverRecord列表,循環每個ReceiverRecord判斷 filter 和 intent 中的 action、type、scheme、data、categoried 是否 match(intentFilter的match機制),是的話則保存到receivers列表中,發送 what 爲MSG_EXEC_PENDING_BROADCASTS的消息,通過 Handler 去處理。
(4)消息處理

Java
private void executePendingBroadcasts() {
    while (true) {
        BroadcastRecord[] brs = null;
        synchronized (mReceivers) {
            final int N = mPendingBroadcasts.size();
            if (N <= 0) {
                return;
            }
            brs = new BroadcastRecord[N];
            mPendingBroadcasts.toArray(brs);
            mPendingBroadcasts.clear();
        }
        for (int i=0; i<brs.length; i++) {
            BroadcastRecord br = brs[i];
            for (int j=0; j<br.receivers.size(); j++) {
                br.receivers.get(j).receiver.onReceive(mAppContext, br.intent);
            }
        }
    }
}

private void executePendingBroadcasts() {
    while (true) {
        BroadcastRecord[] brs = null;
        synchronized (mReceivers) {
            final int N = mPendingBroadcasts.size();
            if (N <= 0) {
                return;
            }
            brs = new BroadcastRecord[N];
            mPendingBroadcasts.toArray(brs);
            mPendingBroadcasts.clear();
        }
        for (int i=0; i<brs.length; i++) {
            BroadcastRecord br = brs[i];
            for (int j=0; j<br.receivers.size(); j++) {
                br.receivers.get(j).receiver.onReceive(mAppContext, br.intent);
            }
        }
    }
}

以上爲消息處理的函數。mPendingBroadcasts轉換爲數組BroadcastRecord,循環每個receiver,調用其onReceive函數,這樣便完成了廣播的核心邏輯。
(5)取消註冊

public void unregisterReceiver(BroadcastReceiver receiver) {
    synchronized (mReceivers) {
        ArrayList<IntentFilter> filters = mReceivers.remove(receiver);
        if (filters == null) {
            return;
        }
        for (int i=0; i<filters.size(); i++) {
            IntentFilter filter = filters.get(i);
            for (int j=0; j<filter.countActions(); j++) {
                String action = filter.getAction(j);
                ArrayList<ReceiverRecord> receivers = mActions.get(action);
                if (receivers != null) {
                    for (int k=0; k<receivers.size(); k++) {
                        if (receivers.get(k).receiver == receiver) {
                            receivers.remove(k);
                            k--;
                        }
                    }
                    if (receivers.size() <= 0) {
                        mActions.remove(action);
                    }
                }
            }
        }
    }
}

從mReceivers及mActions中移除相應元素。

(1) LocalBroadcastManager 的核心實現實際還是 Handler,只是利用到了 IntentFilter 的 match 功能,至於 BroadcastReceiver 換成其他接口也無所謂,順便利用了現成的類和概念而已。
(2) 因爲是 Handler 實現的應用內的通信,自然安全性更好,效率更高。

2、LocalBroadcastManager總結

本地廣播發送的廣播只在自身app傳播。不必擔心隱私數據泄露。
其他app無法對該app發送廣播。不必擔心安全漏洞的利用。
本地廣播更加高效、安全。

  • 高效:因爲它內部是通過Handler實現的,它的sendBroadcast()方法含義並非和系統的sendBroadcast()一樣,它的sendBroadcast()方法其實就是通過Handler發送了一個Message而已。
  • 安全:既然它是通過Handler實現廣播發送的,那麼相比系統廣播通過Binder機制實現那肯定更加高效,同時使用Handler來實現,別的app無法向我們應用發送該廣播,而我們app內部發送的廣播也不會離開我們的app。

LocalBroadcast內部協作主要是靠兩個Map集合:mReceivers和mActions,當然還有一個List集合mPendingBroadcasts,這個主要存儲待接收的廣播對象。

3、LocalBroadcastManager使用

(1)自定義BroadcastReceiver子類LocalBroadcastReceiver

public class LocalBroadcastReceiver extends BroadcastReceiver {
 
    @Override
    public void onReceive(Context context, Intent intent) {
        localMsg.setText(intent.getStringExtra(MSG_KEY));
    }
}

(2)註冊接收器

LocalBroadcastReceiver localReceiver = new LocalBroadcastReceiver();
LocalBroadcastManager.getInstance(context).registerReceiver(localReceiver, new IntentFilter(ACTION_LOCAL_SEND));

(3)發送廣播

LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(ACTION_LOCAL_SEND));

(4)取消註冊

LocalBroadcastManager.getInstance(context).unregisterReceiver(localReceiver);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章