Android 源码系列之<三>从安全的角度深入理解BroadcastReceiver(下)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/51152723

在上一篇文章中我们结合实验讲解了有关使用BroadcastReceiver存在的安全性问题并且给出了相应的解决方案,如果你还没有看过上篇文章请点击这里,最后一条的解决方案是采用官方v4包中的LocalBroadcastManager来解决的,官方介绍说这种方式不仅安全而且更高效,今天我们就从源码的角度来了解一下LocalBroadcastManager,如果你对它非常熟悉,可以跳过本文了(*^__^*)

分析源码之前,我们首先回顾一下LocalBroadcastManager的用法:

1、定义广播接收器,代码如下:

public class LocalBroadcastReceiver extends BroadcastReceiver {

	public static final String LOCAL_CUSTOM_ACTION = "com.llew.seetao.customaction";

	public LocalBroadcastReceiver() {
	}

	@Override
	public void onReceive(Context context, Intent intent) {
		Log.e(this.getClass().getSimpleName(), "current time is :" + System.currentTimeMillis());
	}
}

2、注册广播接收器,代码如下:

private void registerBroadcastReceiver() {
	mBroadcastManager = LocalBroadcastManager.getInstance(MainActivity.this);
	mLocalReceiver = new LocalBroadcastReceiver();
	IntentFilter filter = new IntentFilter(LocalBroadcastReceiver.LOCAL_CUSTOM_ACTION);
	mBroadcastManager.registerReceiver(mLocalReceiver, filter);
}

3、发送一个广播,代码如下:

public void sendBroadcast(View v) {
	Intent intent = new Intent(LocalBroadcastReceiver.LOCAL_CUSTOM_ACTION);
	mBroadcastManager.sendBroadcast(intent);
}

分析源码一般是从使用开始分析,在开始分析之前我们先大致了解一下LocalBroadcastManager中都定义了哪些属性吧,部分属性如下所示:

private final Context mAppContext;
// 缓存集合
private final HashMap<BroadcastReceiver, ArrayList<IntentFilter>> mReceivers
        = new HashMap<BroadcastReceiver, ArrayList<IntentFilter>>();
private final HashMap<String, ArrayList<ReceiverRecord>> mActions
        = new HashMap<String, ArrayList<ReceiverRecord>>();

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

private final Handler mHandler;

private static LocalBroadcastManager mInstance;

主要定义了代表当前App运行环境的mAppContext属性,然后定义了三个集合类型的属性,既然是定义为集合类型肯定是用来装载数据的,具体装载什么类型的数据,看泛型定义的是什么类型就明确了,了解了相关属性后我们开始分析源码。

首先mBroadcastManager的实例化是同过LocalBroadcastManager的全局静态方法getInstance()来实现的,该方法需要一个Context,我们直接传递当前的Context就好了。我们进入该方法查看一下是如何进行实例化的,代码如下:

public static LocalBroadcastManager getInstance(Context context) {
    synchronized (mLock) {
        if (mInstance == null) {
            mInstance = new LocalBroadcastManager(context.getApplicationContext());
        }
        return mInstance;
    }
}

此方法很简单,通过单例模式来保证只有一个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);
            }
        }
    };
}

在构造方法中把传递进来的context赋值给了mAppContext,然后通过context的getMainLooper()方法初始化了mHandler变量(这种方式是基于主线程的Looper进行了初始化,之所以采用此方式来初始化mHandler是因为采用了主线程的消息队列,有关Handler,Message等相关介绍会在后续文章中做详细解说)并且重写了handleMessage()方法,在该方法中只处理类型为MSG_EXEC_PENDING_BROADCASTS的消息,当收到此类型的消息时就去调用executePendingBroadcasts()方法,其他情况下转交给其父类来处理,这就是初始化的过程。

我们接着看注册广播的过程,注册广播也很简单,直接调用LocalBroadcastManager的registerReceiver()方法即可,该方法需要传递BroadcastReceiver和IntentFilter实例,在以上步骤2中我们定义了BroadcastReceiver和IntentFilter,那就看一看LocalBroadcastManager的registerReceiver()方法都做了怎样的操作吧,代码如下:

public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    synchronized (mReceivers) {
    	// 根据传递进来的receiver和filter创建ReceiverRecord实例entry
        ReceiverRecord entry = new ReceiverRecord(filter, receiver);
        // 根据receiver从缓存中查找
        ArrayList<IntentFilter> filters = mReceivers.get(receiver);
        if (filters == null) {
        	// 如果为空就创建,然后加入缓存中
            filters = new ArrayList<IntentFilter>(1);
            mReceivers.put(receiver, filters);
        }
        // 添加操作
        filters.add(filter);

        // 循环遍历filter中的action
        for (int i=0; i<filter.countActions(); i++) {
        	// 循环获取每一个action
            String action = filter.getAction(i);
            // 根据action从缓存中查找
            ArrayList<ReceiverRecord> entries = mActions.get(action);
            if (entries == null) {
            	// 如果缓存中不存在就创建,然后加入到缓存中
                entries = new ArrayList<ReceiverRecord>(1);
                mActions.put(action, entries);
            }
            // 添加操作
            entries.add(entry);
        }
    }
}

代码中的注释说的很详细,那还是大致捋一下流程吧,在该方法中通过synchronize关键字对mReceivers进行了加锁操是为了保证数据的一致性,首先根据传递进来的receiver和filter构建了一个ReceiverRecord对象entry,见名字就可以猜测是一个封装,然后根据receiver从缓存集合mReceivers中查找是否存在对应的filters,如果缓存中不存在filters就新建一个并把该filters存储在mReceivers中,最后把传递进来的filter装载进filters中。缓存完传递进来的receiver和filter后开始循环遍历filter中包含的action,逻辑如刚刚一样,先是根据action从缓存中查找entries,如果不存在就新建一个然后把新建的entries装载进mActions缓存中,最后把最初新建的entry装载进entries中,这就是注册广播接收器的主要流程,核心思想是把传递进来的receiver和filter添加进缓存中。

我们看一下如何发送一个广播,代码如下:

public boolean sendBroadcast(Intent intent) {
    synchronized (mReceivers) {
    	// 从intent中获取相应值
    	final ContentResolver resolver = mAppContext.getContentResolver();
        final String action = intent.getAction();
        final String type = intent.resolveTypeIfNeeded(resolver);
        final Uri data = intent.getData();
        final String scheme = intent.getScheme();
        final Set<String> categories = intent.getCategories();
        // 根据action从缓存中查找相应值
        ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
        if (entries != null) {
            ArrayList<ReceiverRecord> receivers = null;
            // 缓存中存在对应的action集合entries,开始循环遍历entries
            for (int i=0; i<entries.size(); i++) {
            	// 拿到每一个ReceiverRecord实例receiver
                ReceiverRecord receiver = entries.get(i);
                // 如果receiver.broadcasting为true就跳过本次操作继续循环
                if (receiver.broadcasting) {
                    continue;
                }
                // 调用IntentFilter的match()方法来和intent相匹配,如果返回值小于0表示不匹配
                int match = receiver.filter.match(action, type, scheme, data, categories, "LocalBroadcastManager");
                if (match >= 0) {
                	// 匹配成功
                    if (receivers == null) {
                        receivers = new ArrayList<ReceiverRecord>();
                    }
                    // 把receiver添加进receivers中
                    receivers.add(receiver);
                    // 设置receiver.broadcasting值为true
                    receiver.broadcasting = true;
                }
            }
            // 如果receivers不为空表示匹配到了BroadcastReceiver
            if (receivers != null) {
            	// 循环遍历匹配到的Receiver,把其broadcasting设置为false
                for (int i=0; i<receivers.size(); i++) {
                    receivers.get(i).broadcasting = false;
                }
                // 根据intent和receivers来new一个BroadcastRecord并加入到mPendingBroadcasts集合中
                mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
                // 检测消息队列中时候含有Message.what为MSG_EXEC_PENDING_BROADCASTS的消息,如果没有就发送一个消息
                if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
                    mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
                }
                // 表示发送广播成功
                return true;
            }
        }
    }
    // 发送广播失败
    return false;
}

sendBroadcast()方法很简单,只接收一个Intent,并且返回一个boolean值(true:广播发送成功,false:广播发送失败)。我们接着往下读代码码,为了保证数据一致性还是给mReceivers进行了加锁操作,先是获取到intent中携带的值,比如:action,type,data,schema,categories等,然后根据intent中的action从mActions缓存中查找entries实例,接下来是个if语句,如果条件成立就进入条件语句否则直接返回false(false可理解为发送广播失败),进入if语句后循环遍历entries集合拿到集合中的每一个ReceiverRecord对象receiver,如果receiver.braodcasting=true则跳过当前操作继续下一轮循环,否则调用receiver.filter.match()方法来和intent的携带值进行匹配,通过查看文档可知match()方法返回int类型的值,如果返回值小于0就表示匹配失败,当match大于等于0时进入if语句内把匹配到的receiver记录下来保存在receivers集合中并且设置其broadcasting值为true,此论循环完成之后receivers中装载的都是符合条件的Receiver了,然后又做了一个循环把刚刚符合条件的Receiver的broadcasting属性全都设置为false,最后把这些值放入到pending集合中,加入集合中之后通过mHandler检查消息对列中是否包含了消息类型为MSG_EXEC_PENDING_BROADCASTS的消息,如果没有包含就发送一个消息类型为MSG_EXEC_PENDING_BROADCASTS的消息,消息发送之后返回true,表示广播发送成功。

通过mHandler发送消息类型为MSG_EXEC_PENDING_BROADCASTS的消息也就是说需要在mHandler的handleMessage()方法中对此类消息做出响应,在mHandler的handleMessage的方法中调用到了executePendingBroadcasts()方法,我们看看这个方法所做的操作吧,代码如下:

private void executePendingBroadcasts() {
    while (true) {
        BroadcastRecord[] brs = null;
        // 加锁操作
        synchronized (mReceivers) {
        	// 这个加锁的代码块就是把mPendingBroadcasts中存储的值移动到临时变量brs中,然后清空自己
            final int N = mPendingBroadcasts.size();
            if (N <= 0) {
                return;
            }
            brs = new BroadcastRecord[N];
            mPendingBroadcasts.toArray(brs);
            mPendingBroadcasts.clear();
        }
        // 循环遍历brs,最后调用到Receiver的onReceive()方法并把相应值传递进去
        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);
            }
        }
    }
}

方法executePendingBroadcats()的逻辑很简单,就是先把缓存中的数据移动到一个临时数组中并把缓存清空,接着依次循环临时数组中存在的BroadcastRecord,然后回调Receiver的onReceive()方法,这就最终导致我们注册的BroadcastReceiver的onReceive()方法得到了调用。注册广播和发送广播的逻辑算是分析完了,注册广播接收器的核心逻辑就是把注册的每一个BroadcastReceiver存放在缓存中;发送广播的核心逻辑就是根据IntentFilter的match()方法做匹配,把匹配到的广播接收器添加到一个新的缓存集合中后发送消息,捕获消息后依次回调BroadcastReceiver的onReceive()方法。既然有注册和发送操作,那肯定也少不了注销操作,这时候你肯定会猜想注销广播接收器的操作应该是把广播接收器从缓存中清除吧?恭喜你答对了,代码如下:

public void unregisterReceiver(BroadcastReceiver receiver) {
    synchronized (mReceivers) {
        ArrayList<IntentFilter> filters = mReceivers.remove(receiver);
        // 缓存中不存在则直接返回
        if (filters == null) {
            return;
        }
        // 循环遍历缓存,找出相匹配的BroadcastReceiver后从缓存中删除
        for (int i=0; i<filters.size(); i++) {
            IntentFilter filter = filters.get(i);
            for (int j=0; j<filter.countActions(); j++) {
                String action = filter.getAction(j);
                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);
                    }
                }
            }
        }
    }
}

注销广播接收器的核心就是从receivers和mActions缓存中删除相关值,到这里有关LocalBroadcastManager的源码算是分析完了,其核心主要是一下两点:

  1. 借助Handler进行消息传递
  2. 使用IntentFilter的match()功能

前文中提到在应用内发送广播使用LocalBroadcastManager是官方极力推荐的,它不仅安全更且高效,安全主要是用了Handler的消息机制,使消息只能在应用内发送和接收;高效主要是在缓存中直接遍历查询比使用原来的Binder机制进行通信的方式效率要高很多。好了,看到这后你是不是决定不再使用原来方式来发送或接受广播了?呵呵,反正我是抛弃了原来的使用方式……

从安全的角度深入理解BroadcastReceiver就告一段落了,感谢收看(*^__^*) …

时间: 2024-10-24 00:26:20

Android 源码系列之<三>从安全的角度深入理解BroadcastReceiver(下)的相关文章

Android 源码系列之&lt;二&gt;从安全的角度深入理解BroadcastReceiver(上)

提起BroadcastReceiver大家都很熟悉,它和Activity,Service以及ContentProvider并称为Android的四大组件(四大金刚),可见BroadcastReceiver的重要性,今天我们主要从安全的角度来讲解称为四大组件之一的BroadcastReceiver.可能有的童靴看到这里会有疑问,BroadcastReceiver有啥好讲的,不就是先定义自己的广播接收器然后在manifest.xml文件中注册,在需要发送广播的地方调用Context的sendBroa

Android 源码系列之&lt;十一&gt;从源码的角度深入理解AccessibilityService,打造自己的APP小外挂(下)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52843637 在上篇文章Android 源码系列之<十>从源码的角度深入理解AccessibilityService,打造自己的APP小外挂(上)中我们讲解了通过AccessibilityService实现自动安装APK小外挂的操作流程,如果你还没有看过上篇文章请点击这里.在这篇文章中我将带领小伙伴从源码的角度来深入学习一下AccessibilityServie的技术实现原理,希望这

Android 源码系列之&lt;十三&gt;从源码的角度深入理解LeakCanary的内存泄露检测机制(中)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52958563 在上篇文章Android 源码系列之<十二>从源码的角度深入理解LeakCanary的内存泄露检测机制(上)中主要介绍了Java内存分配相关的知识以及在Android开发中可能遇见的各种内存泄露情况并给出了相对应的解决方案,如果你还没有看过上篇文章,建议点击这里阅读一下,这篇文章我将要向大家介绍如何在我们的应用中使用square开源的LeakCanary库来检测应用中出

Android 源码系列之&lt;十&gt;从源码的角度深入理解AccessibilityService,打造自己的APP小外挂(上)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52822148 说起外挂特别是玩游戏的小伙伴估计对它很熟悉,肯定有部分小伙伴使用过,至于为什么使用它,你懂得(*^__^*) --我最早接触外挂是在大二的时候,那时候盛行玩QQ农场,早上一睁眼就是打开电脑先把自己的菜收了,收完之后再去偷别人的:后来童靴说非凡软件上有一个偷菜外挂,于是赶紧整了一个,有了外挂之后就告别了体力时代,省时又省力--既然在PC上有外挂,那在智能手机上可以做外挂呢?

hbase源码系列(三)Client如何找到正确的Region Server

客户端在进行put.delete.get等操作的时候,它都需要数据到底存在哪个Region Server上面,这个定位的操作是通过HConnection.locateRegion方法来完成的. loc = hConnection.locateRegion(this.tableName, row.getRow()); 这里我们首先要讲hbase的两张元数据表-ROOT-和.META.表,它们一个保存着region的分部信息,一个保存着region的详细信息.在<hbase实战>这本书里面详细写了

Android 源码系列之&lt;十四&gt;从源码的角度深入理解LeakCanary的内存泄露检测机制(下)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52958567 在上边文章Android 源码系列之<十三>从源码的角度深入理解LeakCanary的内存泄露检测机制(中)由于篇幅原因仅仅向小伙伴们讲述了在Android开发中如何使用LeakCanary来检测应用中出现的内存泄露,并简单的介绍了LeakCanary的相关配置信息.根据上篇文章的介绍我们知道LeakCanary为了不给APP进程造成影响所以新开启了一个进程,在新开启的

Android源码浅析(三)——Android AOSP 5.1.1源码的同步sync和编译make,搭建Samba服务器进行更便捷的烧录刷机

Android源码浅析(三)--Android AOSP 5.1.1源码的同步sync和编译make,搭建Samba服务器进行更便捷的烧录刷机 最近比较忙,而且又要维护自己的博客,视频和公众号,也就没仔细的梳理源码的入门逻辑,今天也就来讲一个源码的玩法,各位看官,一起学习学习! 看本篇博客之前,先看下我的前面两篇 Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置 Android源码浅析(二)--Ubuntu Roo

Spark源码系列(三)作业运行过程

导读 看这篇文章的时候,最好是能够跟着代码一起看,我是边看代码边写的,所以这篇文章的前进过程也就是我看代码的推进过程. 作业执行 上一章讲了RDD的转换,但是没讲作业的运行,它和Driver Program的关系是啥,和RDD的关系是啥? 官方给的例子里面,一执行collect方法就能出结果,那我们就从collect开始看吧,进入RDD,找到collect方法. def collect(): Array[T] = { val results = sc.runJob(this, (iter: It

Android 源码系列之&lt;一&gt;从源码的角度深入理解ImageView的ScaleType属性

做Android开发的童靴们肯定对系统自带的控件使用的都非常熟悉,比如Button.TextView.ImageView等.如果你问我具体使用,我会给说:拿ImageView来说吧,首先创建一个新的项目,在项目布局文件中应用ImageView控件,代码如下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.