Android Dex分包原理

为什么要分包?

1、65536问题

  • 导致因素

    随着项目apk的庞大以及加入更多的第三方库,app的方法数已经超过了65536,会导致程序根本跑不起来。

  • 原因
    在生成.dex文件后由于有很多冗余的资源,所以Android中会对dex文件进行优化,Davlik模式下利用dexopt工具进行优化,而dexopt有两个问题:

    • Dexopt 会把每一个类的方法 id 检索起来,存在一个链表结构里面,但是这个链表的长度是用一个 short 类型来保存的,导致了方法 id 的数目不能够超过65536个, 当一个项目足够大的时候,显然这个方法数的上限是不够的;
    • Dexopt 使用 LinearAlloc 来存储应用的方法信息, Dalvik LinearAlloc 是一个固定大小的缓冲区。在Android 版本的历史上,LinearAlloc 分别经历了4M/5M/8M/16M限制。Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB 或16MB。当方法数量过多导致超出缓冲区大小时,也会造成dexopt崩溃;
  • ART模式下 ,采用的是dexoat工具,对应生成art虚拟执行可执行的.oat文件,这个是包含多个dex文件;

2、怎么解决这个问题

  • 在gradle中添加MultiDex支持,加载classes2.dex
multiDexEnabled true
  • 执行MultiDex.install()
@Override protected void attachBaseContext (Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
}

分包导致的问题

  1. API14 之前的不能支持分包 Dalvik linearalloc bug

  2. 在冷启动时因为需要安装dex文件,如果dex文件过大时,处理时间过长,很容易引发ANR(Application Not Responding);

  3. 采用MultiDex方案的应用因为需要申请一个很大的内存,在运行时可能导致程序的崩溃,这个主要是因为Dalvik linearAlloc 的一个限制,这个限制在 Android 4.0 (API level 14)已经增加了, 应用也有可能在低于 Android 5.0 (API level 21)版本的机器上触发这个限制;

  4. 分包后,不同依赖项目间的dex文件函数相互调用,报错找不到方法

Android系统对分包的影响

  • Android 5.0以下:
    运行在Davlik虚拟机上,优化使用dexopt工具并分包,每次运行先加载主包,然后反射子包,存在主包子包的先后问题;

  • Android 5.0以上:
    运行在ART虚拟机上,优化使用dexoat工具,生成多个包含dex文件的.oat文件,.oat文件是混合了主包子包,已经在APK安装时生成,故程序运行起来不存在主包子包的加载先后问题;

MultiDex的基本原理

通过DexFile来加载Secondary DEX,并存放在BaseDexClassLoaderDexPathList中。

解决分包导致调用找不到对应类

1、微信加载方案

首次加载在地球中页中, 并用线程去加载(但是 5.0 之前加载 dex 时还是会挂起主线程一段时间(不是全程都挂起))。

  • dex 形式
    微信是将包放在 assets 目录下的,在加载 Dex 的代码时,实际上传进去的是 zip,在加载前需要验证 MD5,确保所加载的 Dex 没有被篡改。

  • dex 类分包规则
    分包规则即将所有 Application、ContentProvider 以及所有 export 的 Activity、Service 、Receiver 的间接依赖集都必须放在 主 dex

  • 加载 dex 的方式
    加载逻辑这边主要判断是否已经 dexopt,若已经 dexopt,即放在 attachBaseContext 加载,反之放于地球中用线程加载。怎么判断?因为在微信中,若判断 revision 改变,即将 dex 以及 dexopt 目录清空。只需简单判断两个目录 dex 名称、数量是否与配置文件的一致。

总的来说,这种方案用户体验较好,缺点在于太过复杂,每次都需重新扫描依赖集,而且使用的是比较大的间接依赖集。

2、 Facebook 加载方案

Facebook的思路是将 MultiDex.install() 操作放在另外一个经常进行的。

  • dex 形式
    与微信相同。

  • dex 类分包规则
    Facebook 将加载 dex 的逻辑单独放于一个单独的 nodex 进程中。

  <activity 
    android:exported="false"
    android:process=":nodex"
     android:name="com.facebook.nodex.startup.splashscreen.NodexSplashActivity">

所有的依赖集为 Application、NodexSplashActivity 的间接依赖集即可。

  • 加载 dex 的方式
    因为 NodexSplashActivity 的 intent-filter 指定为 MainLAUNCHER ,所以一打开 App 首先拉起 nodex 进程,然后打开 NodexSplashActivity 进行 MultiDex.install() 。如果已经进行了 dexpot 操作的话就直接跳转主界面,没有的话就等待 dexpot 操作完成再跳转主界面。

这种方式好处在于依赖集非常简单,同时首次加载 dex 时也不会卡死。但是它的缺点也很明显,即每次启动主进程时,都需先启动 nodex 进程。尽管 nodex 进程逻辑非常简单,这也需100ms以上。

3、美团加载方案

  • dex 形式
    在 gradle 生成 dex 文件的这步中,自定义一个 task 来干预 dex 的生产过程,从而产生多个 dex 。

    tasks.whenTaskAdded { task ->
     if (task.name.startsWith('proguard') && (task.name.endsWith('Debug') || task.name.endsWith('Release'))) {
     task.doLast {
     makeDexFileAfterProguardJar();
     }
     task.doFirst {
     delete "${project.buildDir}/intermediates/classes-proguard";

     String flavor = task.name.substring('proguard'.length(), task.name.lastIndexOf(task.name.endsWith('Debug') ? "Debug" : "Release"));
     generateMainIndexKeepList(flavor.toLowerCase());
     }
     } else if (task.name.startsWith('zipalign') && (task.name.endsWith('Debug') || task.name.endsWith('Release'))) {
     task.doFirst {
     ensureMultiDexInApk();
     }
     }
    }
  • dex 类分包规则

    把 Service、Receiver、Provider 涉及到的代码都放到主 dex 中,而把 Activity 涉及到的代码进行了一定的拆分,把首页 Activity、Laucher Activity 、欢迎页的 Activity 、城市列表页 Activity 等所依赖的 class 放到了主 dex 中,把二级、三级页面的 Activity 以及业务频道的代码放到了第二个 dex 中,为了减少人工分析 class 的依赖所带了的不可维护性和高风险性,美团编写了一个能够自动分析 class 依赖的脚本, 从而能够保证�主 dex 包含 class 以及他们所依赖的所有 class 都在其内,这样这个脚本就会在打包之前自动分析出启动到主 dex 所涉及的所有代码,保证主 dex 运行正常。

  • 加载 dex 的方式
    通过分析 Activity 的启动过程,发现 Activity 是由 ActivityThread 通过 Instrumentation 来启动的,那么是否可以在 Instrumentation 中做一定的手脚呢?通过分析代码 ActivityThread 和 Instrumentation 发现,Instrumentation 有关 Activity 启动相关的方法大概有:execStartActivity、 newActivity 等等,这样就可以在这些方法中添加代码逻辑进行判断这个 class 是否加载了,如果加载则直接启动这个 Activity,如果没有加载完成则启动一个等待的 Activity 显示给用户,然后在这个 Activity 中等待后台第二个 dex 加载完成,完成后自动跳转到用户实际要跳转的 Activity;这样在代码充分解耦合,以及每个业务代码能够做到颗粒化的前提下,就做到第二个 dex 的按需加载了。

美团的这种方式对主 dex 的要求非常高,因为第二个 dex 是等到需要的时候再去加载。重写Instrumentation 的 execStartActivity 方法,hook 跳转 Activity 的总入口做判断,如果当前第二个 dex 还没有加载完成,就弹一个 loading Activity等待加载完成。

最后,希望此篇博客对大家有所帮助,欢迎提出问题及建议共同探讨,如有兴趣可以关注我的博客,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • 前言 最近开发中我们发现,我们的产品在Android设备版本低于5.0以下第一次安装启动会出现黑屏、ANR等情况。...
    miraclehen阅读 3,507评论 2 11
  • 为什么需要对Dex进行分包 Android在安装应用的过程中,系统会运行一个名为DexOpt的程序为该应用在当前机...
    Boreas_su阅读 4,258评论 0 9
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,359评论 25 707
  • 要意识到 泛泛之交的关系是很脆弱的 千万别越界
    ziziseven阅读 103评论 0 0
  • 一个喜欢拍照,喜欢独自行走的彷徨青年,以下是这一年拍的照片的略影,大部分都保存在电脑上,上传麻烦就简单找了几张手机...
    1988年的冬季阅读 865评论 13 13