ContentProvider和service一样,也是通过代理分发机制进行Hook,并且比service更简单,没有周期的处理,只有几个简单的方法,
例如, query/insert/delete/update等,
在AndroidManifest中注册了一个多进程的ContentProvider,
到此为止,还没有出现ContentProvider的Hook类,
当在插件中调用getContentResolver方法时就会调用PluginContext的getContentResolver方法,
public ContentResolver getContentResolver() {
return new PluginContentResolver(getHostContext());
}
相当于直接利用PluginContentResolver替换掉系统的ContentResolver,并且PluginContentResolver也是继承于ContentResolver,
当调用query/insert/delete/update等方法时,会调用ContentResolver的acquireProvider/acquireExistingProvider等方法,
PluginContentResolver的acquireProvider方法如下,
if (mPluginManager.resolveContentProvider(auth, 0) != null) {
return mPluginManager.getIContentProvider();
}
return (IContentProvider) sAcquireProvider.invoke(mBase, context, auth);
首先调用PluginManager的resolveContentProvider方法进行匹配,如果匹配上了,就需要Hook,否则说明不是插件的ContentProvider,不需要Hook。
PluginManager的getIContentProvider方法如下,
public synchronized IContentProvider getIContentProvider() {
if (mIContentProvider == null) {
hookIContentProviderAsNeeded();
}
return mIContentProvider;
}
如果IcontentProviderProxy对象已经创建了就直接返回,否则就创建IcontentProviderProxy对象,
hookIContentProviderAsNeeded的主要逻辑如下,
1,获取已注册的ContentProvider的uri,然后主动调用了其call方法,
Uri uri = Uri.parse(PluginContentResolver.getUri(mContext));
mContext.getContentResolver().call(uri, "wakeup", null, null);
PluginContentResolver的getUri方法如下,
public static String getUri(Context context) {
return "content://" + getAuthority(context);
}
getAuthority方法如下,
public static String getAuthority(Context context) {
return context.getPackageName() + ".VirtualAPK.Provider";
}
就是在AndroidManifest中注册的RemoteContentProvider的uri。
调用call方法后最后会调用installProvider方法安装RemoteContentProvider。
2,将RemoteContentProvider对应的IcontentProvider对象利用反射替换为IcontentProviderProxy对象。
Field authority = null;
Field mProvider = null;
ActivityThread activityThread = (ActivityThread) ReflectUtil.getActivityThread(mContext);
Map mProviderMap = (Map) ReflectUtil.getField(activityThread.getClass(), activityThread, "mProviderMap");
Iterator iter = mProviderMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
String auth;
if (key instanceof String) {
auth = (String) key;
} else {
if (authority == null) {
authority = key.getClass().getDeclaredField("authority");
authority.setAccessible(true);
}
auth = (String) authority.get(key);
}
if (auth.equals(PluginContentResolver.getAuthority(mContext))) {
if (mProvider == null) {
mProvider = val.getClass().getDeclaredField("mProvider");
mProvider.setAccessible(true);
}
IContentProvider rawProvider = (IContentProvider) mProvider.get(val);
IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
mIContentProvider = proxy;
•••
代码虽然有好几行,但是逻辑很简单, 利用循环拿到RemoteContentProvider对应的IContentProvider对象,
然后利用IContentProviderProxy Hook IcontentProvider。
IContentProviderProxy的newInstance方法如下,
public static IContentProvider newInstance(Context context, IContentProvider iContentProvider) {
return (IContentProvider) Proxy.newProxyInstance(iContentProvider.getClass().getClassLoader(),new Class[] { IContentProvider.class }, new IContentProviderProxy(context, iContentProvider));
}
在获取插件的ContentProvider过程中进行匹配和Hook。
调用PluginManager的resolveContentProvider方法进行匹配,这个过程比Activity/service过程要简单, resolveContentProvider方法如下,
public ProviderInfo resolveContentProvider(String name, int flags) {
for (LoadedPlugin plugin : this.mPlugins.values()) {
ProviderInfo providerInfo = plugin.resolveContentProvider(name, flags);
if (null != providerInfo) {
return providerInfo;
}
}
return null;
}
逐个在所有插件中查询,一旦匹配就立即返回。
LoadedPlugin的resolveContentProvider方法如下,
public ProviderInfo resolveContentProvider(String name, int flags) {
return this.mProviders.get(name);
}
直接从结构体中获取。解析插件的时候放入的。
IContentProviderProxy的invoke方法如下,
wrapperUri(method, args);
try {
return method.invoke(mBase, args);
}
•••
调用wrapperUri方法使用RemoteContentProvider替换目标的ContentProvider, wrapperUri方法主要逻辑如下,
PluginManager pluginManager = PluginManager.getInstance(mContext);
ProviderInfo info = pluginManager.resolveContentProvider(uri.getAuthority(), 0);
if (info != null) {
String pkg = info.packageName;
LoadedPlugin plugin = pluginManager.getLoadedPlugin(pkg);
String pluginUri = Uri.encode(uri.toString());
StringBuilder builder = new StringBuilder(PluginContentResolver.getUri(mContext));
builder.append("/?plugin=" + plugin.getLocation());
builder.append("&pkg=" + pkg);
builder.append("&uri=" + pluginUri);
Uri wrapperUri = Uri.parse(builder.toString());
if (method.getName().equals("call")) {
bundleInCallMethod.putString(KEY_WRAPPER_URI, wrapperUri.toString());
} else {
args[index] = wrapperUri;
}
}
StringBuilder首先加入占坑provider的uri,然后将目标uri,pkg,plugin等参数等拼接上去,替换到args中的uri,
然后继续走原来的流程。
这里就相当于Hook 了ContentProvider的query/insert/delete/update等方法。
调用插件的ContentProvider其实实际上会调用RemoteContentProvider的query/insert/delete/update等方法。
这些方法的调用流程完全相同,以query方法论述,
query方法如下,
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
ContentProvider provider = getContentProvider(uri);
Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));
if (provider != null) {
return provider.query(pluginUri, projection, selection, selectionArgs, sortOrder);
}
return null;
}
首先调用getContentProvider方法获取插件的ContentProvider对象,然后调用query方法。这样就达到了代理的目的。
getContentProvider方法主要逻辑如下,
1,获取插件的Uri,
final PluginManager pluginManager = PluginManager.getInstance(getContext());
Uri pluginUri = Uri.parse(uri.getQueryParameter(KEY_URI));
final String auth = pluginUri.getAuthority();
ContentProvider cachedProvider = sCachedProviders.get(auth);
if (cachedProvider != null) {
return cachedProvider;
}
这里分为2中情况,插件ContentProvider已启动, 未启动。
如果已经启动,就直接可以在sCachedProviders中获取并返回。
2,如果未启动就取出插件ContentProvider的uri,拿到auth。首先加载插件apk,然后匹配ContentProvider,
最后在主线程中反射生成provider对象,在调用其attachInfo方法.
synchronized (sCachedProviders) {
LoadedPlugin plugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
if (plugin == null) {
try {
pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN)));
} catch (Exception e) {
e.printStackTrace();
}
}
final ProviderInfo providerInfo = pluginManager.resolveContentProvider(auth, 0);
if (providerInfo != null) {
RunUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
LoadedPlugin loadedPlugin = pluginManager.getLoadedPlugin(uri.getQueryParameter(KEY_PKG));
ContentProvider contentProvider = (ContentProvider) Class.forName(providerInfo.name).newInstance();
contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo);
sCachedProviders.put(auth, contentProvider);
} catch (Exception e) {
e.printStackTrace();
}
}
}, true);
return sCachedProviders.get(auth);
}
}
经过了这4个步骤,就可以通过代理调用插件的ContentProvider了。ContentProvider并没有生命周期的控制。