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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android高效安全的本地广播LocalBroadcast完全解析相关的知识,希望对你有一定的参考价值。

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

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

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

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

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

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

我们下面来看下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。

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

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

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

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

先来看两个内部类:

再来看几个成员变量:

广播的发送过程

广播的反注册过程

可以看到最终把要发送的广播加入队列mPendingBroadcasts,然后使用Handler发送消息给主线程处理的,调用executePendingBroadcasts()进行分发。

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

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

Android学习总结——本地广播机制

   为了简单解决广播的安全性问题,Android引入了一套本地广播机制,使用这个机制发出的广播只能在程序的内部进行传递,只能接受来自本应用程序发出的广播。否则当我们发送一些携带关键数据的广播可能被截获,一些恶意程序也可能向我们的广播接收器中发送各种垃圾广播。

用法如下:(主要就是使用LocalBroadcastManager来对广播进行管理,并提供注册广播和发送广播的方法)。

package com.example.xch.localbroadcast;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private IntentFilter intentFilter;
    private Button sendBroadcast;
    private LocalReceiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        localBroadcastManager=LocalBroadcastManager.getInstance(this);//通过getInstance()得到LocalBroadcastManager的实例

        sendBroadcast=(Button) findViewById(R.id.sendBroadcast);
        sendBroadcast.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //发送本地广播
                Intent intent=new Intent("com.example.LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent);
            }
        });
        //注册本地广播监听器
        intentFilter=new IntentFilter();
        intentFilter.addAction("com.example.LOCAL_BROADCAST");
        localReceiver=new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver,intentFilter);
    }
    class LocalReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context,"接受到本地广播",Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        localBroadcastManager.unregisterReceiver(localReceiver);//关闭广播
    }
}

 

以上是关于Android高效安全的本地广播LocalBroadcast完全解析的主要内容,如果未能解决你的问题,请参考以下文章

Android本地广播的使用

Android学习总结——本地广播机制

详解广播机制,Broadcast Receiver,Android进阶必备知识。(附使用广播实现一键强制下线功能)

Android广播的使用(自定义广播和本地广播)

Android四大组件——BroadcastReceiver普通广播有序广播拦截广播本地广播Sticky广播系统广播

Android 发送本地广播 以及创建广播接收器