美文网首页
Android LocalBroadcastManager 的使

Android LocalBroadcastManager 的使

作者: 不等离子 | 来源:发表于2018-06-20 18:31 被阅读0次

    我们知道Android中的广播(Broadcast)主要用于应用间的通信,这种通信机制依赖于Binder通信机制及AMS的参与。
    当我们想实现应用内部组件之间的一对多通信时,广播机制的效率和开销可能无法满足要求。
    这个时候我们可以使用第三方提供的开源库,例如EventBus等,
    也可以使用Android支持库提供的LocalBroadcastManager
    本篇博客主要记录一下LocalBroadcastManager的基本用法,
    同时分析一下LocalBroadcastManager的源码,看看其功能实现的原理

    1、基本用法

    我实现一个简单的场景:
    APK中有两个Activity,第一个Activity利用LocalBroadcastManager注册广播接收器,点击界面按键后启动第二个Activity。
    进入第二个Activity后,点击按键就会通过LocalBroadcastManager发送广播,然后结束该Activity。
    如果第一个Activity注册的广播接收器收到广播,就弹出一个Toast进行提示。
    整个APK的功能极其简单,但基本囊括了LocalBroadcastManager的主要接口。
    第一个Activity的代码如下:

    public class FirstActivity extends AppCompatActivity {
        private LocalBroadcastReceiver mReceiver;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_first);
    
            //创建一个BroadcastReceiver,与常规广播一样,自己实现子类即可
            mReceiver = new LocalBroadcastReceiver();
    
            //调用LocalBroadcastManager的接口进行注册,参数与常规Broadcast一致
            LocalBroadcastManager.getInstance(this)
                    .registerReceiver(mReceiver, new IntentFilter("ZJTest"));
    
            Button button = (Button)findViewById(R.id.first_button);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //点击按键后,启动第二个Activity
                    Intent i = new Intent();
                    i.setClass(getApplicationContext(), SecondActivity.class);
                    startActivity(i);
                }
            });
        }
    
        private class LocalBroadcastReceiver extends BroadcastReceiver {
            @Override
            public void onReceive(Context context, Intent intent) {
                //收到广播后,用Toast提示
                Toast.makeText(context, "Receive Local Broadcast", Toast.LENGTH_LONG).show();
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //利用LocalBroadcastManager的接口,进行反注册
            LocalBroadcastManager.getInstance(this)
                    .unregisterReceiver(mReceiver);
        }
    }
    

    我们再来看看SecondActivity的代码:

    public class SecondActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_second);
    
            Button button = (Button) findViewById(R.id.second_button);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //点击按键后,利用LocalBroadcastManager的接口发送本地广播
                    LocalBroadcastManager.getInstance(getApplicationContext())
                            .sendBroadcast(new Intent("ZJTest"));
                    finish();
                }
            });
        }
    }
    

    从上面的代码可以看出,LocalBroadcastManager的使用极其简单。
    与常规Broadcast相比,就是将Context对象替换为LocalBroadcastManager即可。

    2、源码分析

    现在我们来看看LocalBroadcastManager相关的源码。

    2.1 构造函数

    我们首先看一下LocalBroadcast构造函数相关的代码:

    static final int MSG_EXEC_PENDING_BROADCASTS = 1;
    
        private final Handler mHandler;
    
        private static final Object mLock = new Object();
    
        //静态变量
        private static LocalBroadcastManager mInstance;
    
        //获取LocalBroadcastManager的接口
        public static LocalBroadcastManager getInstance(Context context) {
            //很明显,这是单例模式的写法
            synchronized (mLock) {
                if (mInstance == null) {
                    mInstance = new LocalBroadcastManager(context.getApplicationContext());
                }
                return mInstance;
            }
        }
    
        private LocalBroadcastManager(Context context) {
            mAppContext = context;
            //容易看出,LocalBroadcastManager的构造函数创建了一个Handler
            //Handler的使用的是主线程的消息队列
            mHandler = new Handler(context.getMainLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        //收到MSG_EXEC_PENDING_BROADCASTS,调用函数处理广播
                        case MSG_EXEC_PENDING_BROADCASTS:
                            executePendingBroadcasts();
                            break;
                        default:
                            super.handleMessage(msg);
                    }
                }
            };
        }
    

    从LocalBroadcastManager的构造函数可以看出,该对象是进程唯一的,
    且在进程的主线程中处理消息。

    2.2 注册广播接收器

    LocalBroadcastManager注册广播接收器的接口如下所示:

    public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
            synchronized (mReceivers) {
                //LocalBroadcastManager中定义了一个内部类ReceiverRecord
                //保存IntentFilter和BroadcastReceiver
                ReceiverRecord entry = new ReceiverRecord(filter, receiver);
    
                //mReceivers的类型为HashMap<BroadcastReceiver, ArrayList<IntentFilter>>
                //一个BroadcastReceiver可以对应多个IntentFilter
                ArrayList<IntentFilter> filters = mReceivers.get(receiver);
                if (filters == null) {
                    filters = new ArrayList<IntentFilter>(1);
                    mReceivers.put(receiver, filters);
                }
                filters.add(filter);
    
                //mActions的类型为HashMap<String, ArrayList<ReceiverRecord>>
                //一个IntentFilter中可能包含多个Action
                //可能有多个BroadcastReceiver监听了同一个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);
                }
            }
        }
    

    从上述的注册接口的代码可以看出,LocalBroadcastManager在本地维护了BroadcastReceiver、IntentFilter和Action之间的关系。
    使用普通广播时,这些信息都会交由AMS统一维护。

    2.3 反注册广播接收器

    根据上文注册广播接收器的代码,了解LocalBroadcastManager的数据结构后,反注册广播接收器的代码就很容易理解了:

    public void unregisterReceiver(BroadcastReceiver receiver) {
            synchronized (mReceivers) {
                //一个BroadcastReceiver可能对应多个IntentFilter
                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与当前BroadcastReceiver之间的关系
                        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);
                            }
                        }
                    }
                }
            }
        }
    

    2.4 发送广播

    经过前文的铺垫后,我们终于可以看看重头戏了,即LocalBroadcastManager发送广播的流程:

    public boolean sendBroadcast(Intent intent) {
            synchronized (mReceivers) {
                //首先解析出Intent中携带的信息
                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();
    
                .....................
    
                //根据Action取出所有初步匹配的ReceiverRecord,其中包含IntentFilter和BroadcastReceiver
                ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
                if (entries != null) {
                    ...................
                    //receivers中保存最终匹配的ReceiverRecord
                    ArrayList<ReceiverRecord> receivers = null;
    
                    for (int i=0; i<entries.size(); i++) {
                        ReceiverRecord receiver = entries.get(i);
                        ...................
    
                        //当一个receiver被加入到receivers时,就会将broadcasting置为true
                        //这里是避免重复加入
                        //目前自己没看懂这个标识的意义,感觉整个流程不会有重复的ReceiverRecord
                        if (receiver.broadcasting) {
                            ..................
                            continue;
                        }
    
                        //利用IntentFilter的接口进行完整的匹配
                        int match = receiver.filter.match(action, type, scheme, data,
                                categories, "LocalBroadcastManager");
                        if (match >= 0) {
                            ..................
                            if (receivers == null) {
                                receivers = new ArrayList<ReceiverRecord>();
                            }
                            //匹配成功后,将receiver加入到receivers中,并将broadcasting标志置为true
                            receivers.add(receiver);
                            receiver.broadcasting = true;
                        } else {
                            //打印log信息
                            ...............
                        }
                    }
    
                    //完成上文的匹配后,此时receivers中保存了所有与当前Intent完全匹配的ReceiverRecord
                    if (receivers != null) {
                        for (int i=0; i<receivers.size(); i++) {
                            //将broadcasting重新置为false
                            //这样下一个Intent到来时,ReceiverRecord才有机会重新加入到receivers中
                            //注意到改变broadcasting的操作,均在一个synchronized块中,因此完全是顺序执行
                            //个人感觉,完全可以不需要broadcasting标志
                            receivers.get(i).broadcasting = false;
                        }
    
                        //最后,构造BroadcastRecord,并加入到mPendingBroadcasts中
                        mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
    
                        //若主线程队列中没有MSG_EXEC_PENDING_BROADCASTS,则发送该消息
                        if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
                            mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                        }
                        return true;
                    }
                }
            }
            return false;
        }
    

    LocalBroadcastManager的sendBroadcast接口流程,简单来讲就是根据Intent的信息,匹配出对应的BroadcastReceiver,
    然后用BroadcastReceiver构造出BroadcastRecord对象,并发送消息,触发主线程进行处理。

    2.5 处理广播

    前文在LocalBroadcastManager的构造函数中,我们已经看到了,主线程收到MSG_EXEC_PENDING_BROADCASTS后,
    将调用executePendingBroadcasts函数进行处理

    private void executePendingBroadcasts() {
            //注意此处为true
            while (true) {
                BroadcastRecord[] brs = null;
                synchronized (mReceivers) {
                    final int N = mPendingBroadcasts.size();
                    if (N <= 0) {
                        return;
                    }
                    //保存要处理的BroadcastRecord
                    brs = new BroadcastRecord[N];
                    mPendingBroadcasts.toArray(brs);
    
                    //清空mPendingBroadcasts
                    //因此,再下次while循环结束前
                    //若没有新数据加入到mPendingBroadcasts,就会退出循环
                    //如果在下面的for循环过程中,其它线程再次调用sendBroadcast
                    //并将数据加入到mPendingBroadcasts中,那么while循环将继续处理
                    mPendingBroadcasts.clear();
                }
    
                for (int i=0; i<brs.length; i++) {
                    BroadcastRecord br = brs[i];
                    for (int j=0; j<br.receivers.size(); j++) {
                        //回调BroadcastReceiver的onReceive函数
                        //从这里可以看出,无论在什么线程利用LocalBroadcastManager注册BroadcastReceiver
                        //BroadcastReceiver的onReceive函数均在主线程被回调
                        //这与普通广播的处理相似
                        br.receivers.get(j).receiver.onReceive(mAppContext, br.intent);
                    }
                }
            }
        }
    

    2.6 sendBroadcastSync接口

    最后,我们来看看LocalBroadcastManager提供的sendBroadcastSync接口

    public void sendBroadcastSync(Intent intent) {
        //从前文的代码知道,均有与Intent匹配的BroadcastReceiver时
        //sendBroadcast返回true,同时BroadcastReceiver对应的ReceiverRecord被加入到mPendingBroadcasts待处理
        if (sendBroadcast(intent)) {
            //executePendingBroadcasts就是处理mPendingBroadcasts的
            executePendingBroadcasts();
        }
    }
    

    因此,一旦调用了sendBroadcastSync接口发送广播,那么该广播被处理后(有匹配的BroadcastReceiver时),
    sendBroadcastSync接口才会返回。
    具体分为两种情况:
    1、主线程正在调用executePendingBroadcasts时,其它线程调用sendBroadcastSync接口,
    那么新的ReceiverRecord将被加入到mPendingBroadcasts中。
    由于executePendingBroadcasts中的while循环,那么mPendingBroadcasts变为非空后,
    其中的信息有可能再次被主线程处理,即BroadcastReceiver的onReceive函数被主线程调用。
    2、主线程没有调用executePendingBroadcasts时,其它线程线程调用sendBroadcastSync接口, 那么executePendingBroadcasts将在其它线程中运行。 此时,BroadcastReceiver的onReceive函数将被其它线程调用。例如: 修改FirstActivity和SecondActivity的代码:

    public class FirstActivity extends AppCompatActivity {
        MyHandlerThread mThread;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_first);
    
            //在非主线程中注册BroadcastReceiver
            mThread = new MyHandlerThread(this, "ZJTest");
            mThread.start();
            mThread.getLooper();
    
            Button button = (Button)findViewById(R.id.first_button);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent i = new Intent();
                    i.setClass(getApplicationContext(), SecondActivity.class);
                    startActivity(i);
                }
            });
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mThread.quitSafely();
        }
    
        private class MyHandlerThread extends HandlerThread {
            private LocalBroadcastReceiver mReceiver;
            private Context mContext;
    
            MyHandlerThread(Context context, String name) {
                super(name);
                mContext = context.getApplicationContext();
            }
    
            @Override
            protected void onLooperPrepared() {
                mReceiver = new LocalBroadcastReceiver();
                //注册
                LocalBroadcastManager.getInstance(mContext)
                        .registerReceiver(mReceiver, new IntentFilter("ZJTest"));
            }
    
            @Override
            public boolean quitSafely() {
                //反注册
                LocalBroadcastManager.getInstance(mContext)
                        .unregisterReceiver(mReceiver);
                return super.quitSafely();
            }
        }
    
        private class LocalBroadcastReceiver extends BroadcastReceiver {
            @Override
            public void onReceive(Context context, Intent intent) {
                String name = Thread.currentThread().getName();
                //打印线程名称
                Toast.makeText(context, "MyName is " + name, Toast.LENGTH_LONG).show();
            }
        }
    }
    

    不论SecondActivity在主线程还是其它线程,调用sendBroadcast接口发送广播时,
    Toast均会提示“MyName is main”。
    但如果SecondActivity在其它线程调用sendBroadcastSync函数,例如:

    public class SecondActivity extends AppCompatActivity {
        private HandlerThread mThread;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_second);
    
            Button button = (Button) findViewById(R.id.second_button);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //线程名为“test”
                    mThread = new HandlerThread("test");
                    mThread.start();
                    Handler handler = new Handler(mThread.getLooper());
    
                    //线程中调用sendBroadcastSync
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            LocalBroadcastManager.getInstance(getApplicationContext())
                                    .sendBroadcastSync(new Intent("ZJTest"));
                        }
                    });
                    finish();
                }
            });
        }
    
        @Override
        public void onDestroy() {
            mThread.quit();
            super.onDestroy();
        }
    }
    

    此时,Toast提示为“MyName is test”(后台线程抛出Toast后结束,Toast不会主动消失,除非回收进程,这里仅作为测试)。

    3、总结

    分析了LocalBroadcastManager后,我们知道了其原理实际上是:
    将AMS中关于广播的处理流程移植到了本地。
    由进程独立完成广播相关组件信息的存储、匹配及接口回调。

    相关文章

      网友评论

          本文标题:Android LocalBroadcastManager 的使

          本文链接:https://www.haomeiwen.com/subject/xrgxyftx.html