LocalBroadcastManager原理分析及应用

来源:互联网 发布:司考倒计时软件 编辑:程序博客网 时间:2024/06/07 08:53

引言

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声明页面的回调。在回调中,我们就可以实现自己的广播接收逻辑了。

本地广播的响应逻辑为

@Overridepublic 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();   }}

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

原创粉丝点击