LocalBroadcastManager本地廣播原理解析

以前有被問到過Android普通廣播和本地廣播的區別,因此打算分析下本地廣播的實現原理以及簡單結束二者的區別,算是對自個人一此源碼學習總結。java

目前官網文檔上是說LocalBroadcastManager被廢棄了,若是想使用的話須要自行依賴或者使用LiveData(後面會簡單介紹下其用法)來代替它。android

說明

基本使用

首先咱們在項目添加本地廣播的依賴設計模式

implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
複製代碼

界面是Activity包裹兩個Fragment,一個負責接受廣播刷新界面,另外一個負責發送廣播。數組

佈局比較簡單,貼個大概就曉得了。安全

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".activity.localbroad.LocalBroadActivity">

    <fragment android:id="@+id/fragmentOne" android:name="com.vico.livedatademo.fragment.localbroad.LocalBroadOneFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" tools:layout="@layout/fragment_local_broad_one" />

    <View android:layout_width="match_parent" android:layout_height="3dp" android:background="@android:color/black" />

    <fragment android:id="@+id/fragmentTwo" android:name="com.vico.livedatademo.fragment.localbroad.LocalBroadTwoFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" tools:layout="@layout/fragment_local_broad_two" />

</LinearLayout>
複製代碼

佈局

從上圖能夠看到,除本地廣播以外我還增長了普通廣播發送的功能,目的也是爲了下文的跨應用接收作驗證。bash

來看看代碼的實現部分:app

class LocalBroadOneFragment : Fragment() {

    companion object {
        const val LOCAL = "com.vico.livedatademo_local"
        const val EXPORT = "com.vico.livedatademo_export"
    }

    private lateinit var localBroadcast: LocalBroadcast
    private lateinit var exportBroadcast: ExportBroadcast

    override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? {
        return inflater.inflate(R.layout.fragment_local_broad_one, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        //註冊本地廣播
        localBroadcast = LocalBroadcast()
        //指定action
        val localIntentFilter = IntentFilter(LOCAL)
        LocalBroadcastManager.getInstance(requireContext()).registerReceiver(localBroadcast, localIntentFilter)

        //註冊普通廣播
        exportBroadcast = ExportBroadcast()
        val exportIntentFilter = IntentFilter(EXPORT)
        requireContext().registerReceiver(exportBroadcast, exportIntentFilter)
    }

    override fun onDetach() {
        //別忘了最後要進行註銷的操做
        LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(localBroadcast)
        requireContext().unregisterReceiver(exportBroadcast)
        super.onDetach()
    }

    //自定義廣播,用來接收本地廣播發來的數據
    inner class LocalBroadcast : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {
            context ?: return
            intent ?: return
            if (intent.action == LOCAL) {
                tv1.text = intent.getStringExtra(LocalBroadTwoFragment.LOCAL_EVENT) ?: ""
            }
        }
    }

    inner class ExportBroadcast : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            context ?: return
            intent ?: return
            if (intent.action == EXPORT) {
                tv2.text = intent.getStringExtra(LocalBroadTwoFragment.EXPORT_EVENT) ?: ""
            }
        }

    }
}
複製代碼
class LocalBroadTwoFragment : Fragment() {

    companion object {
        const val LOCAL_EVENT = "local_event"
        const val EXPORT_EVENT = "export_event"
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_local_broad_two, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        //發送一條包含時間戳內容的本地廣播,指定action
        btnLocal.setOnClickListener {
            val localIntent = Intent(LocalBroadOneFragment.LOCAL)
            localIntent.putExtra(LOCAL_EVENT, System.currentTimeMillis().toString())
            LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(localIntent)
        }

        //發送一條包含時間戳內容的普通廣播,指定action
        btnNormal.setOnClickListener {
            val exportIntent = Intent(LocalBroadOneFragment.EXPORT)
            exportIntent.putExtra(EXPORT_EVENT, (System.currentTimeMillis() / 100).toString())
            requireContext().sendBroadcast(exportIntent)
        }
    }
}
複製代碼

來看下運行效果:dom

發送廣播

em........這樣好像也沒什麼區別嘛,沒事咱們在新啓一個應用來看看可否接收當前應用發出的廣播。ide

新建的Phone Module代碼跟上面接收廣播的相似函數

OtherActivity:

class OtherActivity : AppCompatActivity() {

    companion object {
        const val LOCAL = "com.vico.livedatademo_local"
        const val EXPORT = "com.vico.livedatademo_export"
    }

    private lateinit var localBroadcast: LocalBroadcast
    private lateinit var exportBroadcast: ExportBroadcast

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_other)
        localBroadcast = LocalBroadcast()
        val localIntentFilter = IntentFilter(LOCAL)
        LocalBroadcastManager.getInstance(this).registerReceiver(localBroadcast, localIntentFilter)

        exportBroadcast = ExportBroadcast()
        val exportIntentFilter = IntentFilter(EXPORT)
        registerReceiver(exportBroadcast, exportIntentFilter)
    }

    override fun onDestroy() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(localBroadcast)
        unregisterReceiver(exportBroadcast)
        super.onDestroy()
    }

    inner class LocalBroadcast : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {
            context ?: return
            intent ?: return
            if (intent.action == LOCAL) {
                tv1.text = intent.getStringExtra("local_event") ?: ""
            }
        }
    }

    inner class ExportBroadcast : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            context ?: return
            intent ?: return
            if (intent.action == EXPORT) {
                tv2.text = intent.getStringExtra("export_event") ?: ""
            }
        }

    }
}
複製代碼

activity_other.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" tools:context=".OtherActivity">

    <TextView android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="來自本地廣播的值" />

    <TextView android:id="@+id/tv2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="來自普通廣播的值" />

</LinearLayout>
複製代碼

運行一下,看下這個新建的應用可否接收到另外一個應用發送出來的廣播。

發送廣播2

確實本地廣播沒法作到跨應用發送,那麼其內部的實現原理是什麼呢?下面咱們一塊兒來看看

LocalBroadcastManager原理

經過對LocalBroadcastManager的使用咱們能夠得知,LocalBroadcastManger採用了單例設計模式,將其構造函數私有化。看下構造函數裏作了些上什麼:

private final Context mAppContext;
    
    static final int MSG_EXEC_PENDING_BROADCASTS = 1;

    private final Handler mHandler;
    
    private static final Object mLock = new Object();
    private static LocalBroadcastManager mInstance;
    

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

    private LocalBroadcastManager(Context context) {
        mAppContext = context;
        //初始化一個在主線程運行的Handler
        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);
                }
            }
        };
    }
複製代碼

嗯,在構造函數裏初始化了一個在主線程運行的Handler,在接收到消息後就會執行待處理的廣播。

好的,接下去看看註冊registerReceiver函數裏作了什麼:

//維護不一樣BroadcastReceiver的ReceiverRecord集合的HashMap
    private final HashMap<BroadcastReceiver, ArrayList<ReceiverRecord>> mReceivers
            = new HashMap<>();

    //維護不一樣Action的ReceiverRecord集合的HashMap
    private final HashMap<String, ArrayList<ReceiverRecord>> mActions = new HashMap<>();
    
    private static final class ReceiverRecord {
        final IntentFilter filter;
        final BroadcastReceiver receiver;

        boolean broadcasting;
        //是否被註銷
        boolean dead;

        ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {
            filter = _filter;
            receiver = _receiver;
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder(128);
            builder.append("Receiver{");
            builder.append(receiver);
            builder.append(" filter=");
            builder.append(filter);
            if (dead) {
                builder.append(" DEAD");
            }
            builder.append("}");
            return builder.toString();
        }
    }
    
    
    /**
     * 註冊一個接收匹配給定的IntentFilter任何本地廣播
     *
     * @param receiver 處理廣播的BroadCastReceiver
     * @param filter 
     */
    public void registerReceiver(@NonNull BroadcastReceiver receiver,
            @NonNull IntentFilter filter) {
        synchronized (mReceivers) {
            //將filter和receiver包裝成ReceiverRecord對象
            ReceiverRecord entry = new ReceiverRecord(filter, receiver);
            //查找mReceivers這個HashMap裏是否有對應的key,有則在value裏添加一條記錄,無則put
            ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
            if (filters == null) {
                filters = new ArrayList<>(1);
                mReceivers.put(receiver, filters);
            }
            filters.add(entry);
            //mActions一樣也是這個道理
            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);
            }
        }
    }
複製代碼

能夠看出registerReceiver函數是對兩個HashMap的新增操做,那麼unregisterReceiver同理是對兩個HashMap的刪除操做:

public void unregisterReceiver(@NonNull 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);
                //將ReceiverRecord標記爲已註銷
                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) {
                                //將ReceiverRecord標記爲已註銷
                                rec.dead = true;
                                receivers.remove(k);
                            }
                        }
                        if (receivers.size() <= 0) {
                            mActions.remove(action);
                        }
                    }
                }
            }
        }
    }
複製代碼

好的,看完註冊和註銷,咱們看看發送廣播sendBroadcast(Intent intent)是怎麼實現的:

private static final String TAG = "LocalBroadcastManager";
    private static final boolean DEBUG = false;
    
    private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();
    
    private static final class BroadcastRecord {
        final Intent intent;
        final ArrayList<ReceiverRecord> receivers;

        BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) {
            intent = _intent;
            receivers = _receivers;
        }
    }
    
    public boolean sendBroadcast(@NonNull 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);
            
            //查詢是否有action匹配的記錄,無則結束
            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) {
                        if (debug) {
                            Log.v(TAG, " Filter's target already added");
                        }
                        continue;
                    }

                    //看IntentFilter是否匹配,不匹配就結束
                    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 (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));
                    //Handler發送一條消息,最終在handleMessage裏會執行處理廣播的操做executePendingBroadcasts
                    if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
                        mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                    }
                    return true;
                }
            }
        }
        return false;
    }
複製代碼

最後看看executePendingBroadcasts

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) {
                        rec.receiver.onReceive(mAppContext, br.intent);
                    }
                }
            }
        }
    }
複製代碼

將mPendingBroadcasts裏元素進行復制並清空,複製後的數組遍歷執行receiver。本地廣播的流程到這裏就結束了,下面講講它和普通的廣播有什麼區別。

和普通廣播的區別在哪?

兩個不一樣的應用在Android系統中至關於兩個不一樣的進程。當前應用發出的普通廣播可以被其餘應用所接收到,那麼這勢必是一個進程間通訊的過程(IPC)。

廣播的註冊分爲靜態和動態兩種註冊方式,其中靜態註冊的廣播在應用安裝時由系統自動完成註冊,具體點是PMS(PackageMangerService)完成的。這裏就只簡單分析下廣播的動態註冊過程。動態註冊的過程從ContextWrapperregisterReceiver方法開始。ContextWrapper並無作什麼實際工做,將過程交給了ContextImpl來完成。

@Override
    public Intent registerReceiver( BroadcastReceiver receiver, IntentFilter filter) {
        return mBase.registerReceiver(receiver, filter);
    }
複製代碼

ContextImplregisterReceiver方法調用了本身的registerReceiverInterval方法,實現以下:

private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, IntentFilter filter, String broadcastPermission, Handler scheduler, Context context, int flags) {
        IIntentReceiver rd = null;
        if (receiver != null) {
            if (mPackageInfo != null && context != null) {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = mPackageInfo.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);
            } else {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = new LoadedApk.ReceiverDispatcher(
                        receiver, context, scheduler, null, true).getIIntentReceiver();
            }
        }
        try {
            final Intent intent = ActivityManager.getService().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
                    broadcastPermission, userId, flags);
            if (intent != null) {
                intent.setExtrasClassLoader(getClassLoader());
                intent.prepareToEnterProcess();
            }
            return intent;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
複製代碼

系統首先從mPackageInfo獲取IIntentReceiver對象,而後再採用跨進程的方式向AMS發送廣播註冊的請求。IIntentReceiver是一個Binder接口,它的具體實現是LoadedApk.ReceiverDispatcher.InnerRecevier,ReceiverDispatcher的內部同時保存了BroadcastReceiverInnerReceiver,這樣當接收到廣播時,ReceiverDispatcher能夠很方便地調用BroadcastReceiveronReceive方法。

相比而言,使用LocalBroadcastManager來收發本地廣播效率更高(無需進行進程間通訊),而且無需考慮其餘應用在收發廣播時帶來的任何安全問題。

使用LiveData替換?

開頭說到LocalBroadcastManager已經被廢棄,須要引入依賴支持。可使用LiveData來代替實現相關功能。這裏就簡單舉例一下:

LiveData

界面如上圖所示,Fragment一、Fragment2以及包裹它們的Activity都共同持有一個ViewModel。點擊Fragment2中的按鈕,Fragment1中的TextView和中間的分割線會發生變化。

建立一個須要用到的ViewModel:

class LiveDataViewModel : ViewModel() {

    /** * 只暴露不可變的LiveData給外部。 */
    private val _value = MutableLiveData<String>()
    val value: LiveData<String> get() = _value

    fun setValue() {
        _value.value = System.currentTimeMillis().toString()
    }
}
複製代碼

在Fragment2中更改value的值:

class LiveDataTwoFragment : Fragment() {

    private lateinit var viewModel: LiveDataViewModel

    override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? {
        return inflater.inflate(R.layout.fragment_live_data_two, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProvider(requireActivity()).get(LiveDataViewModel::class.java)

        btnLiveData.setOnClickListener {
            viewModel.setValue()
        }
    }
}
複製代碼

在Fragment1和Activity中觀察value的變化刷新UI:

class LiveDataActivity : AppCompatActivity() {

    private lateinit var viewModel: LiveDataViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_live_data)

        viewModel = ViewModelProvider(this).get(LiveDataViewModel::class.java)

        viewModel.value.observe(this, Observer {
            val random = Random()
            val r = random.nextInt(255)
            val g = random.nextInt(255)
            val b = random.nextInt(255)
            viewLine.setBackgroundColor(Color.rgb(r, g, b))
        })
    }
}
複製代碼
class LiveDataOneFragment : Fragment() {

    private lateinit var viewModel: LiveDataViewModel

    override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? {
        return inflater.inflate(R.layout.fragment_live_data_one, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProvider(requireActivity()).get(LiveDataViewModel::class.java)

        viewModel.value.observe(requireActivity(), Observer { value ->
            tvLiveData.text = value
        })
    }
}
複製代碼

最終的效果以下:

LiveData.gif

END

文章到這裏就已經結束了,這一次就LocalBroadcastManager的實現原理和與普通廣播的區別作了介紹,以及相關的其餘方案。文中的LiveData+ViewModel的實現只是其中一種方式而已,還有其餘諸多優秀的方案能夠選擇,好比:EventBus,RxBus,LiveEventBus等等。 因爲我的水平的緣由,可能沒法更深層次的去介紹,但願能多多包涵。我也同廣大的開發者同樣,一步步地在慢慢成長。

相關文章
相關標籤/搜索