一、Android Activity启动流程与Hook技术实现插件化
概述
在Android系统中,通过结合拦截修改startActivity
方法和Hook Handler的LAUNCH_ACTIVITY
消息,可以实现插件Activity的动态加载和执行,同时绕过AMS(Activity Manager Service)对插件Activity的直接检测。腾讯的QZone和Tinker等应用本质上就是采用了这种hook方式来实现插件化,主要操作的是dexElements。
详细流程
-
应用层发起启动请求
- 用户或代码在应用层通过
startActivity
方法发起启动插件Activity的请求。
- 用户或代码在应用层通过
-
拦截修改
startActivity
方法(Hook节点1)- 在
startActivity
方法执行后,但在Intent被传递给AMS之前,利用反射和动态代理等技术修改Intent的组件信息,将其指向宿主应用中的一个代理Activity。 - 这一步的目的是为了绕过AMS对插件Activity的直接检测,因为AMS通常不允许直接启动未在Manifest中声明的Activity。
- 在
-
Intent传递给AMS
- 修改后的Intent(指向代理Activity)被传递给AMS,请求启动该代理Activity。
-
AMS处理启动请求
- AMS接收到启动请求后,解析Intent,检查权限等,并准备启动代理Activity。此时,AMS并不知晓最终将启动的是插件Activity。
-
准备启动Activity
- AMS为代理Activity准备必要的资源,如创建Task和ActivityRecord等。
-
通知目标应用进程
- AMS通过IPC机制通知宿主应用进程的
ApplicationThread
,准备启动代理Activity。
- AMS通过IPC机制通知宿主应用进程的
-
Hook Handler的
LAUNCH_ACTIVITY
消息(Hook节点2)- 在宿主应用进程的
ActivityThread
中,当Handler
接收到来自AMS的LAUNCH_ACTIVITY
消息时,准备处理该消息以启动Activity。 - 在消息被处理之前,通过反射和Hook技术拦截这个消息,并修改其中的Intent对象,将其组件信息更改为插件Activity。
- 在宿主应用进程的
-
处理
LAUNCH_ACTIVITY
消息- 使用修改后的Intent(指向插件Activity)启动Activity。
ActivityThread
调用插件Activity的onCreate
、onStart
、onResume
等生命周期方法,完成插件Activity的启动和显示。
-
自定义DexClassLoader加载相关类
- 使用自定义的DexClassLoader来加载和执行插件中的类文件。
注意事项
- 安全性和稳定性:由于此方法涉及对系统底层机制的修改,需特别注意安全性和稳定性问题。不当的Hook操作可能导致系统崩溃或应用行为异常。
- 兼容性问题:Android系统不断更新,不同版本在系统实现上可能存在差异。因此,实现Hook时需考虑兼容性,确保在不同Android版本上均能正常工作。
- 权限问题:修改系统级组件或拦截系统消息通常需要较高权限,这些权限可能无法通过普通应用获取。因此,此方法可能需要在系统层面或具有root权限的环境下实现。
结论
通过上述方法,Android系统可实现插件化技术,动态加载和执行插件Activity。然而,此方法具有复杂性和风险性,需谨慎使用,并确保充分测试以避免潜在问题。
二、具体源码实现和解析
1. Hook AMS,绕过AMS对插件Activity的检测
private void hookAmsAction() throws Exception {
// 获取必要的类
Class<?> mIActivityManagerClass = Class.forName("android.app.IActivityManager");
Class<?> mActivityManagerNativeClass2 = Class.forName("android.app.ActivityManagerNative");
// 获取IActivityManager实例
Object mIActivityManager = mActivityManagerNativeClass2.getMethod("getDefault").invoke(null);
// 创建IActivityManager的动态代理
Object mIActivityManagerProxy = Proxy.newProxyInstance(
HookApplication.class.getClassLoader(),
new Class[]{mIActivityManagerClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
// 修改Intent以启动ProxyActivity
Intent intent = new Intent(HookApplication.this, ProxyActivity.class);
intent.putExtra("actionIntent", (Intent) args[2]);
args[2] = intent;
}
Log.d("hook", "拦截到了IActivityManager的方法: " + method.getName());
// 调用原始方法
return method.invoke(mIActivityManager, args);
}
}
);
// 替换ActivityManagerNative中的gDefault为代理对象
Class<?> mActivityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = mActivityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
gDefaultField.set(null, mIActivityManagerProxy);
// 替换Singleton中的mInstance(如果需要)
// 注意:这部分通常不是必需的,除非有特定实现要求
// ...
}
2. Hook LAUNCH_ACTIVITY事件,启动插件Activity
private void hookLaunchActivity() throws Exception {
// 替换Handler的mCallback
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
// 获取ActivityThread的Handler
Class<?> mActivityThreadClass = Class.forName("android.app.ActivityThread");
Object mActivityThread = mActivityThreadClass.getMethod("currentActivityThread").invoke(null);
Field mHField = mActivityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(mActivityThread);
// 替换Handler的Callback
mCallbackField.set(mH, new MyCallback(mH));
}
class MyCallback implements Handler.Callback {
private Handler mH;
public MyCallback(Handler mH) {
this.mH = mH;
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
// 替换Intent为原始插件Activity的Intent
try {
Object obj = msg.obj;
Field intentField = obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(obj);
Intent actionIntent = intent.getParcelableExtra("actionIntent");
if (actionIntent != null) {
intentField.set(obj, actionIntent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 继续处理消息
mH.handleMessage(msg);
return true;
}
}
3. 将插件dex和宿主dex合并
private void mergePluginDex() throws Exception {
// 获取宿主DexPathList
PathClassLoader pathClassLoader = (PathClassLoader) this.getClassLoader();
Class<?> mBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = mBaseDexClassLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object mDexPathList = pathListField.get(pathClassLoader);
// 获取插件DexPathList
File pluginDirFile = getDir("plugin", Context.MODE_PRIVATE);
File pluginFile = new File(pluginDirFile, "p.apk");
DexClassLoader dexClassLoader = new DexClassLoader(
pluginFile.getAbsolutePath(),
getDir("pluginDir", Context.MODE_PRIVATE).getAbsolutePath(),
null,
getClassLoader()
);
Class<?> mBaseDexClassLoaderClassPlugin = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListFieldPlugin = mBaseDexClassLoaderClassPlugin.getDeclaredField("pathList");
pathListFieldPlugin.setAccessible(true);
Object mDexPathListPlugin = pathListFieldPlugin.get(dexClassLoader);
// 合并DexElements
Field dexElementsField = mDexPathList.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
Object dexElements = dexElementsField.get(mDexPathList);
Object dexElementsPlugin = dexElementsField.get(mDexPathListPlugin);
int mainDexLeng = Array.getLength(dexElements);
int pluginDexLeng = Array.getLength(dexElementsPlugin);
Object newDexElements = Array.newInstance(
dexElements.getClass().getComponentType(),
mainDexLeng + pluginDexLeng
);
System.arraycopy(dexElements, 0, newDexElements, 0, mainDexLeng);
System.arraycopy(dexElementsPlugin, 0, newDexElements, mainDexLeng, pluginDexLeng);
// 设置新的DexElements
dexElementsField.set(mDexPathList, newDexElements);
// 处理加载插件中的布局等后续操作
doPluginLayoutLoad();
}
4. 小结
插件化技术主要包括以下三个步骤:
- 欺骗AMS:通过Hook IActivityManager的startActivity方法,使得系统认为插件中的Activity是宿主应用的一部分。
- Hook LAUNCH_ACTIVITY事件:拦截并修改AMS的LAUNCH_ACTIVITY消息,以启动插件中的Activity。
- 合并插件Dex和宿主Dex:动态地将插件的Dex文件合并到宿主应用的Dex列表中,使得插件中的类可以被宿主应用直接加载和使用。
这种插件化方式能够减少对宿主应用的侵入性,但同时也需要处理Android系统版本的适配问题,增加了开发和维护的复杂度。