专栏首页安卓圈BroadcastReceiver插件化解决方案

BroadcastReceiver插件化解决方案

1.静态广播和动态广播仅区别于注册方式的不同。静态广播的注册信息保存在PMS中,动态广播的注册信息保存在AMS中

2.发送广播,也就是Context的sendBroadcast方法,最终会调用AMN.getDefault().broadcastIntent,把要发送的广播告诉AMS;

  AMS在收到上述信息后,搜索AMS和PMS中保存的广播,看哪些广播符合条件,然后通知App进程启动这些广播,也就是调用这些广播的onReceive方法

3.无论发送广播还是接受广播,都携带一个筛选条件:intent-filter。

<receiver
    android:name=".MyReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="baobao2"/>
</receiver>
MyReceiver myReceiver = new MyReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("baobao2");
registerReceiver(myReceiver,intentFilter);

***动态广播的插件化解决方案***

使用前面介绍的dex合并技术,插件中的动态广播就可以被宿主App正常调用了

***静态广播的插件化解决方案***

1)PMS只能读取宿主App的AndroidManifest文件,读取其中的静态广播并注册。我们可以通过反射,手动控制PMS读取插件的AndroidManifest中声明的静态广播列表

2)遍历这个静态广播列表。使用插件的classLoader加载列表中的每个广播类,实例化成一个对象,然后作为动态广播注册到AMS中

public final class ReceiverHelper {

    private static final String TAG = "ReceiverHelper";

    /**
     * 解析插件Apk文件中的 <receiver>, 并存储起来
     *
     * @param apkFile
     * @throws Exception
     */
    public static void preLoadReceiver(Context context, File apkFile) {
        // 首先调用parsePackage获取到apk对象对应的Package对象
        Object packageParser = RefInvoke.createObject("android.content.pm.PackageParser");
        Class[] p1 = {File.class, int.class};
        Object[] v1 = {apkFile, PackageManager.GET_RECEIVERS};
        Object packageObj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage", p1, v1);

        // 读取Package对象里面的receivers字段,注意这是一个 List<Activity> (没错,底层把<receiver>当作<activity>处理)
        // 接下来要做的就是根据这个List<Activity> 获取到Receiver对应的 ActivityInfo (依然是把receiver信息用activity处理了)
        List receivers = (List) RefInvoke.getFieldObject(packageObj, "receivers");

        for (Object receiver : receivers) {
            registerDynamicReceiver(context, receiver);
        }
    }

    // 解析出 receiver以及对应的 intentFilter
    // 手动注册Receiver
    public static void registerDynamicReceiver(Context context, Object receiver) {
        //取出receiver的intents字段
        List<? extends IntentFilter> filters = (List<? extends IntentFilter>) RefInvoke.getFieldObject(
                "android.content.pm.PackageParser$Component", receiver, "intents");

        try {
            // 把解析出来的每一个静态Receiver都注册为动态的
            for (IntentFilter intentFilter : filters) {
                ActivityInfo receiverInfo = (ActivityInfo) RefInvoke.getFieldObject(receiver, "info");

                BroadcastReceiver broadcastReceiver = (BroadcastReceiver) RefInvoke.createObject(receiverInfo.name);
                context.registerReceiver(broadcastReceiver, intentFilter);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

***不启动App和插件中的静态广播通信***

在宿主的androidmanifest中注册占位StubReceiver

<application
    android:name="jianqiang.com.receiverhook.UPFApplication"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <receiver
        android:name=".StubReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="jianqiang1" />
        </intent-filter>
        <intent-filter>
            <action android:name="jianqiang2" />
        </intent-filter>
        <intent-filter>
            <action android:name="jianqiang3" />
        </intent-filter>
        <intent-filter>
            <action android:name="jianqiang4" />
        </intent-filter>
        <intent-filter>
            <action android:name="jianqiang5" />
        </intent-filter>
        <intent-filter>
            <action android:name="jianqiang6" />
        </intent-filter>
        <intent-filter>
            <action android:name="jianqiang7" />
        </intent-filter>
        <intent-filter>
            <action android:name="jianqiang8" />
        </intent-filter>
        <intent-filter>
            <action android:name="jianqiang9" />
        </intent-filter>
        <intent-filter>
            <action android:name="jianqiang10" />
        </intent-filter>
    </receiver>
</application>

插件的androidmanifest中注册

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

    <receiver
        android:name=".MyReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="baobao" />
        </intent-filter>
        <meta-data android:name="oldAction" android:value="jianqiang1"></meta-data>
    </receiver>
    <receiver
        android:name=".MyReceiver2"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="baobao2" />
        </intent-filter>
        <meta-data android:name="oldAction" android:value="jianqiang2"></meta-data>
    </receiver>
</application>

具体流程是宿主清单文件中查找jianqiang1,然后到插件的清单文件中查找jianqiang1,最后找到jianqiang1对应的baobao,这才是真正要注册的广播

实现逻辑如下

public final class ReceiverHelper {

    private static final String TAG = "ReceiverHelper";

    /**
     * 解析插件Apk文件中的 <receiver>, 并存储起来
     *
     * @param apkFile
     * @throws Exception
     */
    public static void preLoadReceiver(Context context, File apkFile) {
        // 首先调用parsePackage获取到apk对象对应的Package对象
        Object packageParser = RefInvoke.createObject("android.content.pm.PackageParser");
        Class[] p1 = {File.class, int.class};
        Object[] v1 = {apkFile, PackageManager.GET_RECEIVERS};
        Object packageObj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage", p1, v1);

        String packageName = (String)RefInvoke.getFieldObject(packageObj, "packageName");

        // 读取Package对象里面的receivers字段,注意这是一个 List<Activity> (没错,底层把<receiver>当作<activity>处理)
        // 接下来要做的就是根据这个List<Activity> 获取到Receiver对应的 ActivityInfo (依然是把receiver信息用activity处理了)
        List receivers = (List) RefInvoke.getFieldObject(packageObj, "receivers");

        try {
            for (Object receiver : receivers) {
                Bundle metadata = (Bundle)RefInvoke.getFieldObject(
                        "android.content.pm.PackageParser$Component", receiver, "metaData");
                String oldAction = metadata.getString("oldAction");

                // 解析出 receiver以及对应的 intentFilter
                List<? extends IntentFilter> filters = (List<? extends IntentFilter>) RefInvoke.getFieldObject(
                        "android.content.pm.PackageParser$Component", receiver, "intents");

                // 把解析出来的每一个静态Receiver都注册为动态的
                for (IntentFilter intentFilter : filters) {
                    ActivityInfo receiverInfo = (ActivityInfo) RefInvoke.getFieldObject(receiver, "info");
                    BroadcastReceiver broadcastReceiver = (BroadcastReceiver) RefInvoke.createObject(receiverInfo.name);
                    context.registerReceiver(broadcastReceiver, intentFilter);

                    String newAction = intentFilter.getAction(0);
                    ReceiverManager.pluginReceiverMappings.put(oldAction, newAction);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class StubReceiver extends BroadcastReceiver {
    public StubReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String newAction = intent.getAction();
        if(ReceiverManager.pluginReceiverMappings.containsKey(newAction)) {
            String oldAction = ReceiverManager.pluginReceiverMappings.get(newAction);
            context.sendBroadcast(new Intent(oldAction));
        }
    }
}

缺点是要为StubReceiver配置几百个Action,无法避免

--摘自《android插件化开发指南》

本文分享自微信公众号 - 安卓圈(gh_df75572d44e4)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • MyBatis的CRUD

    MyBatis的增删改查,特点是只要定义接口,不用实现方法,sql语句在xml中配置,非常方便

    用户3112896
  • 接收数据实时更新的波状曲线图

    前面做了一个心电图的demo 心电图,结果发现那个心电图是静态的,是应用一启动就已经画好了的,整个页面向左滑动而已

    用户3112896
  • 搭建本地maven库(nexus服务器)

    第一步,下载https://www.sonatype.com/download-oss-sonatype 别下3.x版本,下2.x版本

    用户3112896
  • Android开发笔记(四十)组件通讯工具Intent

    Intent用于处理Android各组件之间的通讯。Intent完成的工作主要有三部分: 1、Intent需标明本次通讯请求是从哪里来,到哪里去,要怎么走...

    用户4464237
  • 撤回了一条消息,60%因为慌乱……

    ? ? 健身瑜伽跑步机 IT中年硬标配 健身,不求身体健康 而是为了更好的工作 手动感慨2分钟后 小编撤回了keep的3公里跑步截图 虽是动动手指撤回...

    腾讯云视频
  • Android开发笔记(一百二十三)下拉刷新布局SwipeRefreshLayout

    下拉刷新布局SwipeRefreshLayout是Android又一与时俱进的控件,顾名思义它随着用户手势向下滑动就会触发刷新操作。从实际的下拉效果来看,S...

    用户4464237
  • 3_0_4 要理解并会用的几个脚本

    随便找几个文件进行练习,只是为了说明问题,这些其实是RNA-seq数据,但无所谓,只是看脚本的处理 有以下几个文件

    Y大宽
  • keepalived实现mycat高可用问题排查;道路坎坷,布满荆棘,定让你大吃一惊!

        医院里,一母亲带着小女孩打针。小女孩:“妈妈我不想打针,疼!”妈妈:“宝贝儿听话,这里这么多护士阿姨,咱们找个打针不疼的。”小女孩:“那哪个阿姨打针不疼...

  • Hive应用:设置字段自增 原

    简单的说row_number()从1开始,为每一条分组记录返回一个数字,这里的ROW_NUMBER() OVER (ORDER BY xlh DESC) 是先把...

    云飞扬
  • [android] 安卓自定义样式和主题

    简单练习自定义样式和主题,样式是加在View上,主题是加在Application或者Activity上

    陶士涵

扫码关注云+社区

领取腾讯云代金券