plugin调用ContentProvider是通过plugin-lib的PluginProviderClient类进行的,以PluginProviderClient.query为例,看一下Replugin ContentProvider的实现机制。
先总结一下调用栈
PluginProviderClient.query -- plugin-lib
PluginProviderClient.query -- host-lib(通过反射调用到)
PluginProviderClient.toCalledUri -- 关键代码
PluginPitProviderBase.query
PluginProviderHelper.getProvider().query -- 真正的provider的query
关键代码就在com.qihoo360.replugin.component.provider.PluginProviderClient中,它的几个方法如下
public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Uri turi = toCalledUri(c, uri);
return c.getContentResolver().query(turi, projection, selection, selectionArgs, sortOrder);
}
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);
}
/**
* 将从插件里的URI转化成系统传过来的URI。可自由指定在哪个进程启动。例如:
* Before: content://com.qihoo360.contacts.abc/people (Contacts插件,UI)
* After: content://com.qihoo360.mobilesafe.PluginUIP/contacts/com.qihoo360.mobilesafe.contacts.abc/people
*
* @param context 当前的Context对象,目前暂无用
* @param plugin 要使用的插件
* @param uri URI对象
* @param process 进程信息,若为PROCESS_AUTO,则根据插件Manifest来指定进程
* @return 转换后可直接在ContentResolver使用的URI
*/
public static Uri toCalledUri(Context context, String plugin, Uri uri, int process) {
if (TextUtils.isEmpty(plugin)) {
throw new IllegalArgumentException();
}
if (uri == null) {
throw new IllegalArgumentException();
}
if (uri.getAuthority().startsWith(PluginPitProviderBase.AUTHORITY_PREFIX)) {
// 自己已填好了要使用的插件名(以PluginUIProvider及其它为开头),这里不做处理
return uri;
}
// content://com.qihoo360.mobilesafe.PluginUIP
if (process == IPluginManager.PROCESS_AUTO) {
// 直接从插件的Manifest中获取
process = getProcessByAuthority(plugin, uri.getAuthority());
if (process == PROCESS_UNKNOWN) {
// 可能不是插件里的,而是主程序的,直接返回Uri即可
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;
}
// from => content:// com.qihoo360.contacts.abc/people?id=9
// to => 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);
}
大体说就两步:1、转换uri;2、调用系统方法去query该uri。
- uri的转换。首先,根据plugin的context查询到plugin name,再根据plugin info的ComponentList查询到目标provicer配置的进程,最后根据这两个信息把原uri转换成host可以识别的uri
- 此时,调用系统方法query转换后的uri,就会在对应的进程中调用的host预置的对应的ContentProvider。然后就调用到了基类的query,也就是PluginPitProviderBase.query
在PluginPitProviderBase.query中,会调用PluginProviderHelper.getProvider去load对应plugin里的ContentProvider然后直接调用
Bug(2.1.3版本):
- plugin-lib里的PluginProviderClient.update方法,传递参数漏掉了
- host-lib里的PluginProviderClient,在fetchPluginByContext的时候,fetch到的是调用者plugin,相当于plugin_A调用plugin_B,查到的还是plugin_A...这样就调不到plugin_B了。有一个规避方法,uri写成这样content://com.qihoo360.replugin.sample.host.Plugin.NP.UIP/demo1/com.qihoo360.replugin.sample.demo1.provider2,即显示指定坑位和目标plugin
- 从FAQ看好像还有其他bug,ContentProvider这块做的还是不完善...