XDRush

ActivityThread & Instrumentation在MultiDex实现中的应用

背景说明

这篇文章可以看作是对MultiDex实践的一篇补充,更进一步的背景是对美团MultiDex实现的一些具体细节的一种实现方式。

同美团团队一样,在实现MultiDex时,同样遇到一个问题,那就是:部分在classes2.dex中的二级界面,首次启动时,如果classes2.dex尚未加载完,而这时用户操作了该二级界面,Crash!!是必然的!美团的那篇文章也讨论了这个问题,解决方法就是在操作二级界面Activity时,首先判断classes2.dex是否加载完毕,如果加载完毕,则直接启动该Activity,如果尚未加载完毕,则引入一个WaitActivity,在WaitActivity中阻塞等待classes2.dex加载完成,等dex2加载完毕之后再启动该二级界面Activity。

这么做肯定是没有问题的,但有一点就是,如果二级界面太多,每个调用的地方都去判断dex2是否加载完未免太麻烦,好在Framework告诉了我们答案!我们知道Activity是由ActivityThread通过Instrumentation来启动的,再进一步跟进去,我们发现ActivityThread中有一个mInstrumentation对象,该对象即是Instrumentation,进一步查看Instrumentation源码,发现其与Activity启动相关的方法有以下几个:execStartActivity, newActivity等等,于是,我们就可以在这里面做些手脚!怎么做?修改Instrumentation或者ActivityThread也未免显得太过幼稚了!那么How?

方法其实很简单:(1)自定义MyInstrumentation继承自Instrumentation;(2)通过放射方式在应用刚启动时以MyInstrumentation替换掉ActivityThread中的mInstrumentation,替换的时机最好实在Application.attachBaseContext()中。

自定义Instrumentation

首先自定义MyInstrumentation继承自Instrumentation,在newActivity中作判断该Activity是否加载完,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class MyInstrumentatin extends Instrumentation {
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Log.d(TAG, className);
if (isActivityInDex2() && !isDex2Installed) {
className = "com.myapp.WaitActivity";
}
return (Activity)cl.loadClass(className).newInstance();
}
}

有了这关键的一步,WaitActivity的实现细节这里就不作多述,有兴趣的可以私我!

注册MyInstrumentation

实现了MyInstrumentation,剩下的就是以MyInstrumentation替换掉系统的Instrumentation,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
registerMyInstrumentation();
}
}
/**
* 反射方式替换掉系统Instrumentation.
*/
public static void registerMyInstrumentation() {
try {
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field instrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
instrumentationField.setAccessible(true);
MyInstrumentation myInstrumentation = new MyInstrumentation();
Method getActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object object = getActivityThread.invoke(null);
instrumentationField.set(object, myInstrumentation);
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}

至此,比较完备的MultiDex实现方式算是告一段落了,整个过程,给我最大的感受就是:Read the Fucking Source Code是多么重要!RTFSC过程中,不仅能学到别人代码的组织形式,更重要的是能够从根本上为解决某些问题提供思路!