LocalBroadcastManager源碼解析

轉載請標明出處:【顧林海的博客】


前言

廣播想必大家都不陌生,日常開發中同一個APP中的多個進程之間需要進行傳輸信息,或是不同APP之間的消息通信,都可以使用廣播來實現,學習廣播時,我們知道廣播有普通廣播和系統廣播,通過自定義廣播接受者BroadcastReceiver,並複寫onReceive方法,內部通過Binder機制向AMS(Activity Manager Service)進行註冊,廣播發送者通過Binder機制向AMS發送廣播,AMS內部會尋找符合相應條件的BroadcastReceiver,將廣播發送到BroadcastReceiver相應的消息循環隊列中,之後從消息循環隊列拿到這個廣播,再通過回調BroadcastReceiver的onReceive方法,整個廣播的使用還是很簡單,如果在不考慮安全、性能的情況下,完全可以使用以上方式就可以實現,但在真實環境下還是推薦使用本地廣播進行應用內的信息傳輸,考慮安全、性能方面原因,這也是本篇之所以介紹本地廣播的原因,在正式開始前,


爲什麼本地廣播安全和性能更高?

使用LocalBroadcastManager發送的廣播只會在當前APP內進行傳播,數據傳遞也只會在當前APP內進行傳輸,因此數據並不會泄露出去,同時其他APP發送的廣播,自身的APP並不會接受到,因此我們並不會擔心出現安全漏洞,同時LocalBroadcastManager內部是通過Handler來實現的,性能上要比全局廣播更高。


源碼解析

LocalBroadcastManager的核心方法就是註冊和解註冊,因此分析源碼的時候主要介紹這兩個方法實現的邏輯,使用LocalBroadcastManager前我們需要獲取的它的實例,可以通過靜態方法getInstance方法獲取。

    public static LocalBroadcastManager getInstance(Context context) {
        synchronized (mLock) {
            if (mInstance == null) {
                mInstance = new LocalBroadcastManager(context.getApplicationContext());
            }
            return mInstance;
        }
    }

這段代碼想必大家一點都不陌生,典型的單例模式,注意這裏傳入的Context最終在實例化LocalBroadcastManager時傳入的是Application的上下文,爲什麼這樣做?如果我們傳入的是Activity的上下文,由於LocalBroadcastManager
是個單例類,它的整個生命週期和應用的生命週期一樣長,如果這個單例類持有了Activity的context,那麼在整個應用程序的生命週期它都不能正常被回收,當我們退出Activity時,因爲靜態單例(在應用程序的整個生命週期中存在)會繼續持有這個Activity的引用,導致這個Activity對象無法被回收釋放,最終造成了內存泄露。

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

LocalBroadcastManager私有構造器中創建了一個Handler,Handler傳入的是context.getMainLooper,也就是傳入了主線程的Looper,到這裏我們知道了LocalBroadcastManager的廣播是在主線程中處理的,最終回調的BroadcastReceiver的onReceive方法也是在主線程處理,因此在onReceive方法中是不能做耗時操作的。

LocalBroadcastManager實例獲取完後就可以調用它的registerReceiver方法實現廣播的註冊,發送廣播是在自身APP內進行發送的,因此廣播的註冊者所註冊的過濾信息和廣播之間的映射關係需要保存下來,ReceiverRecord是LocalBroadcastManager的靜態內部類,內部就保存着註冊者所註冊的廣播以及廣播所接受的意圖過濾器IntentFilter,可以通過它的addAction方法添加多個廣播過濾信息,registerReceiver方法如下:

    private final HashMap<BroadcastReceiver, ArrayList<ReceiverRecord>> mReceivers = new HashMap<>();
    private final HashMap<String, ArrayList<ReceiverRecord>> mActions = new HashMap<>();

    public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        synchronized (mReceivers) {
            ReceiverRecord entry = new ReceiverRecord(filter, receiver);
            //獲取該廣播的訂閱信息列表
            ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
            if (filters == null) {
                //該廣播的訂閱信息列表爲空,創建新的列表,並將該廣播的訂閱信息列表通過mReceivers存儲。
                filters = new ArrayList<>(1);
                //
                mReceivers.put(receiver, filters);
            }
            //保存相關信息
            filters.add(entry);
            //遍歷該訂閱信息列表
            for (int i = 0; i < filter.countActions(); i++) {
                String action = filter.getAction(i);
                //獲取訂閱信息對應的ReceiverRecord對象的集合
                ArrayList<ReceiverRecord> entries = mActions.get(action);
                if (entries == null) {
                    entries = new ArrayList<ReceiverRecord>(1);
                    mActions.put(action, entries);
                }
                entries.add(entry);
            }
        }
    }

在registerReceiver方法中,通過ReceiverRecord保存廣播和IntentFilter的映射關係,如果同一個廣播被多個訂閱者註冊呢?LocalBroadcastManager內部提供了集合mReceivers,以BroadcastReceiver爲key,以廣播的註冊者的訂閱信息和廣播之間的映射關係列表爲Value,總的來說mReceivers用於保存同一個廣播與多個訂閱者的關係之間的映射。以上方法中,一開始就從集合mReceivers中通過廣播接收器獲取ReceiverRecord列表,接着會將廣播接收器和廣播過濾信息包裝成ReceiverRecord對象添加到ReceiverRecord列表中。

我們在註冊廣播時,可以添加多個廣播過濾信息,因此需要遍歷廣播過濾信息,再通過mActions集合保存廣播過濾信息對應的ReceiverRecord對象列表,爲什麼需要mActions集合,有了集合mReceivers不是也能獲取到廣播嗎,這是因爲在註冊多個不同廣播情況下,如果設置的廣播過濾器都一樣,最後就只有一個廣播響應,很明顯這是錯誤的,因此需要mActions集合保存每個過濾信息所對應的廣播接收器的列表,這樣就可以根據過濾信息拿到註冊的廣播列表,從而遍歷廣播列表回調onReceive方法。

總的來說,registerReceiver方法的目的是通過Map集合mReceivers和mActions來完成內部廣播處理的一個協作問題。

註冊完廣播後就可以通過sendBroadcast方法來發送廣播了,有了Map集合mReceivers和mActions後處理廣播就簡單多了,
sendBroadcast方法如下:

private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();

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

        final boolean debug = DEBUG ||
                ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
        if (debug) Log.v(
                TAG, "Resolving type " + type + " scheme " + scheme
                        + " of intent " + intent);

        //拿到訂閱信息對應的廣播接收器列表
        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 (debug) Log.v(TAG, "Matching against filter " + receiver.filter);

                if (receiver.broadcasting) {
                    //broadcasting默認false
                    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 (receivers == null) {
                        receivers = new ArrayList<ReceiverRecord>();
                    }
                    //匹配通過進行保存
                    receivers.add(receiver);
                    receiver.broadcasting = true;
                } else {
                    if (debug) {
                        String reason;
                        switch (match) {
                            case IntentFilter.NO_MATCH_ACTION:
                                reason = "action";
                                break;
                            case IntentFilter.NO_MATCH_CATEGORY:
                                reason = "category";
                                break;
                            case IntentFilter.NO_MATCH_DATA:
                                reason = "data";
                                break;
                            case IntentFilter.NO_MATCH_TYPE:
                                reason = "type";
                                break;
                            default:
                                reason = "unknown reason";
                                break;
                        }
                        Log.v(TAG, "  Filter did not match: " + reason);
                    }
                }
            }

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

sendBroadcast方法傳入一個Intent對象,方法中會先從Intent中獲取廣播過濾信息,根據這個廣播過濾信息從mActions集合中獲取ReceiverRecord列表,遍歷ReceiverRecord列表,根據IntentFilter中的match匹配規則方法,符合要求的會將ReceiverRecord添加到局部遍歷receivers集合中,接着將receivers集合包裝成BroadcastRecord對象並添加到mPendingBroadcasts集合中,mPendingBroadcasts集合主要就是存儲待接收的廣播對象。最後通過Handler發送消息處理待接收的廣播對象。

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

    private void executePendingBroadcasts() {
        while (true) {
            final BroadcastRecord[] brs;
            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++) {
                final BroadcastRecord br = brs[i];
                final int nbr = br.receivers.size();
                for (int j = 0; j < nbr; j++) {
                    //遍歷廣播列表
                    final ReceiverRecord rec = br.receivers.get(j);
                    if (!rec.dead) {
                        //調用廣播接收器的onReceive方法
                        rec.receiver.onReceive(mAppContext, br.intent);
                    }
                }
            }
        }
    }

在sendBroadcast方法中最後會通過Handler發送一個MSG_EXEC_PENDING_BROADCASTS的消息,Handler接收到這個消息會調用executePendingBroadcasts方法,在該方法中通過同步代碼塊將待接收的廣播對象轉換成數組,再通過遍歷待接收的廣播列表調用對應廣播的onReceive方法。

當我們離開當前頁面時,需要解除廣播的註冊,這裏調用的是unregisterReceiver方法。

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

unregisterReceiver方法很簡單,主要是進行數據的回收,從mReceivers集合中移除該廣播,並遍歷從mReceiver集合中移除的ReceiverRecord列表,獲取該廣播註冊時的所有廣播過濾信息,並從mActions集合中移除。

除了sendBroadcast方法外還有一個發送廣播的方法是sendBroadcastSync方法,代碼如下:

    public void sendBroadcastSync(Intent intent) {
        if (sendBroadcast(intent)) {
            //直接進行處理
            executePendingBroadcasts();
        }
    }

它跟sendBroadcast方法不同之處在於,sendBroadcast方法中會將待接受的廣播交由Handler來處理,Handler發送消息後會將消息添加到消息隊列中,之後交由該線程的Looper去循環遍歷消息隊列,再由Handler來處理,也就是說通過sendBroadcast方法來發送廣播,廣播接收器接收消息的時機是未知的,但通過sendBroadcastSync方法來發送廣播時,先通過sendBroadcast方法保存待接收的廣播到mPendingBroadcasts集合中,接着就直接執行了executePendingBroadcasts()方法來接收廣播。


總結

LocalBroadcastManager發送的廣播只在自身APP內傳播,其他APP發送的廣播並不會接收到,提高了數據的安全性並避免了安全漏洞的發生,同時由於LocalBroadcastManager內部是通過Handler來發送廣播的,性能更加高效,內部協作中主要靠兩個Map集合mActions和mReceivers,待接收的廣播由List集合mPendingBroadcasts存儲。

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