LocalBroadcastManager原理分析及应用

Posted aspook

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LocalBroadcastManager原理分析及应用相关的知识,希望对你有一定的参考价值。

引言

android页面或模块之间通信的方法有很多,如Intent传值、startActivityForResult、EventBus(RxBus)等,大家追求的无非是解耦以及高灵活性;我们自己的应用中使用了基于Android消息机制封装的一套通信体系,且不谈这些,今天的主角是本地广播。

本地广播是系统提供的一种应用内通信方式,它跟全局广播相比,其运行机制是不同的。全局广播是系统级别的,需要跨进程调用,而且作用域是整个设备,而本地广播的作用域只在当前应用之内,因此无需IPC的过程,本应用发送的本地广播,其他应用接收不到,其他应用发送的本地广播,本应用也接收不到。

本地广播的优点

  1. 本地广播作用于App内部,广播数据不会泄露,本应用也不会接收到其他应用的广播,因此安全性较高
  2. 本地广播是在应用内部执行,无需跨进程逻辑,与普通广播相比效率更高
  3. 使用简单,无需静态注册

原理分析

本地广播的原理也很简单,大概流程如下

  1. 首先需要接收广播的页面创建一个BroadcastReceiver(广播声明跟全局广播是一样的)并注册到一个应用内的本地广播接收器列表(注册方式跟全局广播不同,下文会详细说明)
  2. 某个页面利用Intent发送广播(发送方式跟全局广播不同,下文会详细说明)
  3. 本地广播接收器列表根据IntentFilter匹配得到可以接收该广播的BroadcastReceiver,然后匹配的广播接收器会响应广播(注意这里区分广播的异步与同步发送)
  4. 在适当时候反注册BroadcastReceiver,比如Activity的onDestroy()回调中

为了方便大家的使用,Android提供了LocalBroadcastManager类(姑且称之为本地广播管理器),该类帮我们封装了注册、反注册、广播发送等过程,接下来分析一下源码实现。

LocalBroadcastManager定义

首先LocalBroadcastManager定义为一个单例,方便App内全局使用。

private static LocalBroadcastManager mInstance;

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

注意到LocalBroadcastManager的构造函数中创建了一个Handler实例,并且是运行在主线程的,它响应一种what为MSG_EXEC_PENDING_BROADCASTS的消息,这是用来处理异步广播的,具体如何处理稍后说明。

两个内部类

对于广播接收器的注册过程,并非简单地把BroadcastReceiver进行注册,而是进行了简单的包装,这里涉及到LocalBroadcastManager的两个内部类,如下:

ReceiverRecord
private static class ReceiverRecord 
    final IntentFilter filter;
    final BroadcastReceiver receiver;
    boolean broadcasting;

    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);
        builder.append("");
        return builder.toString();
    

顾名思义,ReceiverRecord简单封装了BroadcastReceiver和IntentFilter。

BroadcastRecord
private static class BroadcastRecord 
    final Intent intent;
    final ArrayList<ReceiverRecord> receivers;

    BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) 
        intent = _intent;
        receivers = _receivers;
    

BroadcastRecord也很简单,封装了广播Intent以及能够匹配该Intent的接收器列表(这里并非直接是BroadcastReceiver,而是包装类ReceiverRecord)。

基于上述两个包装类,LocalBroadcastManager提供了如下两个HashMap用来存储注册的接收器

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

此外还有一个叫mPendingBroadcasts的容器如下

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

它是用来存储待处理广播的列表,其元素为BroadcastRecord。

注册过程

注册过程源码如下,具体流程分析请看注释

/**
 * Register a receive for any local broadcasts that match the given IntentFilter.
 *
 * @param receiver The BroadcastReceiver to handle the broadcast.
 * @param filter Selects the Intent broadcasts to be received.
 *
 * @see #unregisterReceiver
 */
public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) 
    // 加锁同步
    synchronized (mReceivers) 
        // 将BroadcastReceiver和IntentFilter封装成ReceiverRecord
        ReceiverRecord entry = new ReceiverRecord(filter, receiver);
        // BroadcastReceiver作为HashMap的key,注意一般来说注册时一个BroadcastReceiver对应一个IntentFilter。但有一种情况下同一个页面内使用同一个BroadcastReceiver,配合不同的IntentFilter注册多次;更极端情况是应用内只有一个BroadcastReceiver,使用不同的IntentFilter进行多次注册
        ArrayList<IntentFilter> filters = mReceivers.get(receiver);
        if (filters == null) 
            filters = new ArrayList<IntentFilter>(1);
            mReceivers.put(receiver, filters);
        
        filters.add(filter);
        // 一个IntentFilter可以对应多个action
        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);
        
    

反注册过程

对应的反注册过程如下,代码逻辑请看注释

/**
 * Unregister a previously registered BroadcastReceiver.  <em>All</em>
 * filters that have been registered for this BroadcastReceiver will be
 * removed.
 *
 * @param receiver The BroadcastReceiver to unregister.
 *
 * @see #registerReceiver
 */
public void unregisterReceiver(BroadcastReceiver receiver) 
    // 加锁同步
    synchronized (mReceivers) 
        // 同一个BroadcastReceiver可能对应多个IntentFilter,移除以BroadcastReceiver为key的键值对
        ArrayList<IntentFilter> filters = mReceivers.remove(receiver);
        if (filters == null) 
            return;
        
        for (int i=0; i<filters.size(); i++) 
            IntentFilter filter = filters.get(i);
            // 每个IntentFilter可对应多个action
            for (int j=0; j<filter.countActions(); j++) 
                String action = filter.getAction(j);
                // 以action作为key
                ArrayList<ReceiverRecord> receivers = mActions.get(action);
                if (receivers != null) 
                    for (int k=0; k<receivers.size(); k++) 
                        // 移除包含目标BroadcastReceiver的ReceiverRecord
                        if (receivers.get(k).receiver == receiver) 
                            receivers.remove(k);
                            k--;
                        
                    
                    if (receivers.size() <= 0) 
                        mActions.remove(action);
                    
                
            
        
    

异步发送广播

异步发送广播利用了Android消息机制,发送完成后即可返回,广播会异步响应,通常情况下我们都是使用异步方式,否则会被阻塞。

/**
 * Broadcast the given intent to all interested BroadcastReceivers.  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.
 *
 * @see #registerReceiver
 */
public boolean sendBroadcast(Intent intent) 
    synchronized (mReceivers) 
        // 提取出Intent中的action、type、data、scheme、categories等,稍后用于匹配
        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();
        // debug仅用于打印log,可忽略
        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);

        // 首先根据广播Intent的action进行匹配,匹配不上则直接返回false
        ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
        if (entries != null) 
            if (debug) Log.v(TAG, "Action list: " + entries);

            // receivers用来存储匹配上的接收器包装类ReceiverRecord
            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;
                

                // InterFilter的匹配,若匹配成功,返回值match>=0
                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 
                    // 匹配失败在debug为true的情况下仅输出log
                    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) 
                // 将上述匹配成功时设置的标志重新置为false
                for (int i=0; i<receivers.size(); i++) 
                    receivers.get(i).broadcasting = false;
                
                // 将匹配成功的接收器封装为BroadcastRecord,加入待处理广播列表
                mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
                // 利用Handler消息机制,发送消息,异步处理广播
                if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) 
                    mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                
                return true;
            
        
    
    return false;

可以看到异步广播最终利用Android消息机制切换到主线程执行广播接收过程,具体实现逻辑则是在LocalBroadcastManager的构造器中:

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

最终调用方法为executePendingBroadcasts(),稍后详解。

同步发送广播

同步发送广播比较简单,实现如下

/**
 * Like @link #sendBroadcast(Intent), but if there are any receivers for
 * the Intent this function will block and immediately dispatch them before
 * returning.
 */
public void sendBroadcastSync(Intent intent) 
    if (sendBroadcast(intent)) 
        executePendingBroadcasts();
    

具体的广播执行也是在executePendingBroadcasts()中。

执行广播

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中遍历执行每个BroadcastReceiver的onReceive方法,触发BroadcastReceiver声明页面的回调。在回调中,我们就可以实现自己的广播接收逻辑了。

本地广播的响应逻辑为

@Override
public void onReceive(Context context, Intent intent) 
    // 处理逻辑

因此如果要传值的话则要依赖于Intent的能力。

注意事项

  1. 发送广播可以在主线程也可以在子线程
  2. 接收广播在哪个线程要看是异步广播还是同步广播。如果是异步广播,无论发送广播是在主线程还是在子线程,内部都会通过Handler消息机制切换到主线程执行。如果是同步广播,发送广播的线程和接收广播的线程为同一线程,此种方式会导致线程阻塞,使用时需谨慎。
  3. 如果广播接收在主线程的话,注意不要进行耗时操作
  4. 通过广播传值要依赖于Intent的传值方式及要求

下表是一个简单总结

发送本地广播的线程同步 or 异步是否阻塞接收本地广播的线程
主线程异步主线程
主线程同步主线程
子线程异步主线程
子线程同步子线程

应用场景

  1. 单对单通信

    场景描述:一个页面向另一个页面发送消息,可以是相邻页面也可以不相邻

    实现方式:使用特定的action注册BroadcastReceiver,并使用具有该action的Intent发送本地广播

  2. 单对多通信

    场景描述:一个页面发送广播,多个页面可以接收

    实现方式:每个页面都使用相同的action注册BroadcastReceiver即可

  3. 多对单通信

    场景描述:一个页面可以接收到多个不同广播

    实现方式:该页面使用多个不同的action(一个IntentFilter可以有多个action)注册BroadcastReceiver,这样就能接收到具有不同action的广播了

应用示例

以下示例有3个页面,分别为ActivityA、ActivityB、ActivityC,其中ActivityA和ActivityB分别注册了广播接收器,ActivityC发送广播,ActivityA和ActivityB都可以接收到广播,接收到广播之后取出传值显示在UI上。

代码中注释了子线程发送广播、同步发送广播等逻辑,同时还有Log输出,可以查看同步、异步的执行顺序以及发送广播和接收广播所在的线程信息。源代码很简单,如下。

  1. OnReceiveBrodadcastListener接口

    public interface OnReceiveBroadcastListener 
       void onReceive(Context context, Intent intent);
    
  2. LocalBroadcastReceiver类,继承自BroadcastReceiver

    public class LocalBroadcastReceiver extends BroadcastReceiver 
       private OnReceiveBroadcastListener mListener;
    
       @Override
       public void onReceive(Context context, Intent intent) 
           if (mListener != null) 
               mListener.onReceive(context, intent);
           
       
    
       public LocalBroadcastReceiver(OnReceiveBroadcastListener listener) 
           mListener = listener;
       
    
  3. ActivityA的实现

    public class ActivityA extends AppCompatActivity 
       private static final String TAG = "LocalBroadcastManager";
       private TextView tv_a;
       private Button btn_a;
       private LocalBroadcastReceiver mReceiver;
    
       @Override
       protected void onCreate(Bundle savedInstanceState) 
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_a);
    
           Log.d(TAG, "main thread id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName());
    
           tv_a = (TextView) findViewById(R.id.tv_a);
           btn_a = (Button) findViewById(R.id.btn_a);
    
           btn_a.setOnClickListener(new View.OnClickListener() 
               @Override
               public void onClick(View v) 
                   Intent intent = new Intent(ActivityA.this, ActivityB.class);
                   startActivity(intent);
               
           );
    
           mReceiver = new LocalBroadcastReceiver(new OnReceiveBroadcastListener() 
               @Override
               public void onReceive(Context context, Intent intent) 
                   // 注意:异步消息是在主线程处理的,务必避免耗时操作
                   // 如果接收多个广播,可以根据action分别处理
                   String action = intent.getAction();
    
                   Log.d(TAG, "A onReceive=== thread id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName());
                   Log.d(TAG, "A received broadcast,action=" + action);
    
                   // 取出广播传值
                   String name = intent.getStringExtra("NAME");
                   tv_a.setText(name);
               
           );
    
           IntentFilter filter = new IntentFilter();
           // 一个IntentFilter可以配置多个action
           filter.addAction("com.aspook.localbroadcast_A");
           filter.addAction("com.aspook.localbroadcast_AA");
           LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
    
           // 同一个BroadcastReceiver可以注册多次,极端情况下,全局可以只有一个
    //        IntentFilter filter2 = new IntentFilter();
    //        filter2.addAction("com.aspook.localbroadcast_AAA");
    //        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter2);
       
    
       @Override
       protected void onDestroy() 
           super.onDestroy();
           LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
       
    
  4. ActivityB的实现

    public class ActivityB extends AppCompatActivity 
       private static final String TAG = "LocalBroadcastManager";
       private TextView tv_b;
       private Button btn_b;
       private LocalBroadcastReceiver mReceiver;
    
       @Override
       protected void onCreate(Bundle savedInstanceState) 
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_b);
    
           tv_b = (TextView) findViewById(R.id.tv_b);
           btn_b = (Button) findViewById(R.id.btn_b);
    
           btn_b.setOnClickListener(new View.OnClickListener() 
               @Override
               public void onClick(View v) 
                   Intent intent = new Intent(ActivityB.this, ActivityC.class);
                   startActivity(intent);
               
           );
    
           mReceiver = new LocalBroadcastReceiver(new OnReceiveBroadcastListener() 
               @Override
               public void onReceive(Context context, Intent intent) 
                   Log.d(TAG, "B onReceive=== thread id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName());
                   Log.d(TAG, "B received broadcast,action=" + intent.getAction());
    
                   Bundle bundle = intent.getExtras();
                   int age = bundle.getInt("AGE");
                   tv_b.setText(age + "");
               
           );
    
           IntentFilter filter = new IntentFilter();
           // 允许定义一个action数组,以接收不同的消息
           filter.addAction("com.aspook.localbroadcast_B");
           filter.addAction("com.aspook.localbroadcast_BB");
           filter.addAction("com.aspook.localbroadcast_AA");
           LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
       
    
       @Override
       protected void onDestroy() 
           super.onDestroy();
           LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
       
    
  5. ActivityC的实现

    public class ActivityC extends AppCompatActivity 
       private static final String TAG = "LocalBroadcastManager";
       private TextView tv_c;
       private Button btn_c;
    
       @Override
       protected void onCreate(Bundle savedInstanceState) 
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_c);
    
           tv_c = (TextView) findViewById(R.id.tv_c);
           btn_c = (Button) findViewById(R.id.btn_c);
    
           btn_c.setOnClickListener(new View.OnClickListener() 
               @Override
               public void onClick(View v) 
                   // 在主线程发送本地广播
                   Log.d(TAG, "send broadcast=== thread id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName());
    
                   Intent intent = new Intent();
                   // Intent传值示例
                   intent.putExtra("NAME", "kevin");
                   Bundle bundle = new Bundle();
                   bundle.putInt("AGE", 22);
                   intent.putExtras(bundle);
    
                   intent.setAction("com.aspook.localbroadcast_AA");
                   // 异步执行
                   LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
                   // 同步执行
                   //LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcastSync(intent);
                   Log.d(TAG, "do other things after localbroadcast!!!");
    
                   // 在子线程发送本地广播
    //                new Thread(new Runnable() 
    //                    @Override
    //                    public void run() 
    //                        Log.d(TAG, "send broadcast=== thread id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName());
    //
    //                        Intent intent = new Intent();
    //                        intent.putExtra("NAME", "kevin");
    //                        Bundle bundle = new Bundle();
    //                        bundle.putInt("AGE", 22);
    //                        intent.putExtras(bundle);
    //                        // action should defined as message constant
    //                        intent.setAction("com.aspook.localbroadcast_AAA");
    //                        // 异步执行
    //                        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
    //                        // 同步执行
    //                        //LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcastSync(intent);
    //                        Log.d(TAG, "do other things after localbroadcast!!!");
    //                    
    //                ).start();
               
           );
       
    
       @Override
       protected void onDestroy() 
           super.onDestroy();
       
    

上面代码只是一个本地广播的应用演示,具体使用时可以再进一步抽象封装,可用作应用内的通信机制。

以上是关于LocalBroadcastManager原理分析及应用的主要内容,如果未能解决你的问题,请参考以下文章

Android 之LocalBroadcastManager原理简析

LocalBroadcastManager 的实现原理,Handler还是 Binder?

LocalBroadcastManager 的源码分析及使用方法梳理

LocalBroadcastManager 的源码分析及使用方法梳理

LocalBroadcastManager 的源码分析及使用方法梳理

2021年是意义非凡的一年,年薪60W必备