Android插件化实现动态加载Activity笔记

Activity的启动流程

Activity的启动是一个很复杂的过程,涉及的类也非常多,这是一张启动UML流程图:
在这里插入图片描述

简化一下大致就是:①startActivity > ②系统获取启动信息 > ③校验Manifast > ④创建Activity > ⑤调用生命周期。

如果我们想要动态加载的Activity位于一个独立的APK(或.jar、.aar文件)中,那么,在主工程的Manifast中提前注册好想要启动的Activity编译器都会给我们报错,显然不是正确的做法。

可选择的正确思路应该是:

  1. ①startActivity时中传入包含启动目标TargetActivityIntent,无需在主工程的Manifast文件中申明。
  2. 在主工程中创建一个空的ProxyActivity并注册在Manifast中,在②系统获取启动信息(Intent)前,将Intent中包含的TargetActivity替换为ProxyActivity,使得系统拿到的是在Manifast中注册过的ProxyActivity,以通过第③步中的系统校验
  3. 然后在系统④创建Activity之前将Intent中包含启动信息ProxyActivity还原回TargetActivity

寻找Hook锚点

我们要通过系统的校验,需要在系统获取Intent信息之前将他替换掉。从 Activity的启动流程 中我们得知ActivityStarter是用于确定intentflags 应如何转换为 activity的类,所以替换锚点应从启动流程进入ActivityStarter类之前的ActivityTaskManagerService(AMS)类中找。

较为理想的Hook锚点是Instrumentation#execStartActivityActivityTaskManager#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 用于实现应用程序插装代码的基本类,主要负责ActivityApplication的创建和生命周期调用。在运行时,这个类将在任何应用程序代码之前被实例化,从而允许我们监视系统与应用程序的所有交互。

从类的职能上来讲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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值