Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Android中LocalBroadcastManager的基本用法及源码分析

$
0
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中关于广播的处理流程移植到了本地。
由进程独立完成广播相关组件信息的存储、匹配及接口回调。

作者:Gaugamela 发表于2017/2/23 17:06:44 原文链接
阅读:25 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>