Activity启动流程和插件化原理

957 阅读6分钟

Activity启动流程

Activity启动流程分析有很多文章了,为什么我要再写一篇,因为我觉得大部分的文章讲的都有点复杂,个人经验认为学习一种技术尽量从全局去看,否则会陷入细节而让自己对这种技术没有一个全局的概念,看了网上几篇文章,我总结归纳了一张图,图里我也加了很多注释,先看图我再补充一下细节

Activity启动流程.jpg

请求阶段:

ActivityManagerProxy是ActivityManagerService在app进程中的Binder代理对象。调用ActivityManagerProxy.startActivity()最后会调用ActivityManagerService.startActivity()。这样请求就到了ActivityManagerService

响应阶段:

在不考虑多进程的情况下,Activity的启动过程是一个Binder双向通信的过程。AMS要主动与app进程通信要依靠请求启动Activity阶段传过来的IBinder对象,这个IBinder对象就是上面介绍过的Instrumentation.execStartActivity()中的 whoThread对象,它实际上是一个ApplicationThreadProxy对象,用来和ApplicationThread通信。AMS通知app进程启动Activity是通过调用ApplicationThreadProxy.scheduleLaunchActivity()完成的。根据Binder通信,ApplicationThread.scheduleLaunchActivity()会被调用。

  1. scheduleLaunchActivity()将从AMS中传过来的参数封装成ActivityClientRecord对象,然后将消息发送给mH,mH是一个Handler对象。
  2. H是ActivityThread的内部类,继承自Handler,它在收到LAUNCH_ACTIVITY的消息后,会调用ActivityThread.handlerLaunchActivity()。
  3. handleLaunchActivity()主要调用了两个方法:performLaunchActivity()和handleResumeActivity()。performLaunchActivity()会完成Activity的创建,以及调用Activity的onCreate()、onStart()等方法。handleResumeActivity()会完成Activity.onResume()的调用。

插件化的实现

插件化的实现有很多种方式,我下面说的方式是我认为比较简单的方式,滴滴的插件化框架貌似就是这么实现的.

假如在插件中有一个未在AndroidManifest.xml注册的TargetActivity,我们想启动它,可以分为三步。

  1. 在AndroidManifest.xml中预先注册一个我们项目中没有的Activity,例如ProxyActivity。我们把这种行为称为插桩。
  2. 在请求启动Activity阶段,我们把TargetActivity替换成AndroidManifest中预先注册的ProxyActivity。
  3. 在AMS响应阶段,Activity实例产生之前,我们再做一个完全相反的动作。即把响应信息中要启动的ProxyActivity替换回TargetActivity。

第一步十分简单,没什么好说的。要实现第二步和第三步就需要用到Activity启动流程的知识了。 在Activity启动流程中,Instrumentation无论在请求阶段还是响应阶段都扮演着重要的角色。在请求阶段Instrumentation.execStartActivity()会被调用,而在响应阶段Instrumentation.newActivity()会被调用。因此如果我们可以Hook Instrumentation,那么我们就可以在execStartActivity()和newActivity()分别完成第二步和第三步中的功能。

ActivityThread中的Instrumentation在什么时候被创建:

public static void main(String[] args) {
	//...
	ActivityThread thread = new ActivityThread();
	thread.attach(false);
	//...
}

private void attach(boolean system) {
	sCurrentActivityThread = this;
	final IActivityManager mgr = ActivityManagerNative.getDefault();
	//与AMS通信
	mgr.attachApplication(mAppThread);
}

public static ActivityThread currentActivityThread() {
	return sCurrentActivityThread;
}
  1. 在ActivityThread的main()方法中,ActivityThread会被初始化并最终把对象保存在静态的sCurrentActivityThread中。在一个app进程中只有一个ActivityThread实例sCurrentActivityThread。sCurrentActivityThread可以通过ActivityThread.currentActivityThread()拿到。
  2. attach()中,mgr.attachApplication(mAppThread)这段代码又是一个Binder双向通信的过程,它主要为创建Application对象服务。整个通信过程和Activity启动过程类似,我就不再详细介绍了。在通信的最后,ActivtiyThread.handleBindApplication()被调用,而在方法内部,Instrumentation被初始化。

总结:一个App进程,只有一个ActivityThread对象,这个对象保存在sCurrentActivityThread中,可以通过ActivityThread.currentActivityThread()获取。ActivityThread的mInstrumentation会在Application创建之前初始化。

Activity中的Instrumentation在什么时候被设置:

  1. Activtiy中的Instrumentation是通过Activity.attach()传进来的。
  2. Activity.attach()在介绍Activity启动流程时提到过。它会在ActivityThread.performLaunchActivity()中被调用。
  3. 这样ActivtyThread把自己内部的Instrumentation传递到了Activity中。

最终目的:Hook Instrumentation:

通过以上分析,我们知道,要Hook app的Instrumentation,只需要替换掉ActivityThread的Instrumentation即可。但是,Android SDK没有为我们提供任何关于ActivityThread的api。在滴滴的VirtualAPK插件化框架里重新声明了这些Android SDK没有提供的Framework层的类。这些类只有方法的声明,这样我们就可以使用这些Android SDK没有提供的类或隐藏的方法了。需要注意的一点是,AndroidStub应该只参与编译过程,这很简单,用compileOnly依赖就可以了。

  1. 接下来,通过反射替换ActivitThread的Instrumentation:
protected void hookInst rumentation() {
try {
	ActivityThread activityThread = ActivityThread.currentActivityThread();
	Instrumentation baseInstrumentation = activityThread.getInstrumentation();
	final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation);
	Reflector.with(activityThread).field("mInstrumentation").set(instrumentation);
} catch (Exception e) {
	Log.w(TAG, e);
}
}
public class VAInstrumentation extends Instrumentation {
	private Inst rumentation mBase ;
	private PluginManager mPluginManager;
	public VAInstrumentation(PluginManager pluginManager, Instrumentation base) {
		this.mPluginManager = pluginManager;
		this.mBase = base;
	}
}
  1. 上面的VAInstrumentation是对系统Instrumentation的代理类。在VAInstrumentation的内部我们可以加入任何我们想要的逻辑。在Instrumentation.execStartActivity()执行前将我们要启动的Activity替换成预注册的ProxyActivity。
public class VAInstrumentation extends Instrumentation implements Handler.Callback {
	@override
	public ActivityResult execStartActivity(
		Context who, 
		IBinder contextThread, 
		IBinder token, 
		String target, 
		Intent intent, 
		int requestCode, 
		Bundle options) (
	injectIntent(intent);
	return mBase.execStartActivity (who, contextThread, token, target, intent, requestCode, options);
}
private void injectIntent(Intent intent) {
	if(intent.getComponent() != null) {
		String targetPackageName = intent.getComponent().getPackageName();
		String targetClassName = intent.getComponent().getClassName();
		//如果启动插件中的Activity
		if (!targetPackageName.equals(mContext.getPackageName())) (
			//将Activity的原始信息存入Intent中
			intent.putExtra(Constants.KEY_IS_PLUGIN, true);
			intent.putExtra (Constants.KEY_TARGET_ PACKAGE, targetPackageName);
			intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
			//用ProxyActivity替换
			dispatchStubActivity(intent);
		}
	}
}
private void dispatchStubActivity(Intent intent) {
	String stubActivity = "com.like.virtualapk.ProxyActivity";
	intent.setClassName(mContext, stubActivity);
}
  1. 在Instrumentation.newActivity()执行前将预注册的ProxyActivity替换回我们要启动的Activity。
@override
public Activity newActivity(ClassLoader cl, String className, Intent intent) {
	try {
		cl.loadClass(className);
	} catch (ClassNotFoundException e) {
		ComponentName component = getComponent(intent);
		if ( component == null) {
			return mBase.newActivity(cl, className, intent) ;
		}
		String targetClassName = component.getClassName();
		Log. i(TAG, String. format("newActivity[%s : &s/%s]", className, component.getPackageName(), targetClassName));
		Activity activity = mBase.newActivity(cl, targetClassName, intent);
		activity.setIntent(intent);
		return activity;
	}
	return mBase.newActivity(cl, className, intent);
}
public static boolean isIntentFromPlugin(Intent intent) {
	if ( intent = null) {
		return false;
	}
	return intent.getBooleanExtra(Constants.KEY_IS_PLUGIN, false);
}
public static ComponentName getComponent(Intent intent) {
	if (intent = null) {
		return null;
	}
	if(isIntentFromPlugin(intent)) {
		return new ComponentNameintent.getStringExtra(Constants.KEY_TARGET_PACKAGE),
			intent.getStringExtra(Constants.KEY_TARGET_ACTIVITY));
	}
	return intent.getComponent() ;
}

加载插件资源:

  1. 反射调用AssetsManager的addAssetPath方法,将外部的apk路径添加进去,构建新的Resource对象。
  2. 通过DexClassLoader加载R.class,通过资源名称获取对应的id,通过上述构建的Resource和资源id获取资源对象。
/**
* 反射添加资源路径,并创建新的Resources 对象
*/
private Resources getPluginResources() {
    try {
        AssetManager assetManager = AssetManager.class.newInstance();
        //反射获取AssetManager的addAssetPath方法
        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
        //将插件包地址添加进行
        addAssetPath.invoke(assetManager, apkDir+ File.separator+apkName);
        Resources superRes = context.getResources();
        //创建Resources
        Resources mResources = new Resources(assetManager, superRes.getDisplayMetrics(),
                superRes.getConfiguration());
        return mResources;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}


/**
* 1. 先获取资源的名称对应的id(通过反射R.class文件的变量)
* 2. 再根据我们构造的Resources 获取对应的资源对象。
*/
public Drawable getApkDrawable(String drawableName){
    try {
        DexClassLoader dexClassLoader = new DexClassLoader(apkDir+File.separator+apkName,
        optimizedDirectoryFile.getPath(), null, context.getClassLoader());
 
        //通过使用apk自己的类加载器,反射出R类中相应的内部类进而获取我们需要的资源id
        Class<?> clazz = dexClassLoader.loadClass(apkPackageName + ".R$drawable");
        Field field = clazz.getDeclaredField(drawableName);
        int resId = field.getInt(R.id.class);//得到图片id
        Resources mResources = getPluginResources();
        assert mResources != null;
        return mResources.getDrawable(resId);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;
}

常见的插件化框架:

  1. 静态代理 dynamic-load-apk最早使用ProxyActivity这种静态代理技术,由ProxyActivity去控制插件中PluginActivity的生命周期
  2. 动态替换(HOOK) 在实现原理上都是趋近于选择尽量少的hook,并通过在manifest中预埋一些组件实现对四大组件的动态插件化。像Replugin。
  3. 容器化框架 VirtualApp能够完全模拟app的运行环境,能够实现app的免安装运行和双开技术。
  4. Atlas是阿里的结合组件化和热修复技术的一个app基础框架,号称是一个容器化框架。