ndroid-Easy-MultiDex

项目地址:TangXiaoLv/Android-Easy-MultiDex
简介:Android 傻瓜式 MultiDex 插件,从此再也不用担心方法数问题!

注 1:不想看前半部分的话可以直接跳过到最下面配置部分。
注 2:本插件是基于DexKnifePlugin 1.5.6优化改造而来,感谢 ceabie 的无私奉献。

填坑之路

坑 1:65536 ,So easy!

原因:Dalvik 的 invoke-kind 指令集中,method reference index 只留了 16 bits,最多能引用 65535 个方法。
参考=>由 Android 65K 方法数限制引发的思考.

解决:

dependencies { compile 'com.android.support:MultiDex:1.0.1'
}

继承 Application ,重写 attachBaseContext(Context)

@Override
protected void attachBaseContext(Context base) {super.attachBaseContext(base);MultiDex.install(this);
}

坑 2:Too many classes in –main-dex-list ,what?

原因:通过上面的官方分包,已经把原 Dex 分为 1 主 Dex 加多从 Dex。主 Dex 包含所有 4 大组件,Application,Annotation,multidex 等及其必要的直接依赖。由于我们方法数已达到 16W 之巨,上百个 Activity 全部塞进主 Dex,又成功的把主 Dex 撑爆了。

解决: gradle

afterEvaluate { tasks.matching { it.name.startsWith('dex') }.each { dx -> if (dx.additionalParameters == null) { dx.additionalParameters = []}  dx.additionalParameters += '--set-max-idx-number=48000' }
}

参考=>Android Dex 分包之旅

坑 3:gradle 1.5.0 之后不支持这种写法 ,what the fuck?

原因:官方解释 Gralde1.5.0以上已经将(jacoco, progard, multi-dex)统一移到Transform API里,然而 Transform API 并没有想象的那么简单好用,翻遍 Google 终于找到一个兼容 Gradle 1.5.0以上的分包插件DexKnifePlugin。
扩展=>这篇Android 热修复使用 Gradle Plugin1.5 改造 Nuwa 插件比较好的介绍了 Transform API 的使用。

坑 4:NoClassDefFoundError ,are you kiding me?

原因:通过插件手动指定 main dex 中要保留的类,虽然分包成功,但是 main dex 中的类及其直接引用类很难通过手动的方式指定。

解决方式:
美团 Android DEX 自动拆包及动态加载简介,他们是通过编写了一个能够自动分析 Class 依赖的脚本去算出主 Dex 需要包含的所有必要依赖。看来写脚本是跑不掉了。

坑 5:自定义脚本 ,read the fuck source!

问题一:哪些类是需要放入主 Dex 中?
查看 sdk\build-tools\platform-version\mainDexClasses.rules 发现放入主 Dex 相关类有 Instrumentation,Application,Activity,Service,ContentProvider,BroadcastReceiver,BackupAgent 的所有子类。

问题二:gradle 是在哪里算出主 Dex 依赖?
查看 Gradle 编译任务发现有如下 3 个编译任务:

运行 collect 任务,发现会在 build/multi-dex 目录下单独生成manifest_keep.txt文件,该文件其实就是通过上述规则扫描AndroidManifest生成。manifest_keep.txt保留的是所有需要放入主 Dex 里的类。还没完,接下来transformClassesWithMultidexlist任务会根据manifest_keep.txt生成必要依赖列表maindexlist.txt,这里面所有类才是真正放入主 Dex 里的。bingo,现在非常清楚,我们只需要控制进入 manifest_keep.txt 中的类即可,最终其类的依赖关系由系统帮我们生成即可,安全绿色可靠!


问题三:在哪里控制maindexlist.txt的大小?
由问题一我们知道生成manifest_keep.txt的规则,对于绝大部分工程来说,manifest_keep.txt中 80%是 Activity,其实我们并不需要把全部的 Activity 放入主 Dex,只需要保留必要的 Activity 即可,如首页 Activity、Laucher Activity 、欢迎页的 Activity 等启动时必要的 Activity 就 OK 了。

下图是 Gradle 的工作流程: 
来源:深入理解 Android 之 Gradle

我们只需要在完成任务向量图之后,执行任务之前 Hook 一下 collect 任务,过滤掉不必要的 activity 就 OK 了。添加 Gradle:

//需要加入主 dex 的 Activity 列表
def mainDexListActivity = ['WelcomeActivity', 'MainFunctionActivity']
afterEvaluate {project.tasks.each { task ->if (task.name.startsWith('collect') && task.name.endsWith('MultiDexComponents')) {println "main-dex-filter: found task $task.name"task.filter { name, attrs ->String componentName = attrs.get('android:name')if ('activity'.equals(name)) {def result = mainDexListActivity.find {componentName.endsWith("${it}")}return result != null} else {return true}}}}
}

坑 6:主 dex 依然爆表,shit again!

其实上面那段脚本已经成功筛选出我们想要放入主 Dex 的manifest_keep 列表maindexlist 列表,但是在打包的时候还是把所有类打进主 Dex(已无语)。这个时候就需要跟DexKnifePlugin插件配合使用,首先在 gradle 中加上上述脚本,然后使用插件时在配置文件中加上 -split **.**#-donot-use-suggest。DexKnifePlugin 插件运行原理很简单,在生成 Dex 任务之前首先读取自己的配置文件(包含前面我们通过 Gradle 脚本生成的maindexlist列表),然后扫描 combined.jar(包含工程中所有.class 文件)匹配出我们自定义的 maindexlist.txt,再替换掉 build/multi-dex/maindexlist.txt,和 build 实例。这样分包的时候就会基于我们的规则生成主 Dex。

坑 7:ANR,HAHAHA!

我们最低 API=16,测试并未发现 ANR 问题,所以暂时没考虑景上添花,这个问题比较好解决。
参考=>Android Dex 分包之旅

Congratulation

恭喜,填坑终于结束,不过还有点不爽的是需要同时维护 Gradle 脚本和插件的配置。 于是乎就将 Gradle 脚本整合进了插件,这样只需维护一个配置文件就行了。读者可以根据自己需求自行选择分开配置还是整合配置。通过这种方式我们把主 Dex 的方法数维持在 15000 左右,从此再也不用担心方法数问题了!!!

配置部分

第一步:添加根目录 Gradle

buildscript {dependencies {classpath 'com.library.tangxiaolv:dexknife-plus:1.0.1'}
}

第二步:在你的 App 模块的 build.gradle 添加插件

apply plugin: 'dexknifePlus'

第三步:配置参数

dexKnife{//必选参数enabled true //if false,禁用分包插件//可选参数//1.如果没有可选参数,将根据 enabled 决定是否分包。//2.如果有可选参数,需满足必选参数和可选参数的条件才允许分包productFlavor 'mock'buildType 'debug'/**eg:当前 productFlavors = dev,buildType = debug,*参数组合 1:enabled = true,productFlavor = dev,buildType = debug 分包*参数组合 2:enabled = true,productFlavor = mock,buildType = debug 不分包*参数组合 1:enabled = true,buildType = debug 所有 buildType = debug 分包*参数组合 1:enabled = true,productFlavor = dev 所有 productFlavor = dev 分包* */
}

第四步:在你的 App 模块目录下新建 dexknife.txt,并自定义配置

#为注释符#-----------主 Dex 中必要依赖的脚本配置-----------
#默认保留四大组件中其他三大组件(并计算其依赖树),Activity 组件选择性保留(使用-just activity 选项),若为空不保留任何 Activity
-just activity com.ceabie.demo.MainActivity#-----------附加类-----------
# 如果你想要某个包路径在 maindex 中,则使用 -keep 选项,即使他已经在分包的路径中.若为空,不保留任意类
#-keep com.ceabie.demo.**# 保留单个类.
#-keep android.support.v7.app.AppCompatDialogFragment.class# 这条配置可以指定这个包下类在第二及其他 dex 中.
#-split android.support.v?.**
#将全部类移出主 Dex
-split **.**# 不包含 Android gradle 插件自动生成的 miandex 列表.(不使用建议的依赖树,注释掉表示使用,否则-just activity 无效)
#-donot-use-suggest# (分割每个 dex 包的方法数上限) 扩展参数:例如 --set-max-idx-number=50000
# 如果出现 DexException: Too many classes in --main-dex-list, main dex capacity exceeded:
# 表明限制的方法数小于 main dex 的必要方法数,调大到合适数值即可
-dex-param --set-max-idx-number=4000# 不进行 dex 分包, 直到 dex 的 id 数量超过 65536.(设置自动执行分包策略)
#-auto-maindex# 显示 miandex 的日志.
#-log-mainlist

第五步:在 defaultConfig 或者 buildTypes 中打开 multiDexEnabled true,否则不起作用

已知错误

注:分包的时候如果发现一些莫名的错误,可以关掉 instant run,一般都能解决

错误 1: (已修复)

Error:Execution failed for task ':Toon:transformClassesWithDexForDebug'.> java.lang.NullPointerException (no error message)

发生此错误只要切换一次 Gradle 版本就 OK 了,比如 1.5.0

错误 2:(已修复)

Unsupported major.minor version 52.0

由于插件中使用到了 JDK1.8 的一些 API,所以将 JDK 升级到 1.8 就可以了

错误 3: (已修复)

Error:Execution failed for task ':app:transformClassesWithDexForDebug'.
> DexKnife Warnning: Main dex is EMPTY ! Check your config and project!

gradle 切到 1.5.0,目前就发现 gradle 2.1.2 有这问题。

android 傻瓜式 MultiDex 插件,从此再也不用担心方法数问题!相关推荐

  1. word公式插件_再也不用担心我的公式写不出来了:一款公式输入神器实测

    → → https://mp.weixin.qq.com/s/gNRWdN-W4_Eb2MyyTpWzjw关注微信公众号:人工智能前沿讲习,重磅干货,第一时间送达 本文推荐一个公式输入神器,只要截图就 ...

  2. Android高级开发面试题目,再也不用担心不能升职加薪了。

    Java基础 1.内部类的作用 内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立. 在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类. 创 ...

  3. Idea代码自动补全!插件推荐:Codota,再也不用担心写不出代码了。

    前言 最近,有小伙伴推荐给D哥一款IDEA插件,叫我一定得给大家分享一下,简直太好用了~ 这款插件解决的痛点:当你不知道一个类该怎么用的时候,它能帮你快速找到想要的实例代码. 群里有不少同学说,&qu ...

  4. android 机器人gif,看完这15张GIF图 就再也不用担心机器人叛乱了

    流量预警 非WIFI请果断退出 今天大福我闲逛Reddit论坛时,看到一个帖子:有哪些GIF图是你百看不厌的? 回答中投票最高的一个网友贴了一张GIF图:机器人开阀门 图片较大 请耐心等待 然后他说道 ...

  5. 怎样知道android的手机号码,如何查询自己的手机号码?看完这三种方法,再也不用担心了...

    原标题:如何查询自己的手机号码?看完这三种方法,再也不用担心了 导语:现在智能手机如此盛行的时代,相信很多人有不止一部手机,这样就会出现自己的手机号码记不住的情况,身边经常有朋友遇到过自己的号码也记不 ...

  6. 30岁的万维网活不长了! 蒂姆·伯纳斯·李要借去中心化亲手杀死它, 你再也不用担心......

    整理 | 佩奇 公开信编译 | DeepTech(deeptechchina) 出品 | 区块链大本营(blockchain_camp) Tim Berners-Lee(蒂姆·伯纳斯·李),万维网之父 ...

  7. python实现抢劵_用Python实现微信自动化抢红包,再也不用担心抢不到红包了

    1. 概述 刚刚收到了两个消息,一个好消息,一个坏消息. 先说好消息,好消息就是微信群里有人要发红包,开心~ 不过转念一想,前几次的红包一个都没抢到,这次???不由自主的叹了一口气 ... 过了一会, ...

  8. 高考数学考用计算机求函数吗,高考数学52种快速做题方法整理!再也不用担心考试时间不够了...

    原标题:高考数学52种快速做题方法整理!再也不用担心考试时间不够了 1 . 适用条件 [直线过焦点],必有ecosA=(x-1)/(x+1),其中A为直线与焦点所在轴夹角,是锐角.x为分离比,必须大于 ...

  9. ERC721:全生命周期精析,妈妈再也不用担心我不会玩NFT合约啦

    ERC721:全生命周期精析,妈妈再也不用担心我不会玩NFT合约啦 由于篇幅有限,本博客将围绕ERC721核心展开介绍,文章内容尽量做到通俗易懂,但其中不可避免地可能涉及一些新手不友好的概念,您可以查 ...

最新文章

  1. c/c++ 避免重复包含 pragma once 和 #ifndef 的区别
  2. CC 攻击检测研究现状
  3. 基于小波变换到图像融合
  4. 展示使用 boost bind 和 phoenix 处理删除的不同方法的测试程序
  5. 【数据结构总结】第三章:栈和队列(线性结构)
  6. python生成四位随机数
  7. JS入门之Date对象
  8. 前端学习(1378):express静态资源处理
  9. Repeater点击表头双向排序
  10. 文件的上传下载功能的实现(包括进度条)[telerik控件]
  11. java.lang.Class.isPrimitive()用法解析
  12. Atitit.阿里云c盘 系统盘爆满解决方案
  13. stm32 整数加法循环时间_剑指 Offer 65. 不用加减乘除做加法 leetcode 剑指offer系列...
  14. 关于微信小程序的相关接口以及问题
  15. WiFi路由的PIN码与常见问题处理破解教程
  16. 【Python】socket编程——使用TCP协议实现智能聊天机器人
  17. java中的约瑟夫问题_java实现约瑟夫问题
  18. 有源码如何搭建网站(从零开始搭建教程)
  19. CocosCreator角色动态更换武器实现
  20. 玩纸牌游戏计算机教案,小班数学活动好玩的扑克牌教案

热门文章

  1. linux苹果photodata文件夹,iPhone系统文件夹中的文件及用途
  2. FAST 常见问题-1
  3. Unity官方教程—space shooter练习日记之脚色移动
  4. NLP系列(9)_深入理解BERT Transformer ,不仅仅是注意力机制
  5. redis脚本命令执行(redis.call)
  6. Educational Codeforces Round 93 (Rated for Div. 2) A. Bad Triangle 签到
  7. 网页html web直接打开cad文件dwg格式等的方法
  8. 介绍了一些代理相关工具
  9. 什么是IP转换器?我们看看IP转换器的原理与应用及了解它的一些功能和用途
  10. tomcat的启动start和停止Stop都是灰色的