Android高效安全的本地广播LocalBroadcast完全解析

背景

广播作为Android 四大组件有非常广泛的用途。广播可以用作进程间通信,也会用作进程内部某些组件内消息的传递。
这就会有个问题,如果想让发送的广播只有我自己能收到,不想被别人劫持到,来获取到广播中的敏感信息。
另外其他人如果发送相同Action的广播来伪造真正的广播,就会欺骗我的receiver.

如何安全高效的实现进程内部的广播发送呢?
有人说可以使用给广播加权限啊,你可以在Intent中指定PackageName 啊,后面的文章详解,先简单看下:

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

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

当然这都是书上告诉我们的方式,但是我感觉还不够简单。当然经过一番配置你可以实现了。
好了,现在安全解决了,那高效呢?

我们翻看context.sendBroadcast源码,看到发送广播的流程真的是相当的复杂啊。曾经天真年少的我竟然幻想一天弄懂广播的整个过程,但当我看到sendBroadcast方法的行数时我脸上是大写的崩溃。暂且不谈广播队列的分发规则和过程。这中间是存在的两次binder call就让这个过程变的不是那么高效。
首先你sendBroadcast会把广播信息告诉System_server (第一次Binder call),然后system_server经过一番查看找到你要的receivers,然后进入分发队列等待分发(过程很复杂),然后调用APP进程receiver的onReceiver()方法(第二次Binder call).大兄弟,我明明只想在我的进程内部发送一个广播在进程内部接收,为啥还要通过system_server呢。就算你长得帅,你有Free style,可是你很忙啊,找你的人那么多。自己的事情自己做,这是小学了老师经常教导我们的。看来Google的程序员一直没有忘记小学老师的教诲:

简介

看,迈着整齐步伐雄赳赳气昂昂向我们走来的是LocalBroadcast:
先来看官方说明:

 **
 - 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 {@link android.content.Context#sendBroadcast}:
 - 
    -
  • 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. -
*/

意思就是这个很牛逼,和全局广播相比有很多数不清的优势。(看来实现这个的哥们和实现全局广播的哥们关系不大好,竟然用这个词语: has a number of advantages) 。

  • 广播中携带的数据只会在你的APP中,不会暴露给其他APP,所以不用担心数据泄露的问题。
  • 其他APP无法伪造广播来欺骗你的Receiver

源码分析

我们下面来看下LocalBroadcastManager的源码:
https://android.googlesource.com/platform/frameworks/support/+/android-support-lib-19.1.0/v4/java/android/support/v4/content/LocalBroadcastManager.java

1.先来看下LocalBroadcastManager的构造,是使用标准的单例模式实现的。
APP开发者拿到mInstance之后就可以调用registerReceiver、unregisterReceiver、sendBroadcast。

    private final Handler mHandler;
    private static final Object mLock = new Object();
    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);
                }
            }
        };
    }

看到构造函数中没有做复杂的操作,在主线程初始化了一个Handler.
可以猜测到这个Handler正是用于对广播的分发。

2.广播的注册、反注册、发送流程
如果让我们来自己来实现广播的注册、反注册、发送我们会怎么搞呢?
首先,注册的时候需要提供BroadcastReceiver和对应的IntentFilter,我们可以对这种数据结构进行封装,放到一个类中ReceiverRecord。
然后维护一个ReceiverRecord对象列表,用于记录当前注册了哪些BroadcastReceiver。可以简单使用ArrayList.
在unRegister的时候根据提供的BroadcastReceiver对象,遍历List找出对应的receiver进行移除。
这样每来一个unRegister我们都需要对Receiver列表做一次遍历,开销有点大,在查操作比较多的时候我们可以使用MAP。
HashMap
ReceiverRecord中已经包含BroadcastReceiver对象了,所以value直接使用IntentFilte就行了,简化数据结构。
那如果一个Receiver注册了多个IntentFilter呢?比如说一个receiver对象注册两次传入不同的IntentFilter.所以Value需要改造为ArrayList。 最终用于维护当前Reciver对象列表的数据结构是这样事儿的:
HashMap> mReceivers.
当删除时可以通过receiver对象为key在map中快速查找并移除。

发送广播的时候呢?我们知道sendBroadcast时只传入了Intent对象,Intent携带了Action用于和已经注册的receiver匹配。在查找receiver时,需要对HashMap> mReceivers 的Value进行遍历,每一个Value ArrayList 又需要遍历一次。这个查找的开销实在太大了。
看来我们为了实现Action和receiver的快速匹配需要再维护一个数据结构了。同样是频繁查找的需求使用HashMap.
将Action作为Key,value肯定是与之匹配的receiver了。因为一个Action可能会对应多个receiver,receiver注册的时候可以使用相同的Action.所以value需要使用ArrayList. 当发送广播时可以快速根据Action找到对应的receiver。对了,不仅仅要使用Action匹配,filter中还有其他信息匹配成功之后才能确认是真正的receiver.所以需要使用ReceiverRecord作为value,因为不仅包含了receiver对象,同时包含了IntentFilter.所以最终的数据结构是HashMap>.

我们来看Google是怎么实现的呢?

先来看两个内部类:

    //内部类ReceiverRecord Receiver记录:用于记录reciver,对应的IntentFilter和是否在broadcast状态
    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();
        }
    }

    //BroadcastRecord 广播记录:用于记录广播的intent以及有哪些对应的ReceiverRecord
    private static class BroadcastRecord {
        final Intent intent;
        final ArrayList receivers;

        BroadcastRecord(Intent _intent, ArrayList _receivers) {
            intent = _intent;
            receivers = _receivers;
        }
    }

再来看几个成员变量:

  //维护一个mReceivers Map 记录一个所有的receiver,每一个对应哪些Intentfilters。主要用于记录当前有哪些receiver需要维护接受广播。
     //方便广播的反注册,反注册时可以快速找到filter从而找到Action,从而操作mAction.试想如果没有mReceivers,只能全部遍历mAction找出所有BroadcastRecord,从而找到filter和Action,性能很差。
    private final HashMap> mReceivers
            = new HashMap>();
    //维护一个mActions Map 记录所有的Action,每一个对应哪些ReceiverRecord
    private final HashMap> mActions
            = new HashMap>();

    //维护一个List,记录当前正处在等待状态的广播BroadcastRecord,通过BroadcastRecord可以找到intent对应的receivers
    private final ArrayList mPendingBroadcasts
            = new ArrayList();

广播的发送过程

    /**
     * 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) {
            ReceiverRecord entry = new ReceiverRecord(filter, receiver);
            //查找receiver有没有在mReceiver记录中,如果不在需要添加进去。
            ArrayList filters = mReceivers.get(receiver);
            if (filters == null) {
                filters = new ArrayList(1);
                mReceivers.put(receiver, filters);
            }
            //将IntentFilter加入receiver对应的匹配规则中。filters为mReceivers map的value,类型是IntentFilter的ArrayList。
            //同一个receiver可能有多个IntentFilter。mReceivers就记录了所有的receiver,并且指明每一个receiver所能匹配到的IntentFilter.
            filters.add(filter);
            //开始遍历IntentFilter中的Action.检查Action是否在mActions,如果不在不要添加进去。
            //mActions是Action为Key , ArrayList为value的MAP。
            //记录了当前有那些Action,并且每个Action对应的Receiver(ReceiverRecord)是哪个。
            for (int i=0; i entries = mActions.get(action);
                if (entries == null) {
                    entries = new ArrayList(1);
                    mActions.put(action, entries);
                }
                //将ReceiverRcoder对象加入entries:entries是该Action对应的ReceiverRecord列表。
                entries.add(entry);
            }
        }
    }

广播的反注册过程

    /**
     * Unregister a previously registered BroadcastReceiver.  All
     * 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) {
            //在Receiver列表中移除要注销的receiver,返回对应的filters.
            ArrayList filters = mReceivers.remove(receiver);
            if (filters == null) {
                return;
            }
            //在Action Map中移除对应的receiver
            for (int i=0; i receivers = mActions.get(action);
                    if (receivers != null) {
                        for (int k=0; k
广播的发送过程:
    /**
     * 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) {
            final String action = intent.getAction();
            final String type = intent.resolveTypeIfNeeded(
                    mAppContext.getContentResolver());
            final Uri data = intent.getData();
            final String scheme = intent.getScheme();
            final Set categories = intent.getCategories();

            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);

            //根据Action从mActions MAP 中取对应的receivers.
            ArrayList entries = mActions.get(intent.getAction());
            if (entries != null) {
                if (debug) Log.v(TAG, "Action list: " + entries);

                ArrayList receivers = null;
                //遍历receivers找出符合IntentFilter条件的
                for (int i=0; i= 0) {
                        if (debug) Log.v(TAG, "  Filter matched!  match=0x" +
                                Integer.toHexString(match));
                        if (receivers == null) {
                            receivers = new ArrayList();
                        }
                        receivers.add(receiver);
                        //将receiver 是否在分发中置位true
                        receiver.broadcasting = true;
                    } else {
                        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);
                        }
                    }
                }
                //将broadcasting置位false 加入mPendingBroadcasts等待队列中.
                if (receivers != null) {
                    for (int i=0; i

可以看到最终把要发送的广播加入队列mPendingBroadcasts,然后使用Handler发送消息给主线程处理的,调用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

LocalBroadcast也支持使用同步的方式进行分发:

 /**
     * 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();
        }
    }

总结

1.LocalBroadcast是APP内部维护的一套广播机制,有很高的安全性和高效性。
所以如果有APP内部发送、接收广播的需要应该使用LocalBroadcast。
2.Receiver只允许动态注册,不允许在Manifest中注册。
3.LocalBroadcastManager所发送的广播action,只能与注册到LocalBroadcastManager中BroadcastReceiver产生互动。

你可能感兴趣的:(Android高效安全的本地广播LocalBroadcast完全解析)