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,无法避免
欢迎关注我的微信公众号:安卓圈
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
2017-12-12 android工具