本文最后更新于 872 天前,其中的信息可能已经有所发展或是发生改变。
1. 思路
- 将MultiDex加载多dex合并的耗时操作放在一个新的进程进行加载。新建进程创建一个临时文件tempFile,加载完成后删除这个文件并结束当前新建进程。主进程轮询判断tempFile是否存在-不存在说明加载完成,进行后续的步骤。
-
MultiDex 会将apk转为zip,并遍历zip获取每个dex 并也转为zip (根据DexPathList源码 文件后缀判断,这些转换并无必要。)
private static final class V19 {
private V19() {
}
static void install(ClassLoader loader, List<? extends File> additionalClassPathEntries, File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
//1 反射ClassLoader 的 pathList 字段
Field pathListField = MultiDex.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList();
// 2 扩展数组 就是将dex2 dex3一起加到dexElements数组内,Tinker热修复原理就是这个
MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory, suppressedExceptions));
...
}
private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class, ArrayList.class);
return (Object[])((Object[])makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions));
}
}
- BaseDexClassLoader内的成员变量DexPathList
-
DexPathList内的 Element[] dexElements;
- DexPathList
2. Application
对象第一次创建的时候,java虚拟机首先检查类对应的Class 对象是否已经加载。如果没有加载,jvm会根据类名查找.class文件,将其Class对象载入。同一个类第二次new的时候就不需要加载类对象,而是直接实例化,创建时间就缩短了。
public class MyApplication extends Application {
private static final String TAG = "lxb-MyApplication";
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Log.d(TAG, "attachBaseContext-getPackageName: " + base.getPackageName());
Log.d(TAG, "attachBaseContext-getProcessName: " + SystemUtil.getProcessName(base));
boolean isMainProcess = isMainProcess(base);
Log.d(TAG, "attachBaseContext-isMainProcess: " + isMainProcess);
//主进程并且vm不支持多dex的情况下才使用 MultiDex
if (isMainProcess && !SystemUtil.isVMMultidexCapable()){
loadMultiDex(base);
}
}
private boolean isMainProcess(Context context) {
return context.getPackageName().equals(SystemUtil.getProcessName(context));
}
@Override
public void onCreate() {
super.onCreate();
if (!isMainProcess(this)){
Log.d(TAG, "onCreate: 非主进程,return");
return;
}
Log.d(TAG, "主进程 onCreate: 一些初始化操作");
}
private void loadMultiDex(Context context) {
newTempFile(context); //创建临时文件
//启动另一个进程去加载MultiDex
Intent intent = new Intent(context, LoadMultiDexActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
//检查MultiDex是否安装完(安装完会删除临时文件)
checkUntilLoadDexSuccess(context);
//另一个进程以及加载 MultiDex,有缓存了,所以主进程再加载就很快了。
//为什么主进程要再加载,因为每个进程都有一个ClassLoader
long startTime = System.currentTimeMillis();
MultiDex.install(context);
Log.d(TAG, "第二次 MultiDex.install 结束,耗时: " + (System.currentTimeMillis() - startTime));
// 优化点-提前new Activity,后续加载就很快了,涉及到类加载机制
preNewActivity();
}
private void preNewActivity() {
long startTime = System.currentTimeMillis();
MainActivity mainActivity = new MainActivity();
Log.d(TAG, "preNewActivity 耗时: " + (System.currentTimeMillis() - startTime));
}
//创建一个临时文件,MultiDex install 成功后删除
private void newTempFile(Context context) {
try {
File file = new File(context.getCacheDir().getAbsolutePath(), "load_dex.tmp");
if (!file.exists()) {
Log.d(TAG, "newTempFile: ");
file.createNewFile();
}
} catch (Throwable th) {
th.printStackTrace();
}
}
/**
* 检查MultiDex是否安装完,通过判断临时文件是否被删除
* @param context
* @return
*/
private void checkUntilLoadDexSuccess(Context context) {
File file = new File(context.getCacheDir().getAbsolutePath(), "load_dex.tmp");
int i = 0;
int waitTime = 100; //睡眠时间
try {
Log.d(TAG, "checkUntilLoadDexSuccess: >>> ");
while (file.exists()) {
Thread.sleep(waitTime);
Log.d(TAG, "checkUntilLoadDexSuccess: sleep count = " + ++i);
if (i > 40) {
Log.d(TAG, "checkUntilLoadDexSuccess: 超时,等待时间: " + (waitTime * i));
break;
}
}
Log.d(TAG, "checkUntilLoadDexSuccess: 轮循结束,等待时间 " +(waitTime * i));
}catch (Exception e){
e.printStackTrace();
}
}
}
3. 新进程Activity
<activity android:name="com.lanshifu.launchtest.LoadMultiDexActivity"
android:process=":multi_dex"
android:launchMode="singleTask"
android:excludeFromRecents="true"
></activity>
public class LoadMultiDexActivity extends Activity {
private static final String TAG = "lxb-LoadMultiDexActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_load_multi_dex);
Thread thread = new Thread() {
@Override
public void run() {
loadMultiDex();
}
};
thread.setName("multi_dex");
thread.start();
showLoadingDialog();
}
private void loadMultiDex(){
Log.d(TAG, "MultiDex.install 开始: ");
long startTime = System.currentTimeMillis();
MultiDex.install(LoadMultiDexActivity.this);
Log.d(TAG, "MultiDex.install 结束,耗时: " + (System.currentTimeMillis() - startTime));
try {
//模拟MultiDex耗时很久的情况
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
aftetMultiDex();
}
private void aftetMultiDex() {
deleteTempFile(this);
//将这个进程杀死
Log.d(TAG, "aftetMultiDex: ");
finish();
Process.killProcess(Process.myPid());
}
private void deleteTempFile(Context context) {
try {
File file = new File(context.getCacheDir().getAbsolutePath(), "load_dex.tmp");
if (file.exists()) {
file.delete();
Log.d(TAG, "deleteTempFile: ");
}
} catch (Throwable th) {
th.printStackTrace();
}
}
private void showLoadingDialog(){
new AlertDialog.Builder(this)
.setMessage("加载中,请稍后...")
.show();
}
}
4. Utils
public class SystemUtil {
public static String getProcessName(Context context){
ActivityManager activityManager = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager
.getRunningAppProcesses();
int myPid = Process.myPid();
if(appProcesses == null || appProcesses.size() == 0){
return null;
}
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.processName.equals(context.getPackageName())) {
if (appProcess.pid == myPid){
return appProcess.processName;
}
}
}
return null;
}
public static boolean isVMMultidexCapable(){
return isVMMultidexCapable(System.getProperty("java.vm.version"));
}
//MultiDex 拷出来的的方法,判断VM是否支持多dex
public static boolean isVMMultidexCapable(String versionString) {
boolean isMultidexCapable = false;
if (versionString != null) {
Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
if (matcher.matches()) {
try {
int major = Integer.parseInt(matcher.group(1));
int minor = Integer.parseInt(matcher.group(2));
isMultidexCapable = major > 2 || major == 2 && minor >= 1;
} catch (NumberFormatException var5) {
}
}
}
Log.i("MultiDex", "VM with version " + versionString + (isMultidexCapable ? " has multidex support" : " does not have multidex support"));
return isMultidexCapable;
}
}