Activity启动流程
Activity启动流程分析有很多文章了,为什么我要再写一篇,因为我觉得大部分的文章讲的都有点复杂,个人经验认为学习一种技术尽量从全局去看,否则会陷入细节而让自己对这种技术没有一个全局的概念,看了网上几篇文章,我总结归纳了一张图,图里我也加了很多注释,先看图我再补充一下细节
请求阶段:
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()会被调用。
- scheduleLaunchActivity()将从AMS中传过来的参数封装成ActivityClientRecord对象,然后将消息发送给mH,mH是一个Handler对象。
- H是ActivityThread的内部类,继承自Handler,它在收到LAUNCH_ACTIVITY的消息后,会调用ActivityThread.handlerLaunchActivity()。
- handleLaunchActivity()主要调用了两个方法:performLaunchActivity()和handleResumeActivity()。performLaunchActivity()会完成Activity的创建,以及调用Activity的onCreate()、onStart()等方法。handleResumeActivity()会完成Activity.onResume()的调用。
插件化的实现
插件化的实现有很多种方式,我下面说的方式是我认为比较简单的方式,滴滴的插件化框架貌似就是这么实现的.
假如在插件中有一个未在AndroidManifest.xml注册的TargetActivity,我们想启动它,可以分为三步。
- 在AndroidManifest.xml中预先注册一个我们项目中没有的Activity,例如ProxyActivity。我们把这种行为称为插桩。
- 在请求启动Activity阶段,我们把TargetActivity替换成AndroidManifest中预先注册的ProxyActivity。
- 在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;
}
- 在ActivityThread的main()方法中,ActivityThread会被初始化并最终把对象保存在静态的sCurrentActivityThread中。在一个app进程中只有一个ActivityThread实例sCurrentActivityThread。sCurrentActivityThread可以通过ActivityThread.currentActivityThread()拿到。
- attach()中,mgr.attachApplication(mAppThread)这段代码又是一个Binder双向通信的过程,它主要为创建Application对象服务。整个通信过程和Activity启动过程类似,我就不再详细介绍了。在通信的最后,ActivtiyThread.handleBindApplication()被调用,而在方法内部,Instrumentation被初始化。
总结:一个App进程,只有一个ActivityThread对象,这个对象保存在sCurrentActivityThread中,可以通过ActivityThread.currentActivityThread()获取。ActivityThread的mInstrumentation会在Application创建之前初始化。
Activity中的Instrumentation在什么时候被设置:
- Activtiy中的Instrumentation是通过Activity.attach()传进来的。
- Activity.attach()在介绍Activity启动流程时提到过。它会在ActivityThread.performLaunchActivity()中被调用。
- 这样ActivtyThread把自己内部的Instrumentation传递到了Activity中。
最终目的:Hook Instrumentation:
通过以上分析,我们知道,要Hook app的Instrumentation,只需要替换掉ActivityThread的Instrumentation即可。但是,Android SDK没有为我们提供任何关于ActivityThread的api。在滴滴的VirtualAPK插件化框架里重新声明了这些Android SDK没有提供的Framework层的类。这些类只有方法的声明,这样我们就可以使用这些Android SDK没有提供的类或隐藏的方法了。需要注意的一点是,AndroidStub应该只参与编译过程,这很简单,用compileOnly依赖就可以了。
- 接下来,通过反射替换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;
}
}
- 上面的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);
}
- 在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() ;
}
加载插件资源:
- 反射调用AssetsManager的addAssetPath方法,将外部的apk路径添加进去,构建新的Resource对象。
- 通过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;
}
常见的插件化框架:
- 静态代理 dynamic-load-apk最早使用ProxyActivity这种静态代理技术,由ProxyActivity去控制插件中PluginActivity的生命周期
- 动态替换(HOOK) 在实现原理上都是趋近于选择尽量少的hook,并通过在manifest中预埋一些组件实现对四大组件的动态插件化。像Replugin。
- 容器化框架 VirtualApp能够完全模拟app的运行环境,能够实现app的免安装运行和双开技术。
- Atlas是阿里的结合组件化和热修复技术的一个app基础框架,号称是一个容器化框架。