概述
MultiDex适用于API版本在4-20的Android系统 , 即Android 2.1 - 4.4 . 而在这些版本之间 , MultiDex会通过Application.getClassLoader进行加载. 而如果Dex比较多比较大的话 , 主线程加载Dex时间会很长 , 导致主线程ANR.
由于Android 5.0之后使用ART虚拟机进行dex2oat , 将多dex在安装的时候将APK中多个Dex进行优化 , 优化过后生成一个ELF文件 , 名为.oat文件. 在加载后 , 会将oat文件直接映射到ART虚拟机中使用 , 这样就减少Dex加载的耗时..
MultiDex加载过程简述
- 读取APK的CRC32以及modifyTime进行校验
- 通过反射 , 从BaseDexClassLoader中找到pathList对象
- 通过反射调用PathList.makeDexElements创建Elements[]
- 通过反射将Elements[]添加到dexElements数组中
- 后续在该ClassLoader查找类到时候 , 会优先在dexElements中开始遍历查找
MultiDex加载过程
MultiDex
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static void install(Context context) { if (IS_VM_MULTIDEX_CAPABLE) { //VM版本大于2.1时,IS_VM_MULTIDEX_CAPABLE为true,这时候MultiDex.install什么也不用做,直接返回。因为大于2.1的VM会在安装应用的时候,就把多个dex合并到一块 } else if (VERSION.SDK_INT < 4) { //Multi dex最小支持的SDK版本为4 throw new RuntimeException("Multi dex installation failed. SDK " + VERSION.SDK_INT + " is unsupported. Min SDK version is " + 4 + "."); } else { // 这里是重点逻辑 File dexDir = new File(e.dataDir, "code_cache/secondary-dexes"); //1. 把dex文件缓存到/data/data/<packagename>/code_cache/secondary-dexes/目录 List files = MultiDexExtractor.load(context, e, dexDir, false); //2. 安装所有的附属dex installSecondaryDexes(loader, dexDir, files); } }
|
执行文件拷贝工作
1 2 3 4 5 6 7 8 9
| MultiDexExtractor.load(){ try { //从缓存目录中直接查找缓存文件,跳过解压 files = loadExistingExtractions(context, sourceApk, dexDir); } catch (IOException var9) { files = performExtractions(sourceApk, dexDir); putStoredApkInfo(context, getTimeStamp(sourceApk), currentCrc, files.size() + 1); } }
|
获取到所有的dex文件
1 2 3 4 5 6 7 8 9
| MultiDexExtractor.performExtractions() { for (ZipEntry dexFile = apk.getEntry("classes" + e + ".dex"); dexFile != null; dexFile = apk.getEntry("classes" + e + ".dex")) { String fileName = extractedFilePrefix + e + ".zip"; File extractedFile = new File(dexDir, fileName); files.add(extractedFile); }
}
|
1 2 3 4 5 6 7 8 9 10 11
| MultiDex.installSecondaryDexes() { if (!files.isEmpty()) { if (VERSION.SDK_INT >= 19) { MultiDex.V19.install(loader, files, dexDir); } else if (VERSION.SDK_INT >= 14) { MultiDex.V14.install(loader, files, dexDir); } else { MultiDex.V4.install(loader, files); } } }
|
1 2 3 4 5 6 7
| MultiDex.V14.install(loader, files, dexDir){
Field pathListField = MultiDex.findField(loader, "pathList"); Object dexPathList = pathListField.get(loader); MultiDex.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries), optimizedDirectory)); }
|
1 2 3 4 5 6 7
| private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements", new Class[]{ArrayList.class, File.class}); return (Object[]) ((Object[]) makeDexElements.invoke(dexPathList, new Object[]{files, optimizedDirectory})); }
|
1 2 3 4 5 6 7 8 9 10 11
| private static void expandFieldArray(Object instance, String fieldName, Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field jlrField = findField(instance, fieldName); Object[] original = (Object[]) ((Object[]) jlrField.get(instance)); Object[] combined = (Object[]) ((Object[]) Array.newInstance(original.getClass() .getComponentType(), original.length + extraElements.length)); System.arraycopy(original, 0, combined, 0, original.length); System.arraycopy(extraElements, 0, combined, original.length, extraElements.length); jlrField.set(instance, combined); }
|
Multidex的缺陷
在冷启动时因为需要安装DEX文件,如果DEX文件过大时,处理时间过长,很容易引发ANR
解决方案
- 第一次启动的时候,检测到未曾加载过second dex,那么启动欢迎页面(启动新的进程,原来进程进入阻塞等待,注意,此时不会发生ANR,因为已经不是前台进程了),在欢迎页面里面进行second dex的加载,加载完成后通知主线程继续
- 设定单个dex文件最大方法数为48000(经验值)而不是65536,避免内存问题
- 控制程序逻辑,未曾加载完second dex之前,进入阻塞等待,直到加载完程序才往下走
1 2 3
| Application.attachBaseContext(Context context) { loadMultiDex(context) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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 MultiDex.install(context); }
|
1 2 3 4
| LoadMultiDexActivity.onCreate() { new thread().run{loadMultiDex();}
}
|
1 2 3 4 5 6 7 8 9 10 11 12
| loadMultiDex(){ //执行加载 MultiDex.install(LoadMultiDexActivity.this); //加载结束后,删除临时文件,application就可以继续执行了 deleteTempFile(this);
//将这个进程杀死 Log.d(TAG, "aftetMultiDex: "); finish(); Process.killProcess(Process.myPid()); }
|
抖音BoostMultiDex优化实践