本篇我们来看看四大组件中的BroadcaseReceiver
和ContentProvider
。总体来说,这两个组件的生命周期相对简单,所以要在Replugin框架中处理插件的BroadcaseReceiver
和ContentProvider
更简单容易一些,框架中的代码逻辑也很好理解。
BroadcaseReceiver
广播我们分两步来讲解:
- 注册广播
- 接收广播
Replugin中广播的整体逻辑非常清楚简单,基本上可以用下面这张图来概括。
注册广播
如果你看过Replugin 全面解析 (3) 你应该会记得在插件加载的过程中,有一个步骤就是解析Plugin中的BroadcastReceiver并注册,我们就从这里开始讲起。当然,在Replugin中通过代码注册广播跟原生并没有什么两样,不同的只是插件中在Manifest文件中静态注册的广播的注册方式有所不同。所以这里我们所讲的就是插件中静态广播的注册流程啦!
Loader.regReceivers
先得到插件的名字,调用IPluginHost
接口的regReceiver
函数,将加载插件Dex时解析出来的广播信息通过远程调用注册到Persistent
进程中。
private void regReceivers() throws android.os.RemoteException {
String plugin = mPluginObj.mInfo.getName();
if (mPluginHost == null) {
mPluginHost = getPluginHost();
}
if (mPluginHost != null) { // 第一个参数是插件名,第二个参数是广播信息
mPluginHost.regReceiver(plugin, ManifestParser.INS.getReceiverFilterMap(plugin));
}
}
远程调用实际是调用Persistent
进程中的PmHostSvc.regReceiver
函数,这个函数会完成以下任务:
- 创建
PluginReceiverProxy
对象,给它添加一个保存Action的Map
对象mActionPluginComponents
- 遍历插件中的广播,并将广播以及广播的
IntentFilter
信息都保存到mActionPluginComponents
中 -
PluginReceiverProxy
实际上就是一个广播,所以将它注册到Android系统中,并将插件中所有广播的所有IntentFilter
都添加到这个广播中。因此当系统发送广播时,所有匹配这些IntentFilter
的广播都会首先被PluginReceiverProxy
接收到。
public void regReceiver(String plugin, Map rcvFilMap) throws RemoteException {
......
HashMap<String, List<IntentFilter>> receiverFilterMap = (HashMap<String, List<IntentFilter>>) rcvFilMap;
// 遍历此插件中所有静态声明的 Receiver
for (HashMap.Entry<String, List<IntentFilter>> entry : receiverFilterMap.entrySet()) {
if (mReceiverProxy == null) {
mReceiverProxy = new PluginReceiverProxy();
mReceiverProxy.setActionPluginMap(mActionPluginComponents);
}
String receiver = entry.getKey();
List<IntentFilter> filters = entry.getValue();
if (filters != null) {
for (IntentFilter filter : filters) {
int actionCount = filter.countActions();
while (actionCount >= 1) {
saveAction(filter.getAction(actionCount - 1), plugin, receiver);
actionCount--;
}
mContext.registerReceiver(mReceiverProxy, filter);//注册PluginReceiverProxy
}
}
}
}
注册广播的过程就这是这么简单!
接收广播
接收广播正如上面所提到的,首先是PluginReceiverProxy.onReceive
来处理。
这里会选择使用IPluginHost
或者IPluginClient
来进一步处理广播。在Replugin 全面解析 (4) 中有讲过这两者的区别。所以如果广播并不是Persistent
进程中的,就会使用IPluginClient.onReceive
来处理。
public void onReceive(Context context, Intent intent) {
......
String action = intent.getAction();
if (!TextUtils.isEmpty(action)) {
......
List<String> receivers = new ArrayList<>(entry.getValue());
for (String receiver : receivers) {
try {
......
if (process == IPluginManager.PROCESS_PERSIST) {
IPluginHost host = PluginProcessMain.getPluginHost();
host.onReceive(plugin, receiver, intent); // Persistent进程
} else {
IPluginClient client = MP.startPluginProcess(plugin, process, new PluginBinderInfo(PluginBinderInfo.NONE_REQUEST));
client.onReceive(plugin, receiver, intent); // 非Persistent进程
}
} catch (Throwable e) {
}
}
}
}
上一步通过远程调用会调用到PluginProcessPer.onReceive
,这一步实际上就是调用PluginReceiverHelper.onReceive
函数。
- 查找出插件的
Context
对象,也就是PluginContext
对象 - 通过
PluginDexClassLoader
加载BroadcastReceiver
类并创建实例对象 - 在UI线程调用
BroadcastReceiver
对象的onReceive
函数,这样就完成了广播的处理
public static void onPluginReceiverReceived(final String plugin, final String receiverName, final HashMap<String, BroadcastReceiver> receivers, final Intent intent) {
......
// 使用插件的 Context 对象
final Context pContext = Factory.queryPluginContext(plugin);
......
String key = String.format("%s-%s", plugin, receiverName);
BroadcastReceiver receiver = null;
if (receivers == null || !receivers.containsKey(key)) {
try {
// 使用插件的 ClassLoader 加载 BroadcastReceiver
Class c = loadClassSafety(pContext.getClassLoader(), receiverName);
if (c != null) {
receiver = (BroadcastReceiver) c.newInstance();
if (receivers != null) {
receivers.put(key, receiver);
}
}
} catch (Throwable e) {
}
} else {
receiver = receivers.get(key);
}
if (receiver != null) {
final BroadcastReceiver finalReceiver = receiver;
// 转到 ui 线程
Tasks.post2UI(new Runnable() {
@Override
public void run() {
finalReceiver.onReceive(pContext, intent);
}
});
}
}
唯一要在最后说明的一点是,由于广播的特殊性,不需要任何额外的设计,就可以完全支持原生广播的所有特性。
- Host可以向Plugin发送广播,Plugin也可以向Host或者其他Pluglin发送广播。
- 外部应用可以向Host或者Plugin发送广播,Plugin也可以向外部应用发送广播。
- 插件内动态注册广播跟原生应用做法也是一样的,并且支持所有原生广播特性。
广播相关的内容就讲解这么多,是不是很简单明了?
ContentProvider
ContentProvider
的设计理念跟BroadcastReceiver
非常相似,对插件中ContentProvider
的访问是通过坑位ContentProvider
来代理的,然后坑位ContentProvider
会根据uri
去访问插件中的目标ContentProvider
以完成数据的CRUD(create, remove, query, delete)操作。先通过一张图来做一个大概了解:
整个过程分为以下几步:
- 插件中通过反射调用
PluginProviderClient
的CRUD操作函数,以uri
作为参数 - 将目标
uri
组装成一个newUri
,newUri
指向目标provider
所在进程的一个坑位provider
- 坑位
privider
接收到操作请求以后从newUri中解析出
目标uri
- 通过
ClassLoader
加载并创建目标provider
的实例,并显示调用对应的CRUD操作
在Replugin 全面解析(3) 中,我们在Plugin的环境初始化过程中提到过PluginProviderClient
,在replugin-host-lib
中也有一个同名的类。在Plugin中需要访问provider
的时候,就会调用replugin-pugin-lib
中的PluginProviderClient
类,它会通过反射调用replugin-host-lib
中的PluginProviderClient
的方法,我们以insert
操作为例。
replugin-pugin-lib
中的PluginProviderClient.insert
,如果RePluginFramework
没有初始化,说明当前的插件是被当作普通应用在使用,所以直接使用原生的provider
操作。
public static Uri insert(Context c, Uri uri, ContentValues values) {
if (!RePluginFramework.mHostInitialized) {
return c.getContentResolver().insert(uri, values); // 原生操作
}
try { // 反射调用replugin-host-lib中的PluginProviderClient.insert
return (Uri) ProxyRePluginProviderClientVar.insert.call(null, c, uri, values);
} catch (Exception e) {
}
return null;
}
replugin-host-lib
中的PluginProviderClient.insert
,这里最重要的一步就是toCalledUri
。
public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
Uri turi = toCalledUri(c, uri);
return c.getContentResolver().query(turi, projection, selection, selectionArgs, sortOrder, cancellationSignal);
}
PluginProviderClient.toCalledUri
负责将上面的uri参数组装成一个新的newUri,这里要注意:
- 如果通过
Context
和Uri
找不到插件,说明并不是要访问插件中的provider
,所以就不需要组装newUri,直接通过系统逻辑访问即可。 - 如果发现要访问的
provider
是插件中,则去组装newUri
public static Uri toCalledUri(Context c, Uri uri) {
String pn = fetchPluginByContext(c, uri);
if (pn == null) {
return uri;
}
return toCalledUri(c, pn, uri, IPluginManager.PROCESS_AUTO);
}
组装newUri,目的就是将原来的uri于坑位provider
的uri组合在一起,成为一个新的uri,但实际上这个uri是用来访问坑位privider
的。这里要完成几件事情:
- 通过Uri中插件名和
authority
找到想要访问的provider
所在的进程 - 找到对应进程中的坑位
provider
的authority
- 将坑位
provider
的authority
与插件provider
的authority
组合在一起形成newUri,组合规则请看下面代码中的注释部分
public static Uri toCalledUri(Context context, String plugin, Uri uri, int process) {
......
// content://com.qihoo360.mobilesafe.PluginUIP
if (process == IPluginManager.PROCESS_AUTO) {
process = getProcessByAuthority(plugin, uri.getAuthority());
if (process == PROCESS_UNKNOWN) {
return uri;
}
}
String au;
if (process == IPluginManager.PROCESS_PERSIST) {
au = PluginPitProviderPersist.AUTHORITY;
} else if (PluginProcessHost.isCustomPluginProcess(process)) {
au = PluginProcessHost.PROCESS_AUTHORITY_MAP.get(process);
} else {
au = PluginPitProviderUI.AUTHORITY;
}
// 插件名plugin_name
// 插件provider的uri => content://com.qihoo360.contacts.abc/people?id=9
// 坑位provider看到uri => content://com.qihoo360.mobilesafe.Plugin.NP.UIP
// 组合后的newUri => content://com.qihoo360.mobilesafe.Plugin.NP.UIP/plugin_name/com.qihoo360.contacts.abc/people?id=9
String newUri = String.format("content://%s/%s/%s", au, plugin, uri.toString().replace("content://", ""));
return Uri.parse(newUri);
}
在UI进程,persistent进程以及Replugin默认提供的三个自定义进程中都各自有一个坑位
provider
,他们的authority
是各不相同的,但他们都是PluginPitProviderBase
的子类。这几个provider
是:
- PluginPitProviderUI
- PluginPitProviderPersist
- PluginPitProviderP0
- PluginPitProviderP1
- PluginPitProviderP2
通过newUri就可以访问坑位provider
了,insert
操作自然是provider
的insert
函数来处理,而他们都是PluginPitProviderBase
的子类,而且没有提供自己的insert
函数,所以自然就会使用PluginPitProviderBase
提供的insert函数
。
- 第一步从参数uri中解析出目标
provider
的uri,就是从上面的newUri中将插件名后面的部分取出来 -
PluginProviderHelper.getProvider
加载目标provider
类名并通过反射创建一个实例 - 使用这个目标
provider
实例真正执行insert
操作,实现最终的数据插入操作
这三个小步骤的代码都很容易理解,有兴趣可以看看源码哦~
public Uri insert(Uri uri, ContentValues values) {
PluginProviderHelper.PluginUri pu = mHelper.toPluginUri(uri);
if (pu == null) {
return null;
}
ContentProvider cp = mHelper.getProvider(pu);
if (cp == null) {
return null;
}
return cp.insert(pu.transferredUri, values);
}
好了,Replugin中ContentProvider
的原理就是这样,也很好理解!
注意:
- 外部应用不能访问插件中的
provider
- 不过插件中是可以访问外部应用的
provider
的- Host要访问插件中的provider需要使用
PluginProviderClient
提供的方法- 当然,插件之间是可以相互访问对方的
provider
的
总结
通过这四篇分析,Replugin的大部分核心原理已经都分析过了,当然框架中有很多细节或者小的设计并没有能够全部覆盖到,在需要的时候可以看看代码。在Replugin中还有两个gradle插件,已经有人写过两篇比较详细的分析:
Replugin整体的设计很巧妙,支持的原生特性也很多,当然在目前的阶段坑定也还有一些不能支持的特性,希望在以后的版本中能够逐步得到支持!目前只是在360相关的少数应用上使用,虽然崩溃率很低(万分之三),但还需要接受大面积使用,不同应用场景的应用的检验,在这个过程中发现问题逐步完善!