Hook实现动态加载Activity
Activity的启动流程
Activity的启动是一个很复杂的过程,涉及的类也非常多,这是一张启动UML流程图:
简化一下大致就是:①startActivity > ②系统获取启动信息 > ③校验Manifast > ④创建Activity > ⑤调用生命周期。
如果我们想要动态加载的Activity位于一个独立的APK(或.jar、.aar文件)中,那么,在主工程的Manifast中提前注册好想要启动的Activity编译器都会给我们报错,显然不是正确的做法。
可选择的正确思路应该是:
- 在①startActivity时中传入包含启动目标
TargetActivity
的Intent
,无需在主工程的Manifast文件中申明。 - 在主工程中创建一个空的
ProxyActivity
并注册在Manifast中,在②系统获取启动信息(Intent)前,将Intent
中包含的TargetActivity
替换为ProxyActivity
,使得系统拿到的是在Manifast中注册过的ProxyActivity
,以通过第③步中的系统校验。 - 然后在系统④创建Activity之前将Intent中包含启动信息
ProxyActivity
还原回TargetActivity
。
寻找Hook锚点
我们要通过系统的校验,需要在系统获取Intent
信息之前将他替换掉。从 Activity的启动流程 中我们得知ActivityStarter
是用于确定intent
和 flags
应如何转换为 activity的类,所以替换锚点应从启动流程进入ActivityStarter
类之前的ActivityTaskManagerService
(AMS)类中找。
较为理想的Hook锚点是Instrumentation#execStartActivity
和ActivityTaskManager#startActivity
。
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
>>>
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
>>>
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
>>>
int result = ActivityTaskManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);//Activity是否在Manifast中注册过,就是在这个方法里校验的。
>>>
}
我们看一下execStartActivity
方法的实现,发现在不同的SDK版本中谷歌已经对他进行了三次改动:
- Android8.0之前的版本中通过
ActivityManagerNative.getDefault
获取ActivityManagerService
的实例来调用其startActivity
方法。 - Android10之前的版本是通过
ActivityManager.getService
获取ActivityManagerService
的实例来调用其startActivity
方法。 - Android10开始的版本是通过
ActivityTaskManager.getService
获取ActivityTaskManagerService
的实例来调用其startActivity
方法。
不同版本之间,获取AMS实例的类是不同的。从版本兼容性上来讲,把ActivityTaskManager
作为Hook目标并不是很理想。我们再把目光转向Instrumentation
。
Instrumentation
用于实现应用程序插装代码的基本类,主要负责Activity
和Application
的创建和生命周期调用。在运行时,这个类将在任何应用程序代码之前被实例化,从而允许我们监视系统与应用程序的所有交互。
从类的职能上来讲execStartActivity
是可以选做Hook目标的。而且,此类并没有因为版本的差异而改变其创建方式。所以,替换ProxyActivity
选择Hook Instrumentation#execStartActivity
的方式 要更合适一些。
替换的锚点找到了,那么还原的呢?我们的最终目的是要系统在创建启动Activity的时候使用我们指定的Intent,所以要从Activity创建之前去寻找。
还是从 Activity的启动流程 来看。ActivityThread
在收到Handler
发送的启动Activity的消息后会在ActivityThread#performLaunchActivity
中处理:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
>>>
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
>>>
}
>>>
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
>>>
}
Instrumentation#newActivity:
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);
}
public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
@Nullable Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Activity) cl.loadClass(className).newInstance();
}
Activity
的实例是在类Instrumentation
中开始创建的。因为替换点我们选择了 Instrumentation
类的execStartActivity
方法。所以,Instrumentation
类的newActivity
可以作为还原点,这样我们只需要Hook Instrumentation
一个类,可以避免过多的Hook操作,后续SDK版本中谷歌如果对API做了改动,我们也只需要主要关注这一个类。
替换目标Activity
Hook操作会涉及到一系列反射操作,我们可以封装一个反射的工具类ReflectUtil:
public class ReflectUtil{
public static