Android 之使用LocalBroadcastManager,源码解析

来源:互联网 发布:新浪首页软件下载 编辑:程序博客网 时间:2024/05/16 20:29

在Android系统中,BroadcastReceiver的设计初衷就是从全局考虑的,可以方便应用程序和系统、应用程序之间、应用程序内的通信,所以对单个应用程序而言BroadcastReceiver是存在安全性问题的,相应问题及解决如下:

1、当应用程序发送某个广播时系统会将发送的Intent与系统中所有注册的BroadcastReceiver的IntentFilter进行匹配,若匹配成功则执行相应的onReceive函数。可以通过类似sendBroadcast(Intent, String)的接口在发送广播时指定接收者必须具备的permission。或通过Intent.setPackage设置广播仅对某个程序有效。

2.  当应用程序注册了某个广播时,即便设置了IntentFilter还是会接收到来自其他应用程序的广播进行匹配判断。对于动态注册的广播可以通过类似registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler)的接口指定发送者必须具备的permission,对于静态注册的广播可以通过android:exported="false"属性表示接收者对外部应用程序不可用,即不接受来自外部的广播。


上面两个问题其实都可以通过LocalBroadcastManager来解决:

  

Android v4 兼容包提供android.support.v4.content.LocalBroadcastManager工具类,帮助大家在自己的进程内进行局部广播发送与注册,使用它比直接通过sendBroadcast(Intent)发送系统全局广播有以下几点好处。

1    因广播数据在本应用范围内传播,你不用担心隐私数据泄露的问题。

2    不用担心别的应用伪造广播,造成安全隐患。

3    相比在系统内发送全局广播,它更高效。

其使用方法也和正常注册广播类似:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1.  LocalBroadcastManager mLocalBroadcastManager;    
  2.  BroadcastReceiver mReceiver;    
  3.   
  4.   
  5.  IntentFilter filter = new IntentFilter();    
  6.  filter.addAction("test");    
  7.   
  8.  mReceiver = new BroadcastReceiver() {    
  9.             @Override    
  10.             public void onReceive(Context context, Intent intent) {    
  11.                 if (intent.getAction().equals("test")) {    
  12.                     //Do Something  
  13.                 }   
  14.             }    
  15.         };    
  16. mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);  
  17. mLocalBroadcastManager.registerReceiver(mReceiver, filter);  
  18. 当然,和正常广播一样,也要在对应的生命周期中反注册掉:  
  19.  @Override  
  20. protected void onDestroy() {  
  21.   
  22.   
  23.    super.onDestroy();  
  24.   
  25.   
  26.    mLocalBroadcastManager.unregisterReceiver(mReceiver);  
  27.   
  28.   
  29. }    

Android编程之LocalBroadcastManager源码详解

LocalBroadcastManager 是V4包中的一个类,主要负责程序内部广播的注册与发送。也就是说,它只是适用代码中注册发送广播,对于在AndroidManifest中注册的广播接收,则不适用。

官方英文解释如下:

Helper to register for and send broadcasts of Intents to local objects within your process. This is has a number of advantages over sending global broadcasts with sendBroadcast(Intent):

You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data.
It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit.
It is more efficient than sending a global broadcast through the system. 


接下来如正题,先看一下全局变量:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private final Context mAppContext;  
  2. private final HashMap<BroadcastReceiver, ArrayList<IntentFilter>> mReceivers = new HashMap();  
  3.   
  4. private final HashMap<String, ArrayList<ReceiverRecord>> mActions = new HashMap();  
  5.   
  6. private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList();  
  7. static final int MSG_EXEC_PENDING_BROADCASTS = 1;  
  8. private final Handler mHandler;  
  9. private static final Object mLock = new Object();  
  10. private static LocalBroadcastManager mInstance;  

mAppContext:即ApplicationContext,所以不用担心内存泄漏问题。

mReceivers:记录注册的BroadcastReceiver及其IntentFilter的数组,这里为什么是数组,下面会有讲到。

mActions:记录IntentFilter中的action对应的BroadcastReceiver数组。虽然这里写的是ReceiverRecord类型,但它实际上是一个内部类,主要保存了BroadcastReceiver及其对应的IntentFilter。

mPendingBroadcasts:在发送广播时,会根据Intent的action,找到与之相对应的BroadcastReceiver。还记得吗?action是可以对应多个BroadcastReceiver,所以这里是数组。

mHandler:就做了一件事情,发送广播。

mLock:同步锁。

mInstance:自己本身的实例对象,有经验的可能已经猜到了,没错,LocalBroadcastManager是一个单例。


看一下它的构造方法,标准的单例写法:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public static LocalBroadcastManager getInstance(Context context) {  
  2.     synchronized (mLock) {  
  3.         if (mInstance == null) {  
  4.             mInstance = new LocalBroadcastManager(  
  5.                     context.getApplicationContext());  
  6.         }  
  7.         return mInstance;  
  8.     }  
  9. }  
  10.   
  11. private LocalBroadcastManager(Context context) {  
  12.     this.mAppContext = context;  
  13.     this.mHandler = new Handler(context.getMainLooper()) {  
  14.         public void handleMessage(Message msg) {  
  15.             switch (msg.what) {  
  16.             case MSG_EXEC_PENDING_BROADCASTS:  
  17.                 LocalBroadcastManager.this.executePendingBroadcasts();  
  18.                 break;  
  19.             default:  
  20.                 super.handleMessage(msg);  
  21.             }  
  22.         }  
  23.     };  
  24. }  

上面谈到的mAppContext是ApplicationContext的证据:mInstance = new LocalBroadcastManager(context.getApplicationContext());

而mHandler就是在构造时创建的,内部就做了一件事,发送广播:executePendingBroadcasts。


接下来就看一下如何注册广播接收者:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {  
  2.     synchronized (this.mReceivers) {  
  3.         ReceiverRecord entry = new ReceiverRecord(filter, receiver);  
  4.         ArrayList filters = (ArrayList) this.mReceivers.get(receiver);  
  5.         if (filters == null) {  
  6.             filters = new ArrayList(1);  
  7.             this.mReceivers.put(receiver, filters);  
  8.         }  
  9.         filters.add(filter);  
  10.         for (int i = 0; i < filter.countActions(); i++) {  
  11.             String action = filter.getAction(i);  
  12.             ArrayList entries = (ArrayList) this.mActions.get(action);  
  13.             if (entries == null) {  
  14.                 entries = new ArrayList(1);  
  15.                 this.mActions.put(action, entries);  
  16.             }  
  17.             entries.add(entry);  
  18.         }  
  19.     }  
  20. }  

就是将BroadcastReceiver和IntentFilter建立一对多的对应关系。可以通过BroadcastReceiver找到其对应的IntentFilter,也可以通过IntentFilter中的action找到所对应的BroadcastReceiver。这里还要解释一下上面提到的mReceivers中,为什么保存的IntentFilter是数组形式。我们知道,IntentFilter中是可以保存多个action的,这也就是为什么它初始化成1个长度的数组。那么这里的数组的意义,其实很简单,就是能支持传入多个IntentFilter。虽然是支持传入多个IntentFilter,但如果里面的action是同名的话,也还是按同一个处理的,后面代码就能看出,action是以键的方法存起来的。


有注册,就得有取消注册:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public void unregisterReceiver(BroadcastReceiver receiver) {  
  2.     synchronized (this.mReceivers) {  
  3.         ArrayList filters = (ArrayList) this.mReceivers.remove(receiver);  
  4.         if (filters == null) {  
  5.             return;  
  6.         }  
  7.         for (int i = 0; i < filters.size(); i++) {  
  8.             IntentFilter filter = (IntentFilter) filters.get(i);  
  9.             for (int j = 0; j < filter.countActions(); j++) {  
  10.                 String action = filter.getAction(j);  
  11.                 ArrayList receivers = (ArrayList) this.mActions.get(action);  
  12.                 if (receivers != null) {  
  13.                     for (int k = 0; k < receivers.size(); k++) {  
  14.                         if (((ReceiverRecord) receivers.get(k)).receiver == receiver) {  
  15.                             receivers.remove(k);  
  16.                             k--;  
  17.                         }  
  18.                     }  
  19.                     if (receivers.size() <= 0)  
  20.                     this.mActions.remove(action);  
  21.                 }  
  22.             }  
  23.         }  
  24.     }  
  25. }  

简单来说,就是将BroadcastReceiver从mReceivers和mActions中移除掉。由于BroadcastReceiver是mReceivers的键,所以移除掉比较简单。而mActions就稍微复杂一些,需要根据BroadcastReceiver中的IntentFilter数组,从mActions中移除掉。

还记得注册时,同名的action可以对应不同的BroadcastReceiver吗,注意这里的一句话:if (((ReceiverRecord) receivers.get(k)).receiver == receiver),没错,它只会移除掉当前的,不会将action对应的BroadcastReceiver都删除掉。


最后就是关键的    public boolean sendBroadcast(Intent intent)方法,整个方法都是synchronized (this.mReceivers)的,由于这个方法内容太长,这里分段来讲解:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. String action = intent.getAction();  
  2. String type = intent.resolveTypeIfNeeded(this.mAppContext.getContentResolver());  
  3.   
  4. Uri data = intent.getData();  
  5. String scheme = intent.getScheme();  
  6. Set categories = intent.getCategories();  
  7.   
  8. boolean debug = (intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION ) != 0;  

首先取出intent中的参数,除了action外,其他都是用来作比较的。如果在intent中setFlag设置了Intent.FLAG_DEBUG_LOG_RESOLUTION,就会可以输出一些log信息。


然后就是从mActions中取出intent的action所对应的ReceiverRecord

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ArrayList entries = (ArrayList) this.mActions.get(intent.getAction());  
  2.                                           
  3. ArrayList receivers = null;  
  4. for (int i = 0; i < entries.size(); i++)  

这段就是for循环里面的内容:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ReceiverRecord receiver = (ReceiverRecord) entries.get(i);  
  2.   
  3. if (receiver.broadcasting) {  
  4.                       
  5.     } else {  
  6.         int match = receiver.filter.match(action, type, scheme,  
  7.         data, categories, "LocalBroadcastManager");  
  8.   
  9.         if (match >= 0) {                          
  10.             if (receivers == null) {  
  11.                 receivers = new ArrayList();  
  12.             }  
  13.             receivers.add(receiver);  
  14.             receiver.broadcasting = true;  
  15.         } else {  
  16.                                                   
  17.         }  
  18.     }  
  19. <span style="font-size:12px;">}</span>  

在这里,如果是发送中,就什么也不做。否则,先匹配一下receiver中的IntentFilter,如果匹配上,就设置其为发送中,即设置变量broadcasting为true。其意义在于避免重复发送。


最后,添加到ArrayList中:this.mPendingBroadcasts.add(new BroadcastRecord(intent,receivers));通知Handler,执行executePendingBroadcasts()方法。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (receivers != null) {  
  2.     for (int i = 0; i < receivers.size(); i++) {  
  3.         ((ReceiverRecord) receivers.get(i)).broadcasting = false;  
  4.     }  
  5.     this.mPendingBroadcasts.add(new BroadcastRecord(intent,  
  6.             receivers));  
  7.     if (!this.mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {  
  8.         this.mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);  
  9.     }  
  10.     return true;  
  11. }  

executePendingBroadcasts()方法就很简单了,就是取出mPendingBroadcasts数组中的BroadcastReceiver(在ReceiverRecord中保存其对象),调用其onReceive方法。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private void executePendingBroadcasts() {  
  2.     while (true) {  
  3.         BroadcastRecord[] brs = null;  
  4.         synchronized (this.mReceivers) {  
  5.             int N = this.mPendingBroadcasts.size();  
  6.             if (N <= 0) {  
  7.                 return;  
  8.             }  
  9.             brs = new BroadcastRecord[N];  
  10.             this.mPendingBroadcasts.toArray(brs);  
  11.             this.mPendingBroadcasts.clear();  
  12.         }  
  13.         for (int i = 0; i < brs.length; i++) {  
  14.             BroadcastRecord br = brs[i];  
  15.             for (int j = 0; j < br.receivers.size(); j++)  
  16.                 ((ReceiverRecord) br.receivers.get(j)).receiver.onReceive(  
  17.                         this.mAppContext, br.intent);  
  18.         }  
  19.     }  
  20. }  

还有一个方法sendBroadcastSync,平常我们一般不会用到这个方法,这里放一下源码:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public void sendBroadcastSync(Intent intent) {  
  2.     if (sendBroadcast(intent))  
  3.         executePendingBroadcasts();  
  4. }  

这里再补一个其内部类ReceiverRecord的源码:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private static class ReceiverRecord {  
  2.     final IntentFilter filter;  
  3.     final BroadcastReceiver receiver;  
  4.     boolean broadcasting;  
  5.   
  6.     ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {  
  7.         this.filter = _filter;  
  8.         this.receiver = _receiver;  
  9.     }  
  10. }  

小结:

1、LocalBroadcastManager在创建单例传参时,不用纠结context是取activity的还是Application的,它自己会取到tApplicationContext。

2、LocalBroadcastManager只适用于代码间的,因为它就是保存接口BroadcastReceiver的对象,然后直接调用其onReceive方法。

3、LocalBroadcastManager注册广播后,当该其Activity或者Fragment不需要监听时,记得要取消注册,注意一点:注册与取消注册在activity或者fragment的生命周期中要保持一致,例如onResume,onPause。

4、LocalBroadcastManager虽然支持对同一个BroadcastReceiver可以注册多个IntentFilter,但还是应该将所需要的action都放进一个IntentFilter,即只注册一个IntentFilter,这只是我个人的建议。

5、LocalBroadcastManager所发送的广播action,只能与注册到LocalBroadcastManager中BroadcastReceiver产生互动。如果你遇到了通过LocalBroadcastManager发送的广播,对面的BroadcastReceiver没响应,很可能就是这个原因造成的。

0 0
原创粉丝点击