Activity 插件化:Instrumentation Hook、AMS 欺骗与 ActivityThread 偷梁换柱
前两篇文章分别解构了 Android 的类加载体系(BaseDexClassLoader → DexPathList → dexElements)和资源管理架构(Resources → AssetManager → resources.arsc)。通过反射操作这两条管线,插件化框架可以让系统"找到"来自外部 APK 的类和资源。但这还不够——Android 的四大组件(Activity、Service、BroadcastReceiver、ContentProvider)与普通 Java 类有一个本质区别:它们必须在 AndroidManifest.xml 中注册,否则系统拒绝启动。
插件 APK 是运行时动态加载的,它的 Activity 显然无法在宿主的 AndroidManifest.xml 中提前声明。那么,插件化框架是如何绕过这一系统级限制,让一个"黑户" Activity 拥有完整生命周期的?
答案隐藏在 Activity 启动流程的每一个环节中。框架的核心策略是:在发送请求时"瞒天过海",在接收响应时"偷梁换柱"。
前置依赖:理解 Android ClassLoader 体系(第一篇)和资源加载机制(第二篇),以及 Android 系统 Activity 启动流程的基本概念。
Activity 启动的全链路源码解析
要理解 Hook 机制,必须先精确掌握 Activity 的启动全链路。只有知道"正常流程每一步做了什么",才能明白"在哪一步动手脚、以及为什么在那一步动手脚"。
宏观流程:三个进程的协作
一个 Activity 的启动涉及三个进程的协作:
┌──────────────────────────────────────────────────────────────────┐
│ App 进程 A(发起方) │
│ │
│ Activity.startActivity(intent) │
│ ↓ │
│ Instrumentation.execStartActivity() │
│ ↓ Binder IPC(跨进程调用) │
├──────────────────────────────────────────────────────────────────┤
│ system_server 进程 │
│ │
│ ActivityTaskManagerService.startActivity() │
│ ↓ │
│ ● 校验 AndroidManifest.xml 注册 ← 插件化的拦路虎 │
│ ● 解析 Intent,匹配目标 Activity │
│ ● 管理任务栈(Task)和回退栈(Back Stack) │
│ ● 如果目标进程未启动,请求 Zygote fork 新进程 │
│ ↓ Binder IPC(回调目标进程) │
├──────────────────────────────────────────────────────────────────┤
│ App 进程 B(目标方) │
│ │
│ ApplicationThread.scheduleTransaction() │
│ ↓ 通过 Handler(H) 切换到主线程 │
│ ActivityThread.handleLaunchActivity() │
│ ↓ │
│ ActivityThread.performLaunchActivity() │
│ ├─ Instrumentation.newActivity() ← 反射创建 Activity 实例 │
│ ├─ Activity.attach() ← 绑定 Context、Window │
│ └─ Instrumentation.callActivityOnCreate() ← 触发 onCreate │
└──────────────────────────────────────────────────────────────────┘
如果把启动 Activity 比作"寄快递",那发起方 App 是寄件人,system_server 中的 AMS(ActivityManagerService)是快递公司的调度中心,目标 App 进程是收件人。调度中心会检查"包裹是否合规"(Manifest 校验),分配路线(栈管理),通知收件人签收(创建 Activity 实例)。
发起端:从 startActivity 到 Instrumentation
当开发者调用 startActivity(intent) 时,内部经历了以下调用链:
// Activity.java(AOSP 源码,简化)
@Override
public void startActivity(Intent intent) {
startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
// ...
startActivityForResult(intent, -1, options);
}
public void startActivityForResult(Intent intent, int requestCode,
@Nullable Bundle options) {
// ...
// 关键:委托给 Instrumentation
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, // 当前 Activity
mMainThread.getApplicationThread(), // App 的 Binder 代理
mToken, // Activity 的标识 Token
this, // 等待结果的 Activity
intent, // 启动 Intent
requestCode, // 请求码
options); // 附加选项
// ...
}
注意 mInstrumentation 这个成员变量。每个 ActivityThread(应用主线程管理者)有且只有一个 Instrumentation 实例,所有 Activity 共享它。Instrumentation 是 Activity 生命周期管理的"总管家":
// Instrumentation.java(AOSP 源码,关键方法)
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token,
Activity target, Intent intent, int requestCode,
Bundle options) {
// ... 通知 Activity 监控器(用于测试框架)
// 核心:通过 Binder 调用 AMS/ATMS 的 startActivity
int result = ActivityTaskManager.getService().startActivity(
whoThread, // IApplicationThread Binder 代理
who.getOpPackageName(), // 调用者包名
who.getAttributionTag(),
intent, // 启动 Intent
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, // 调用者 Activity 的 Token
target != null ? target.mEmbeddedID : null,
requestCode,
0, // 启动标志
null, // ProfilerInfo
options); // ActivityOptions
// 检查启动结果,如果有错误会抛出异常
checkStartActivityResult(result, intent);
return null;
}
ActivityTaskManager.getService() 返回的是 IActivityTaskManager 的 Binder 代理对象——这是一个 AIDL 接口,调用它的 startActivity 会触发跨进程通信,请求发送到 system_server 进程中的 ActivityTaskManagerService(ATMS)。
AMS/ATMS:Manifest 校验——插件化的核心障碍
system_server 进程中的 ATMS 收到请求后,会执行一系列校验。对插件化来说,最致命的是 Manifest 校验:
// ActivityTaskManagerService.java(AOSP 源码,简化调用链)
// startActivity → startActivityAsUser → ... → ActivityStarter.execute()
// ActivityStarter.java
private int executeRequest(Request request) {
// ...
// 解析 Intent,查找目标 Activity 的信息
ResolveInfo rInfo = getSupervisor().resolveIntent(intent, ...);
ActivityInfo aInfo = getSupervisor().resolveActivity(intent, rInfo, ...);
// 关键校验:如果 aInfo 为 null,说明目标 Activity 未在任何已安装 APK 的
// Manifest 中注册——返回错误!
if (aInfo == null) {
// 这就是插件 Activity 被拒绝的地方
// 最终会抛出 ActivityNotFoundException
return START_INTENT_NOT_RESOLVED;
}
// 权限校验、任务栈管理等...
}
这就是插件化 Activity 面临的核心矛盾:插件 APK 是动态加载的,不在系统的已安装应用列表中,插件 Activity 自然无法通过 Manifest 校验。直接 startActivity 启动插件 Activity,只会得到 ActivityNotFoundException。
接收端:ActivityThread 如何创建 Activity
当 AMS 校验通过后,它通过 Binder 回调目标进程的 ApplicationThread。在 Android 9(API 28) 之后,系统使用 ClientTransaction 架构替代了旧的 LAUNCH_ACTIVITY 消息机制:
Android 8 及以下(旧架构):
AMS → ApplicationThread.scheduleLaunchActivity()
→ H.sendMessage(LAUNCH_ACTIVITY, record)
→ handleLaunchActivity()
Android 9 及以上(新架构 ClientTransaction):
AMS → ClientLifecycleManager.scheduleTransaction()
→ ApplicationThread.scheduleTransaction(ClientTransaction)
→ TransactionExecutor.execute()
├─ executeCallbacks() → LaunchActivityItem.execute()
└─ executeLifecycleState() → ResumeActivityItem.execute()
无论哪种架构,最终都会调用到 ActivityThread.performLaunchActivity()——这是 Activity 实例化的核心方法:
// ActivityThread.performLaunchActivity()(AOSP 源码,简化)
private Activity performLaunchActivity(ActivityClientRecord r,
Intent customIntent) {
// 1. 获取 Activity 的类信息
ComponentName component = r.intent.getComponent();
// 2. 通过 Instrumentation 反射创建 Activity 实例
// 这里使用的 ClassLoader 是 r.packageInfo.getClassLoader()
// 即应用的 PathClassLoader
Activity activity = mInstrumentation.newActivity(
cl, // ClassLoader
component.getClassName(), // Activity 类名(全限定名)
r.intent); // Intent
// 3. 创建 Application(如果尚未创建)
Application app = r.packageInfo.makeApplicationInner(false, mInstrumentation);
// 4. 创建 Context 并绑定
ContextImpl appContext = createBaseContextForActivity(r);
activity.attach(appContext, this, getInstrumentation(),
r.token, r.ident, app, r.intent, r.activityInfo,
title, r.parent, r.embeddedID, r.lastNonConfigurationInstances,
config, r.referrer, r.voiceInteractor, window, ...);
// 5. 触发 onCreate 生命周期
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state,
r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
return activity;
}
Instrumentation.newActivity() 的实现非常简单——就是反射创建对象:
// Instrumentation.java(AOSP 源码)
public Activity newActivity(ClassLoader cl, String className, Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
String pkg = intent != null && intent.getComponent() != null
? intent.getComponent().getPackageName() : null;
return getFactory(pkg).instantiateActivity(cl, className, intent);
}
// AppComponentFactory.java(默认实现)
public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl,
@NonNull String className, @Nullable Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity) cl.loadClass(className).newInstance();
}
关键发现:Activity 的类名来自 r.intent.getComponent().getClassName(),而 r.intent 是 AMS 回传的。如果能在 AMS 回传后、newActivity 调用前修改这个 Intent 中的类名,就能让系统创建我们想要的插件 Activity!
核心矛盾与解决思路
矛盾的本质
将上述分析提炼为一句话:AMS 在 system_server 进程中校验 Manifest,应用层无法修改 system_server 的行为。
但换个角度思考——AMS 校验的是 Intent 中携带的 Activity 信息,而 Intent 的构造发生在应用进程中。如果能在 Intent 离开应用进程之前"做手脚",然后在 AMS 回调之后"还原现场",就能完美绕过校验。
"瞒天过海 + 偷梁换柱"策略
┌──── 应用进程 ────┐
│ │
startActivity │ Hook 点 1 │
(插件Activity) ──→ │ ● 将 Intent 中 │
│ 的目标从 │
│ "插件Activity" │
│ 替换为 │ ──→ AMS 校验通过!
│ "占坑Activity"│ (占坑Activity已在
│ (Stub) │ Manifest 中注册)
│ │
│ Hook 点 2 │
AMS ──→ │ ● 将 Intent 中 │
回调 │ 的目标从 │
│ "占坑Activity" │
│ 还原为 │ ──→ 创建的是真正的
│ "插件Activity" │ 插件 Activity!
│ │
└──────────────────┘
这就是插件化 Activity 的核心策略:
- Hook 点 1(去程欺骗):在
startActivity请求发送给 AMS 之前,将 Intent 中的目标 Activity 替换为一个预先在宿主 Manifest 中注册的"占坑 Activity"(Stub Activity)。AMS 看到的是一个合法的已注册 Activity,校验通过。 - Hook 点 2(回程还原):在 AMS 回调应用进程、即将创建 Activity 实例之前,将 Intent 中的目标从"占坑 Activity"还原为真正的插件 Activity。
Instrumentation.newActivity()最终使用插件的 ClassLoader 加载并实例化插件 Activity。
接下来,我们逐一剖析实现这两个 Hook 点的具体技术方案。
占坑 Activity:在 Manifest 中预留"席位"
在实施 Hook 之前,首先需要在宿主的 AndroidManifest.xml 中预先注册一批"占坑 Activity"——它们本身不包含任何业务逻辑,唯一的作用是帮助插件 Activity 通过 AMS 的 Manifest 校验。
为什么需要多个 Stub?
Activity 有四种启动模式(launchMode),不同模式在任务栈中的行为完全不同。AMS 在处理启动请求时会读取 Manifest 中声明的 launchMode,因此必须为每种模式预留对应的 Stub:
<!-- 宿主 AndroidManifest.xml 中的占坑声明 -->
<!-- standard 模式:每次启动都创建新实例 -->
<activity android:name=".stub.StubActivity$Standard1"
android:launchMode="standard" />
<activity android:name=".stub.StubActivity$Standard2"
android:launchMode="standard" />
<!-- 可声明多个,应对同时启动多个 standard Activity 的场景 -->
<!-- singleTop 模式:栈顶复用 -->
<activity android:name=".stub.StubActivity$SingleTop1"
android:launchMode="singleTop" />
<!-- singleTask 模式:栈内复用 -->
<activity android:name=".stub.StubActivity$SingleTask1"
android:launchMode="singleTask" />
<!-- singleInstance 模式:独占任务栈 -->
<activity android:name=".stub.StubActivity$SingleInstance1"
android:launchMode="singleInstance" />
<!-- 还需要声明不同 theme 的 Stub(透明主题、对话框主题等) -->
<activity android:name=".stub.StubActivity$Transparent1"
android:launchMode="standard"
android:theme="@android:style/Theme.Translucent" />
<!-- 不同进程的 Stub(用于插件运行在独立进程的场景) -->
<activity android:name=".stub.StubActivity$Process1"
android:launchMode="standard"
android:process=":plugin1" />
这就像酒店提前预留不同类型的房间——标准间、套房、总统套房——以便任何类型的"客人"(插件 Activity)到来时都能安排入住。
Hook 方案一:替换 Instrumentation
原理
Instrumentation 是 Activity 生命周期的"总管家"。ActivityThread 中有一个 mInstrumentation 字段,所有 Activity 的启动(execStartActivity)和实例化(newActivity)都经过它。
如果用一个自定义的 Instrumentation 子类替换掉系统默认的,就可以同时拦截"去程"和"回程"——在一个对象中完成"欺骗 AMS"和"还原插件 Activity"两步操作。
ActivityThread
│
├── mInstrumentation(原始)
│ ↓ 替换为
├── mInstrumentation(自定义 ProxyInstrumentation)
│ ├── execStartActivity() → Hook 点 1:替换 Intent
│ └── newActivity() → Hook 点 2:还原并创建插件 Activity
第一步:反射替换 Instrumentation
/**
* 将 ActivityThread 中的 Instrumentation 替换为自定义的代理版本
* 这是整个 Hook 链的起点
*/
public static void hookInstrumentation() throws Exception {
// 获取 ActivityThread 的单例(currentActivityThread)
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass
.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object activityThread = currentActivityThreadMethod.invoke(null);
// 获取原始的 Instrumentation
Field instrumentationField = activityThreadClass
.getDeclaredField("mInstrumentation");
instrumentationField.setAccessible(true);
Instrumentation originalInstrumentation =
(Instrumentation) instrumentationField.get(activityThread);
// 替换为自定义的代理 Instrumentation
ProxyInstrumentation proxy = new ProxyInstrumentation(
originalInstrumentation);
instrumentationField.set(activityThread, proxy);
Log.d("PluginHook", "Instrumentation 替换成功");
}
第二步:实现 ProxyInstrumentation
/**
* 自定义 Instrumentation 代理
* 拦截 Activity 的启动和创建过程,实现插件 Activity 的加载
*/
public class ProxyInstrumentation extends Instrumentation {
// 原始 Instrumentation,用于委托非插件相关的调用
private final Instrumentation mOriginal;
// 用于存储被替换的真实 Intent 的 Extra Key
private static final String EXTRA_REAL_INTENT = "plugin_real_intent";
// 占坑 Activity 的类名
private static final String STUB_ACTIVITY =
"com.host.stub.StubActivity$Standard1";
public ProxyInstrumentation(Instrumentation original) {
this.mOriginal = original;
}
/**
* Hook 点 1:拦截 startActivity 请求
* 在 Intent 发送给 AMS 之前,将目标替换为占坑 Activity
*/
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token,
Activity target, Intent intent, int requestCode,
Bundle options) {
// 判断是否是插件 Activity 的启动请求
if (isPluginIntent(intent)) {
Log.d("PluginHook", "拦截插件 Activity 启动: "
+ intent.getComponent());
// 保存真实的 Intent 信息(插件 Activity 的类名)
intent.putExtra(EXTRA_REAL_INTENT, intent.getComponent().clone());
// 将 Intent 的目标替换为占坑 Activity
// AMS 校验时,看到的是这个已注册的占坑 Activity
intent.setComponent(new ComponentName(
who.getPackageName(), STUB_ACTIVITY));
}
// 通过反射调用原始 Instrumentation 的 execStartActivity
// 因为 execStartActivity 不是 public 方法,需要反射调用
try {
Method execMethod = Instrumentation.class.getDeclaredMethod(
"execStartActivity",
Context.class, IBinder.class, IBinder.class,
Activity.class, Intent.class, int.class, Bundle.class);
execMethod.setAccessible(true);
return (ActivityResult) execMethod.invoke(
mOriginal, who, contextThread, token,
target, intent, requestCode, options);
} catch (Exception e) {
throw new RuntimeException("execStartActivity 反射调用失败", e);
}
}
/**
* Hook 点 2:拦截 Activity 实例化
* 在 AMS 回调后,将占坑 Activity 还原为真正的插件 Activity
*/
@Override
public Activity newActivity(ClassLoader cl, String className,
Intent intent) throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
// 检查 Intent 中是否包含被"偷梁换柱"的真实 Activity 信息
ComponentName realComponent = intent.getParcelableExtra(
EXTRA_REAL_INTENT);
if (realComponent != null) {
Log.d("PluginHook", "还原插件 Activity: " + realComponent);
// 使用插件的 ClassLoader 加载插件 Activity 类
String pluginClassName = realComponent.getClassName();
ClassLoader pluginClassLoader = PluginManager.getInstance()
.getPluginClassLoader(realComponent.getPackageName());
// 用插件 ClassLoader 替代宿主 ClassLoader 创建实例
return super.newActivity(pluginClassLoader,
pluginClassName, intent);
}
// 非插件 Activity,走正常流程
return super.newActivity(cl, className, intent);
}
@Override
public void callActivityOnCreate(Activity activity,
Bundle icicle) {
// 可以在 onCreate 之前做额外初始化
// 比如注入插件的 Resources、替换 Context 等
injectPluginResourcesIfNeeded(activity);
mOriginal.callActivityOnCreate(activity, icicle);
}
/**
* 判断 Intent 是否指向插件 Activity
*/
private boolean isPluginIntent(Intent intent) {
ComponentName component = intent.getComponent();
if (component == null) return false;
return PluginManager.getInstance()
.isPluginActivity(component.getClassName());
}
/**
* 为插件 Activity 注入插件的 Resources
*/
private void injectPluginResourcesIfNeeded(Activity activity) {
// 结合上一篇文章的资源加载方案
// 替换 Activity 的 Resources 为插件 Resources
}
}
Instrumentation Hook 方案的优势
这种方案的最大优势是一个 Hook 点解决两个问题——execStartActivity 和 newActivity 都在同一个 Instrumentation 对象中,代码内聚度高、逻辑清晰。VirtualAPK 框架就采用了这种方案。
Hook 方案二:动态代理 AMS Binder + Hook Handler
另一种更经典的方案是分别 Hook 两个点:用动态代理拦截 AMS 的 Binder 接口(去程欺骗),用 Handler Callback 拦截 ActivityThread.mH(回程还原)。
去程 Hook:动态代理 IActivityManager / IActivityTaskManager
ActivityTaskManager.getService() 返回的是一个 IActivityTaskManager 接口的 Binder 代理对象。它在 ActivityTaskManager 内部以单例形式缓存。如果用 Java 动态代理包装这个单例,就能拦截所有发往 AMS 的请求。
/**
* Hook AMS 的 Binder 代理,拦截 startActivity 请求
* 适配 Android 10+ 的 ActivityTaskManager 和旧版的 ActivityManager
*/
public static void hookAMSBinder() throws Exception {
Object amsProxy;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10+:Hook IActivityTaskManager
// ActivityTaskManager 中有一个 IActivityTaskManagerSingleton 单例
Class<?> atmClass = Class.forName("android.app.ActivityTaskManager");
Field singletonField = atmClass.getDeclaredField(
"IActivityTaskManagerSingleton");
singletonField.setAccessible(true);
Object singleton = singletonField.get(null);
// Singleton 类中的 mInstance 字段持有真正的 Binder 代理
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field instanceField = singletonClass.getDeclaredField("mInstance");
instanceField.setAccessible(true);
Object originalProxy = instanceField.get(singleton);
// 创建动态代理
Class<?> iATMInterface = Class.forName(
"android.app.IActivityTaskManager");
Object hookedProxy = Proxy.newProxyInstance(
iATMInterface.getClassLoader(),
new Class[]{iATMInterface},
new AMSInvocationHandler(originalProxy));
// 替换单例中的 Binder 代理为我们的动态代理
instanceField.set(singleton, hookedProxy);
} else {
// Android 8~9:Hook IActivityManager
Class<?> amClass = Class.forName("android.app.ActivityManager");
Field singletonField = amClass.getDeclaredField(
"IActivityManagerSingleton");
singletonField.setAccessible(true);
Object singleton = singletonField.get(null);
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field instanceField = singletonClass.getDeclaredField("mInstance");
instanceField.setAccessible(true);
Object originalProxy = instanceField.get(singleton);
Class<?> iAMInterface = Class.forName(
"android.app.IActivityManager");
Object hookedProxy = Proxy.newProxyInstance(
iAMInterface.getClassLoader(),
new Class[]{iAMInterface},
new AMSInvocationHandler(originalProxy));
instanceField.set(singleton, hookedProxy);
}
}
/**
* AMS Binder 代理的 InvocationHandler
* 拦截 startActivity 调用,替换 Intent 中的目标
*/
static class AMSInvocationHandler implements InvocationHandler {
private final Object mOriginal;
AMSInvocationHandler(Object original) {
this.mOriginal = original;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 只拦截 startActivity 方法
if ("startActivity".equals(method.getName())) {
// 在参数列表中找到 Intent 对象
int intentIndex = -1;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
intentIndex = i;
break;
}
}
if (intentIndex >= 0) {
Intent originalIntent = (Intent) args[intentIndex];
// 判断是否是插件 Activity
if (isPluginIntent(originalIntent)) {
// 保存真实目标
originalIntent.putExtra("plugin_real_component",
originalIntent.getComponent());
// 替换为占坑 Activity
originalIntent.setComponent(new ComponentName(
"com.host.app",
"com.host.stub.StubActivity$Standard1"));
}
}
}
// 调用原始方法
return method.invoke(mOriginal, args);
}
}
回程 Hook:拦截 ActivityThread.mH
AMS 回调后,在旧架构中会通过 ActivityThread 的内部 Handler(mH)发送 LAUNCH_ACTIVITY 消息。通过设置 Handler 的 mCallback 字段,可以在消息被 handleMessage 处理之前拦截它:
/**
* Hook ActivityThread 的 mH Handler
* 在 Activity 创建之前还原被替换的 Intent
*/
public static void hookHandler() throws Exception {
// 获取 ActivityThread 实例
Class<?> atClass = Class.forName("android.app.ActivityThread");
Method currentMethod = atClass.getDeclaredMethod("currentActivityThread");
currentMethod.setAccessible(true);
Object activityThread = currentMethod.invoke(null);
// 获取 mH(Handler)
Field mHField = atClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(activityThread);
// 设置 Callback——Handler 的消息处理优先级:
// mCallback.handleMessage() → Handler.handleMessage()
// 如果 mCallback 返回 true,则 Handler.handleMessage 不会执行
Field callbackField = Handler.class.getDeclaredField("mCallback");
callbackField.setAccessible(true);
callbackField.set(mH, new HookCallback(mH));
}
/**
* 自定义 Handler.Callback
* 拦截 LAUNCH_ACTIVITY 消息,还原插件 Activity 的 Intent
*/
static class HookCallback implements Handler.Callback {
private final Handler mOriginalHandler;
// Android 9 以下的 LAUNCH_ACTIVITY 消息常量
private static final int LAUNCH_ACTIVITY = 100;
// Android 9+ 的 EXECUTE_TRANSACTION 消息常量
private static final int EXECUTE_TRANSACTION = 159;
HookCallback(Handler handler) {
this.mOriginalHandler = handler;
}
@Override
public boolean handleMessage(@NonNull Message msg) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
// Android 8 及以下:直接处理 LAUNCH_ACTIVITY 消息
if (msg.what == LAUNCH_ACTIVITY) {
handleLaunchActivity(msg);
}
} else {
// Android 9+:处理 EXECUTE_TRANSACTION 消息
// 需要从 ClientTransaction 中提取 LaunchActivityItem
if (msg.what == EXECUTE_TRANSACTION) {
handleExecuteTransaction(msg);
}
}
// 返回 false,让原始 Handler 继续处理
return false;
}
/**
* 旧架构(Android 8 及以下):
* msg.obj 是 ActivityClientRecord,直接修改其 intent
*/
private void handleLaunchActivity(Message msg) {
try {
Object record = msg.obj; // ActivityClientRecord
Field intentField = record.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(record);
// 还原真实的插件 Activity 信息
restorePluginIntent(intent);
} catch (Exception e) {
Log.e("PluginHook", "还原 Intent 失败", e);
}
}
/**
* 新架构(Android 9+):
* msg.obj 是 ClientTransaction,需要从中提取 LaunchActivityItem
*/
private void handleExecuteTransaction(Message msg) {
try {
Object transaction = msg.obj; // ClientTransaction
Field callbacksField = transaction.getClass()
.getDeclaredField("mActivityCallbacks");
callbacksField.setAccessible(true);
List<?> callbacks = (List<?>) callbacksField.get(transaction);
if (callbacks == null || callbacks.isEmpty()) return;
for (Object item : callbacks) {
// 查找 LaunchActivityItem
if (item.getClass().getName().contains("LaunchActivityItem")) {
Field intentField = item.getClass()
.getDeclaredField("mIntent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(item);
restorePluginIntent(intent);
}
}
} catch (Exception e) {
Log.e("PluginHook", "还原 ClientTransaction Intent 失败", e);
}
}
/**
* 从 Intent 的 Extra 中取出真实的插件组件名,还原到 Intent 上
*/
private void restorePluginIntent(Intent intent) {
if (intent == null) return;
ComponentName realComponent = intent.getParcelableExtra(
"plugin_real_component");
if (realComponent != null) {
intent.setComponent(realComponent);
Log.d("PluginHook", "还原插件 Activity: " + realComponent);
}
}
}
两种方案的架构对比
| 维度 | 方案一:替换 Instrumentation | 方案二:动态代理 AMS + Hook Handler |
|---|---|---|
| Hook 点数量 | 1 个(Instrumentation) | 2 个(AMS 代理 + Handler Callback) |
| 代码复杂度 | 较低,逻辑内聚 | 较高,分散在两个 Hook 点 |
| 版本适配 | 需适配 execStartActivity 方法签名变化 |
需适配 AMS 单例字段名变化 + ClientTransaction 架构变化 |
| 代表框架 | VirtualAPK | DroidPlugin、360 DroidPlugin |
| 灵活度 | Instrumentation 是所有生命周期的入口,可拦截更多操作 | 仅拦截 startActivity 和 Activity 创建 |
| 风险 | 替换了整个 Instrumentation,影响所有 Activity | 仅在特定方法和消息中做修改,影响范围较小 |
生命周期管理:占坑 Activity "活着" → 插件 Activity "享福"
通过上述 Hook 方案,插件 Activity 可以被成功创建。但一个关键问题是:它的生命周期回调(onStart、onResume、onPause、onStop、onDestroy)是否正常?
答案是:正常的——而且不需要额外 Hook。
为什么生命周期自动生效?
这要从 AMS 管理 Activity 的方式说起。AMS 通过 ActivityRecord(内部数据结构)跟踪每个 Activity 的状态。ActivityRecord 的标识是 IBinder token——一个唯一的 Binder 令牌。
AMS 视角: 应用进程视角:
┌───────────────────┐ ┌───────────────────┐
│ ActivityRecord │ │ Activity 实例 │
│ token: 0xABC │ ←── 对应 ──→ │ mToken: 0xABC │
│ component: │ │ 实际类型: │
│ StubActivity │ │ PluginActivity │
│ (占坑名) │ │ (插件类) │
└───────────────────┘ └───────────────────┘
AMS 记录的组件名是"占坑 Activity",但它并不关心应用进程中实际运行的是哪个类——AMS 只通过 token 来标识和调度。当 AMS 需要触发 onResume 时,它通过 token 找到目标进程中对应的 Activity 实例(无论它实际上是 StubActivity 还是 PluginActivity),调用其生命周期方法。
因此,只要 token 正确绑定(在 Activity.attach() 中完成),插件 Activity 就能自动接收所有生命周期回调。
需要额外处理的场景
虽然基本生命周期自动生效,但以下场景需要框架做额外处理:
| 场景 | 问题 | 解决方案 |
|---|---|---|
onActivityResult |
返回结果通过 token 回传,但 requestCode 与占坑 Activity 绑定 |
在 execStartActivity 中建立 requestCode 映射表 |
taskAffinity |
AMS 使用占坑 Activity 声明的 taskAffinity 管理任务栈 |
为不同 taskAffinity 的插件 Activity 预留不同 Stub |
configChanges |
AMS 根据 Manifest 声明决定是否重建 Activity | 占坑 Activity 声明 configChanges 覆盖所有常见配置变更 |
intent-filter |
隐式 Intent 匹配依赖 Manifest 声明 | 宿主代为注册,或使用显式 Intent 搭配路由框架 |
主流框架的 Activity Hook 策略对比
VirtualAPK(滴滴):Instrumentation 整体替换
VirtualAPK 采用方案一——替换 Instrumentation。它的核心类是 VAInstrumentation,同时负责去程欺骗和回程还原,并在 callActivityOnCreate 中注入插件的 Resources 和 ClassLoader。
VirtualAPK 的 Activity 启动流程:
1. 替换 ActivityThread.mInstrumentation → VAInstrumentation
2. startActivity 时,VAInstrumentation.execStartActivity():
● 查找目标 Activity 对应的 Stub(按 launchMode 匹配)
● 替换 Intent 目标为 Stub
3. AMS 校验通过,回调创建 Activity
4. VAInstrumentation.newActivity():
● 从 Intent Extra 中恢复真实 Activity 类名
● 使用插件的 ClassLoader 加载并实例化
5. VAInstrumentation.callActivityOnCreate():
● 注入插件的 Resources
● 触发 onCreate
Shadow(腾讯):零反射的代理 Activity 方案
Shadow 的设计哲学是彻底避免反射 Framework 层私有 API。它不 Hook Instrumentation,也不 Hook AMS Binder,而是采用了一种完全不同的思路——代理 Activity 模式:
传统 Hook 方案: Shadow 代理方案:
startActivity startActivity
↓ ↓
Hook Instrumentation 直接启动 PluginContainerActivity
(替换 Intent) (已在 Manifest 中注册)
↓ ↓
AMS 校验通过 AMS 校验通过
↓ ↓
Hook newActivity PluginContainerActivity.onCreate():
(还原 Intent,创建插件类) ● 从 Intent 中获取插件 Activity 类名
↓ ● 用插件 ClassLoader 创建 ShadowActivity
插件 Activity 实例 ● 将所有生命周期回调转发给 ShadowActivity
Shadow 的关键技术是编译期字节码替换:
- Transform 阶段:Shadow 的 Gradle 插件在编译插件时,将所有插件 Activity 的父类从
Activity/AppCompatActivity替换为ShadowActivity(Shadow 框架提供的基类)。 ShadowActivity持有一个HostActivityDelegator接口,这是与宿主空壳的通信桥梁。所有Context相关的方法(getResources()、getAssets()等)都通过这个接口委托给宿主容器。PluginContainerActivity在 Manifest 中注册,它是一个极简的空壳。收到启动请求后,创建对应的ShadowActivity实例,然后将自身的所有生命周期回调逐一转发给ShadowActivity。
┌─────────────────────────────────────┐
│ PluginContainerActivity(宿主空壳) │
│ ■ 已在 Manifest 注册 │
│ ■ 持有 ShadowActivity 实例 │
│ ■ 将所有生命周期转发 │
│ │
│ onCreate() → delegate.onCreate() │
│ onResume() → delegate.onResume() │
│ onPause() → delegate.onPause() │
│ onDestroy() → delegate.onDestroy() │
└─────────────────────────────────────┘
↕ 生命周期转发
┌─────────────────────────────────────┐
│ ShadowActivity(插件业务逻辑) │
│ ■ 编译期父类被替换为 ShadowActivity │
│ ■ Context 方法委托给容器 │
│ ■ 开发者像写普通 Activity 一样编码 │
└─────────────────────────────────────┘
三大框架对比总结
| 维度 | VirtualAPK | DroidPlugin | Shadow |
|---|---|---|---|
| Hook 策略 | 替换 Instrumentation | 动态代理 AMS + Hook Handler | 代理 Activity(零 Hook) |
| 反射内部 API | 中度 | 重度 | 极少(几乎为零) |
| Manifest 校验绕过 | 占坑 Activity | 占坑 Activity | 占坑 Activity(ContainerActivity) |
| 生命周期管理 | 系统自动分发(通过 token) | 系统自动分发 | 容器手动转发 |
| 编译期处理 | Gradle 插件(资源 ID 重映射) | 无 | Gradle 插件(字节码改写父类) |
| Android 版本兼容风险 | 中(Instrumentation 结构相对稳定) | 高(AMS 接口变化频繁) | 低(不依赖 Framework 私有 API) |
| 插件开发侵入性 | 低(插件 Activity 正常编写) | 低 | 低(Transform 自动处理继承) |
Android 版本演进对 Activity Hook 的影响
| Android 版本 | API | 关键变化 | 对 Hook 方案的影响 |
|---|---|---|---|
| 8.0 Oreo | 26 | AMS 相关单例字段名稳定 | Hook 方案二的 AMS 代理相对简单 |
| 9.0 Pie | 28 | ClientTransaction 架构替代 LAUNCH_ACTIVITY 消息;Hidden API 限制 |
Handler Hook 必须适配 EXECUTE_TRANSACTION 消息和 LaunchActivityItem |
| 10 | 29 | ActivityTaskManagerService 从 AMS 中分离 | 动态代理需从 IActivityManager 切换到 IActivityTaskManager |
| 11 | 30 | Hidden API 限制持续加强 | 更多反射入口被封锁 |
| 12 | 31 | ActivityThread.mH 的消息类型和处理逻辑持续变化 |
Handler Hook 需要逐版本适配 |
| 12+ | 31+ | 持续加固;灰名单收紧 | 传统反射 Hook 方案的维护成本持续攀升;Shadow 的零反射方案优势放大 |
从发展趋势看,Google 对 Framework 内部 API 的封锁力度越来越大。反射覆盖 mInstrumentation、动态代理 AMS 单例、设置 mH.mCallback 等操作,都直接依赖 Hidden API。Android 9 引入的灰名单机制已经给这些操作标上了"不推荐"的标签,未来任何版本都可能将它们移入黑名单。
这也是 Shadow 采用"零反射"策略的根本原因——通过编译期字节码改写 + 运行时合法的代理转发,彻底绕开 Hidden API 限制,从而获得最高的系统兼容稳定性。
Activity 的插件化是整个插件化技术体系中最复杂的环节——它不仅需要理解 Activity 启动的跨进程全链路,还需要在"去程"和"回程"两端分别做精确的拦截和还原。
核心思路可以提炼为三步:占坑声明(Manifest 预注册 Stub)→ 去程欺骗(替换 Intent 目标为 Stub)→ 回程还原(恢复真实插件 Activity)。不同框架在具体实现策略上各有取舍——VirtualAPK 选择了"替换 Instrumentation"的内聚方案,DroidPlugin 选择了"动态代理 + Handler Hook"的分离方案,而 Shadow 则走了一条完全不同的"零反射代理"路线。
下一篇文章将对当前主流的插件化框架(VirtualAPK、RePlugin、Shadow、Atlas)进行全方位的横向对比,从架构设计、API 稳定性、性能开销、适用场景等维度帮助你做出技术选型。