5 ContentProvider Hook
ContentProvider的Hook和 service类似,都是通过代理分发技术来实现,必须要实现增删改查四个方法。
还有一点不同的是,可以监听数据库的变化。同样的,也有注册,换马甲以及脱马甲过程。
5.1 ContentProvider注册
同样的, provider个数和service一样的少,因为使用没有Activity那么频繁。
<provider
android:name=".stub.ContentProviderStub$StubP00"
android:authorities="com.morgoo.droidplugin_stub_P00"
android:exported="false"
android:label="@string/stub_name_povider" />
这些provider都是ContentProviderStub的内部类,并且都继承于ContentProviderStub, ContentProviderStub继承于
AbstractContentProviderStub类, AbstractContentProviderStub类继承于ContentProvider,并且实现了query/insert/delete/update等方法。
5.2 替换
在AMS章节论述过, 调用AMS的getContentProvider方法其实是调用getContentProvider的beforeInvoke方法。
IactivityManagerHookHandle的内部类getContentProvider的beforeInvoke方法逻辑如下,
1,获取真实的Provider,
ProviderInfo info = mHostContext.getPackageManager().resolveContentProvider(name, 0);
mTargetProvider = PluginManager.getInstance().resolveContentProvider(name, 0);
2,获取一个宿主中已注册的Provider,
mStubProvider = PluginManager.getInstance().selectStubProviderInfo(name);
3,替换,
args[index] = mStubProvider.authority;
并且在afterInvoke方法中,直接Hook 了ContentProvider,
IContentProviderHook invocationHandler = new IContentProviderHook(mHostContext, provider, mStubProvider, mTargetProvider, localProvider);
invocationHandler.setEnable(true);
Class<?> clazz = provider.getClass();
List<Class<?>> interfaces = Utils.getAllInterfaces(clazz);
Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
Object proxyprovider = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, invocationHandler);
FieldUtils.writeField(invokeResult, "provider", proxyprovider);
IcontentProviderHook相关的结构图如下,
IcontentProviderInvokeHandle的init方法如下,
sHookedMethodHandlers.put("query", new query(mHostContext));
sHookedMethodHandlers.put("getType", new getType(mHostContext));
sHookedMethodHandlers.put("insert", new insert(mHostContext));
sHookedMethodHandlers.put("bulkInsert", new bulkInsert(mHostContext));
sHookedMethodHandlers.put("delete", new delete(mHostContext));
sHookedMethodHandlers.put("update", new update(mHostContext));
sHookedMethodHandlers.put("openFile", new openFile(mHostContext));
sHookedMethodHandlers.put("openAssetFile", new openAssetFile(mHostContext));
sHookedMethodHandlers.put("applyBatch", new applyBatch(mHostContext));
sHookedMethodHandlers.put("call", new call(mHostContext));
•••
除了Hook 增删改查之外,还Hook 了一些其他的方法。
这些类都继承于MyHandler方法, MyHandler的beforeInvoke方法如下,
final int index = indexFirstUri(args);
if (index >= 0) {
Uri uri = (Uri) args[index];
String authority = uri.getAuthority();
if (!TextUtils.equals(authority, mStubProvider.authority)) {
Uri.Builder b = new Builder();
b.scheme(uri.getScheme());
b.authority(mStubProvider.authority);
b.path(uri.getPath());
b.query(uri.getQuery());
b.appendQueryParameter(Env.EXTRA_TARGET_AUTHORITY, authority);
b.fragment(uri.getFragment());
args[index] = b.build();
}
}
把Uri的authority替换成stub provider的authority, 这样增删改查等一系列方法都是访问的是stub provider指向的路径了。
真实的authority放在EXTRA_TARGET_AUTHORITY变量中。
5.3 脱马甲过程
AbstractContentProviderStub的query/insert/delete/update等方法都会脱掉马甲, query方法如下,
String targetAuthority = uri.getQueryParameter(Env.EXTRA_TARGET_AUTHORITY);
if (!TextUtils.isEmpty(targetAuthority) && !TextUtils.equals(targetAuthority, uri.getAuthority())) {
ContentProviderClient client = getContentProviderClient(targetAuthority);
try {
return client.query(buildNewUri(uri, targetAuthority), projection, selection, selectionArgs, sortOrder);
} catch (RemoteException e) {
handleExpcetion(e);
}
}
首先获取真实的Authority,然后根据该值获取ContentProviderClient对象,最后调用对应的query方法。
5.4通知
ContentProvider相对于其他三大组件来说,还有个不同的地方。ContentProvider数据库的变化可以通知注册了
该数据的组件。通过contentService Hook来实现该目的。IcontentServiceBinderHook和其他服务的AP实现的
过程完全一样。IContentServiceBinderHook的结构图如下,
Hook 类 | IContentServiceBinderHook |
Hook 代理类 | IContentServiceHandle |
Hook 方法实现类 | registerContentObserver |
notifyChange |
IContentServiceHandle的内部类IContentServiceHookedMethodHandler的beforeInvoke方法如下,
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
if (args != null) {
final int index = 1;
if (args.length > index && args[index] instanceof Uri) {
Uri uri = (Uri) args[index];
String authority = uri.getAuthority();
ProviderInfo provider = PluginManager.getInstance().resolveContentProvider(authority, 0);
if (provider != null) {
ProviderInfo info = PluginManager.getInstance().selectStubProviderInfo(authority);
Uri.Builder newUri = new Uri.Builder();
newUri.scheme("content");
newUri.authority(uri.getAuthority());
newUri.path(uri.getPath());
newUri.query(uri.getQuery());
newUri.appendQueryParameter(Env.EXTRA_TARGET_AUTHORITY,authority);
args[index] = newUri.build();
// return true;
} else {
Log.w(TAG, "getContentProvider,fake fail 2=%s", authority);
}
}
}
return super.beforeInvoke(receiver, method, args);
}
就是将插件的Uri替换成宿主的Uri,这样就可以完成注册和通知了,因为系统只认宿主ContentProvider的相关信息。