BroadcastReceiver插件化解决方案
--摘自《android插件化开发指南》
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,无法避免
欢迎关注我的微信公众号:安卓圈
BroadcastReceiver插件化解决方案的更多相关文章
- Service插件化解决方案
--摘自<android插件化开发指南> 1.ActivityThread最终是通过Instrumentation启动一个Activity的.而ActivityThread启动Servic ...
- ContentProvider插件化解决方案
--摘自<android插件化开发指南> 1.当要传输的数据量大小不超过1M的时候,使用Binder:数据量超过1M时,Binder就搞不定了,需要ContentProvider 2.Co ...
- Activity插件化解决方案
--摘自<android插件化开发指南> 1.宿主App加载插件中的类 2.最简单的插件化方案就是在宿主的androidmanifest.xml中申明插件中的四大组件 把插件dex合并到宿 ...
- 《Android插件化开发指南》面世
本书在京东购买地址:https://item.jd.com/31178047689.html 本书Q群:389329264 (一)这是一本什么书 如果只把本书当作纯粹介绍Android插件化技术的书籍 ...
- Android插件化的兼容性(中):Android P的适配
Android系统的每次版本升级,都会对原有代码进行重构,这就为插件化带来了麻烦. Android P对插件化的影响,主要体现在两方面,一是它重构了H类中Activity相关的逻辑,另一个是它重构了I ...
- 包建强的培训课程(10):Android插件化从入门到精通
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...
- Android 插件化开发(四):插件化实现方案
在经过上面铺垫后,我们可以尝试整体实现一下插件化了.这里我们先介绍一下最简单的实现插件化的方案. 一.最简单的插件化实现方案 最简单的插件化实现方案,对四大组件都是适用的,技术面涉及如下: 1). 合 ...
- Android插件化的兼容性(上):Android O的适配
首先声明,<Android插件化开发指南>这本书所介绍的Android底层是基于Android6.0(API level 23)的,而本书介绍的各种插件化解决方案,以及配套的70多个例子, ...
- android 插件化框架VitualAPK
推荐阅读: 滴滴Booster移动App质量优化框架-学习之旅 一 Android 模块Api化演练 不一样视角的Glide剖析(一) LeakCanary 与 鹅场Matrix ResourceCa ...
随机推荐
- noj算法 装载问题 回溯法
描述: 有两艘船,载重量分别是c1. c2,n个集装箱,重量是wi (i=1…n),且所有集装箱的总重量不超过c1+c2.确定是否有可能将所有集装箱全部装入两艘船. 输入: 多个测例,每个测例的输入占 ...
- 做了5年的Android,我转Java后台了!
很多人做Java开发4,5年后,都会感觉自己遇到瓶颈.什么都会又什么都不会,如何改变困境,为什么很多人写了7,8年还是一个码农,工作中太多被动是因为不懂底层原理.公司的工作节奏又比较快,难有机会学习架 ...
- Oracle 之 外部表
一.外部表概述 外部表只能在Oracle 9i 之后来使用.简单地说,外部表,是指不存在于数据库中的表. 通过向Oracle 提供描述外部表的元数据,我们可以把一个操作系统文件当成一个只读的数 据库表 ...
- Confluence 6 数据库表-杂项(Miscellaneous)
这些部分是一些其他的表格,这些表格有必要在这里提及下能帮你更好的了解系统. os_propertyentry 有关实体和属性相关的特性. bandana 所有的持久层.这个表格包含的的内容有用户设置和 ...
- Confluence 6 SQL Server 创建一个数据库和数据库用户
一旦你成功安装了 SQL Server 服务器,请按照下面的方法为你的 Confluence 创建数据库用户和数据库: 使用你的 SQL 管理员权限,创建一个新的数据库(例如 confluence). ...
- Java的家庭记账本程序(B)
日期:2019.2.3 博客期:029 星期日 看看今天想先完成jsp的连接操作,所以首先意识到自己的程序中,管理员可以对成员进行冻结.解封操作,所以先回去补了一下数据库的内容!成员的内容里多了一项i ...
- angular基础巩固
angular中的模块化 //定义模块 []为依赖的模块 moduleName可以使用[]模块中定义的controller filter .. var app=angular.module('modu ...
- NMT 机器翻译
本文近期学习NMT相关知识,学习大佬资料,汇总便于后期复习用,有问题,欢迎斧正. 目录 RNN Seq2Seq Attention Seq2Seq + Attention Transformer Tr ...
- 常用ajax样例
---恢复内容开始--- // url(String):请求地址 // param(String):请求参数 // targetId(String):结果显示id function ajaxReq(u ...
- MySQL5.7.20报错Access denied for user 'root'@'localhost' (using password: NO)
在centos6.8上源码安装了MySQL5.7.20,进入mysql的时候报错如下: 解决办法如下: 在mysql的配置文件内加入: vim /etc/my.cnf skip-grant-tabl ...