前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MultiDex(一)之源码解析

MultiDex(一)之源码解析

作者头像
用户2898788
发布2018-08-21 10:10:40
7940
发布2018-08-21 10:10:40
举报
文章被收录于专栏:双十二技术哥

一、初识MultiDex

开发Android应用的小伙伴,在经历了众多版本迭代、PM不断加入新功能、尝试新技术引入类库之后,产物Apk急剧膨胀;最终会遇到那个传说中的Android64K方法数问题;具体表现:

Conversion to Dalvik format failed:Unable to execute dex: method ID not in [0, 0xffff]: 65536

编译打包失败,不能愉快的开发了。

超过最大方法数限制的问题,是由于Dex文件格式限制,一个Dex文件中method个数采用使用原生类型short来索引文件中的方法,4个字节共计最多表达65536个method,field/class的个数也均有此限制。生成Dex文件的过程,是将工程所需全部class文件合并且压缩到一个Dex文件期间,也就是Android打包的Dex过程中, 单个Dex文件可被引用的方法总数(自己开发的代码以及所引用的Android框架、类库的代码)被限制为65536;

但是这种小问题怎么能难倒程序猿哥哥呢,相信大家或多或少都听说过Multidex:Google官方对64K方法数问题的一种补救措施,通俗的讲就是:既然你的代码这么多,一个Dex装不下,那就给你多个Dex来装呗。Multidex在构建打包阶段将Class拆分到多个Dex,使之不超过单Dex最大方法数的限制;这样打包就不会失败了。

但是只解决分Dex打包的问题还不够,我们知道Dalvik虚拟机应用启动时默认只会装载classes.dex,那ClassLoader肯定是无法从别的Dex中查找Class的,从而程序运行过程中的各种ClassNotFoundException画面太美简直不敢想象。于是机智如Google又赋予MultiDex另外一项能力:在运行时动态装载别的非主Dex,于是乎一个看似完美的分Dex加载方案就诞生了。

具体的使用指南可以异步官方文档;

二、Multidex工作流程

在分析源码之前,我们先来看一下MultiDex的工作流程,对它有一个初步的认识;

总结:

  1. 运行时提取别的非主Dex出来,然后动态装载执行。
  2. 需要在源码分析过程中重点关注提取Dex以及动态装载这两个过程。

三、源码分析

MultiDex入口:MultiDex.install();

总结:

  1. 进行各种预校验以及获取需要的信息;
  2. 重要方法:MultiDexExtractor.load(context, e, dexDir, false),将Dex文件提取出来;
  3. 重要方法:installSecondaryDexes(loader, dexDir, files),安装提取出来的Dex文件。

重要方法:提取器MultiDexExtractor.load

总结:

  1. 制性提取或者源文件发生变化则重新提取,否则直接使用缓存dex文件;

重要方法:performExtractions()

总结:

  1. 准备Dex缓存的目录,并且删除其中不是以name.apk.classes开头的文件;
  2. 每个Dex的提取最多尝试三次;

重要方法:真实提取extract(apk, dexFile, extractedFile, extractedFilePrefix);

总结:

  1. 将Apk源文件进行解压,将其中的非主Dex文件提取为zip文件。

终于将非主Dex文件提取出来了,接下来就是令人激动的安装过程了。

分析SDK19以上的为例:

总结:

  1. 反射获取ClassLoader中的pathList字段;
  2. 反射调用DexPathList对象中的makeDexElements方法,将刚刚提取出来的zip文件包装成Element对象;
  3. 将包装成的Element对象扩展到DexPathList中的dexElements数组字段里;
  4. makeDexElements中有dexopt的操作,是一个耗时的过程,产物是一个优化过的odex文件。

至此:提取出来的dex文件也被加到了ClassLoader里,而那些Class也就可以被ClassLoader所找到并使用。

跟随源码一步步揭开了Multidex的神秘面纱,再回头看Multidex的工作流程图,就更加清晰明了。

四、问题

1、clearOldDexDir(context)是干嘛的?每一次都清除上一次提取并缓存的Dex?

No,如果只看multidex-1.0.1的代码,clearOldDexDir其实什么事情都没干,因为清除的是data/data/packageName/files/secondary-dexes文件夹下的文件,但是这个文件夹从始至终都没有被使用过。看最新MultiDex库文件Master分支的代码:获取缓存Dex目录的时候出现过,如果正常缓存目录创建失败,则data/data/packageName/files/secondary-dexes作为临时缓存目录。

发布的multidex-1.0.1其实不会出现这个目录,而且这段的逻辑也不严谨,如果临时目录也创建失败了呢?

2、动态装载Dex的过程为什么反射那些字段、方法就可以了?

这就涉及到Android中的Class加载机制了,ClassLoader加载Class调用的是BaseDexClassLoader的findClass方法,其中会对dexElements数组进行遍历,数组每一个元素对应了一个DexFile,真正的加载是在DexFile实现。而正是因为这个数组,使我们有机会将Dex包装成的Element对象扩展到其中。这样ClassLoader加载Class的时候就也会遍历调用到加进来的Dex,从而找到需要的Class。

3、为什么上面写这是一个看似完美的分Dex加载方案?

①INSTALL_FAILED_DEXOPT;在部分机型会出现无法安装的问题没有解决。

这是由于dexopt的LinearAlloc限制引起的,在Android版本不同分别经历了4M/5M/8M/16M限制,4.2.x系统上可能都已到16M, 在Gingerbread或者以下系统LinearAllocHdr分配空间只有5M大小的, 高于Gingerbread的系统提升到了8M。Dalvik linearAlloc是一个固定大小的缓冲区。在应用的安装过程中,系统会运行一个名为dexopt的程序为该应用在当前机型中运行做准备。dexopt使用LinearAlloc来存储应用的方法信息。Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB或16MB。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃。

也就是说,即便是方法数不超标,也不能保证一定能安装成功,因为DexOpt过程可能因为LinearAlloc的限制而失败。但是这个问题为什么之前没有提出呢?因为这个问题对目前的Android市场机型基本不存在,现在一般Android应用的最低兼容版本都是4.0,最可能出现这个问题的2.3之前的版本都不在考虑之列,而且目前5.0以上的机型占有率已经接近70%,低版4.0本机型已经越来越少,而且基本是4.0机型,也只是有可能触发这个限制,因此对目前来讲是个不是问题的问题。

②ANR的问题:从以上MultiDex的工作流程可以看到:MultiDex工作在主线程,而Dex的提取与DexOpt的过程都是耗时的操作,所以ANR的问题是必然存在;而且业务量越大,拆分出来的Dex越多,对应ANR的几率也就越高。

五、结语

既然ANR的问题这么严重,那MultiDex的方案还可以被用到实际场景吗?那必须,不给程序猿哥哥制造挑战的方案绝对不是好方案,经过优化之后依然可以是好同志嘛!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-12-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 双十二技术哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结:
  • 总结:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档