Android65K
随着应用不断迭代,业务线的扩展,应用越来越大(比如集成了各种第三方sdk或者公共支持的jar包,项目耦合性高,重复作用的类越来越多),相信很多人都遇到过如下的错误:
- <span style="color:#ff0000;">UNEXPECTED TOP-LEVEL EXCEPTION:
- java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536
- at com.android.dx.merge.DexMerger$6.updateIndex(DexMerger.java:501)
- at com.android.dx.merge.DexMerger$IdMerger.mergeSorted(DexMerger.java:282)
- at com.android.dx.merge.DexMerger.mergeMethodIds(DexMerger.java:490)
- at com.android.dx.merge.DexMerger.mergeDexes(DexMerger.java:167)
- at com.android.dx.merge.DexMerger.merge(DexMerger.java:188)
- at com.android.dx.command.dexer.Main.mergeLibraryDexBuffers(Main.java:439)
- at com.android.dx.command.dexer.Main.runMonoDex(Main.java:287)
- at com.android.dx.command.dexer.Main.run(Main.java:230)
- at com.android.dx.command.dexer.Main.main(Main.java:199)
- at com.android.dx.command.Main.main(Main.java:103) </span>
这个错误的原因是你的app中方法总数超过了65535个。应用被撑爆了。。
那么让我们看一下为什么会引起这种错误:
在
Android
系统中,一个
App
的所有代码都在一个
Dex
文件里面。
Dex
是一个类似Jar的存储了多有
Java
编译字节码的归档文件。因为
android
系统使用
Dalvik
虚拟机,所以需要把使用
Java
Compiler
编译之后的
class
文件转换成
Dalvik
能够执行的
class
文件。这里需要强调的是,
Dex
和
Jar
一样是一个归档文件,里面仍然是
Java
代码对应的字节码文件。当
Android
系统启动一个应用的时候,有一步是对
Dex
进行优化,这个过程有一个专门的工具来处理,叫
DexOpt
。
DexOpt
的执行过程是在第一次加载Dex文件的时候执行的。这个过程会生成一个
ODEX
文件,即
Optimised Dex
。执行
ODex
的效率会比直接执行
Dex
文件的效率要高很多。 但是在早期的Android系统中,
DexOpt
的LinearAlloc存在着限制: Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB或16MB。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃,导致无法安装.
另外由于DEX文件格式限制,一个DEX文件中method个数采用使用原生类型short来索引文件中的方法,也就是4个字节共计最多表达65536个method,field/class的个数也均有此限制。对于DEX文件,则是将工程所需全部class文件合并且压缩到一个DEX文件期间,也就是Android打包的DEX过程中, 单个DEX文件可被引用的方法总数被限制为65536(自己开发以及所引用的Android Framework和第三方类库的代码).
在API 21中,Google提供了通用的解决方案。那就是
android-support-multidex.jar
. 这个jar包最低可以支持到
API 4
的版本(
Android L
及以上版本会默认支持
mutidex
).
这个问题可以通过将一个DEX文件分拆成多个DEX文件解决
首先使用Android SDK Manager升级到最新的Android SDK Build Tools和Android Support Library R21。然后进行以下两步操作:
1.修改Gradle配置文件,启用MultiDex并包含MultiDex支持:
android {
compileSdkVersion 21 buildToolsVersion "21.1.0"defaultConfig { ... minSdkVersion 14 targetSdkVersion 21 ... // Enabling multidex support. multiDexEnabled true}...}dependencies { compile 'com.android.support:multidex:1.0.0' }
- 在AndroidManifest.xml的application中声明android.support.multidex.MultiDexApplication;
- 如果你已经有自己的Application类,让其继承MultiDexApplication;
- 如果你的Application类已经继承自其它类,你不想/能修改它,那么可以重写attachBaseContext()方法:
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this);}
使用MutiDex的主意事项
一. 如果你继承了MutiDexApplication或者覆写了Application中的attachBaseContext()方法.
Application类中逻辑的注意事项
:
Application
中的静态全局变量会比
MutiDex
的
instal()
方法优先加载,所以建议避免在
Application
类中使用静态变量引用
main classes.dex
文件以外dex文件中的类,可以根据如下所示的方式进行修改:
- @Override
- public void onCreate() {
- super.onCreate();
- final Context mContext = this;
- new Runnable() {
- @Override
- public void run() {
- // put your logic here!
- // use the mContext instead of this here
- }
- }.run();
- }
二.
虽然Google解决了应用总方法数限制的问题,但并不意味着开发者可以任意扩大项目规模。Multidex仍有一些限制:
- DEX文件安装到设备的过程非常复杂,如果第二个DEX文件太大,可能导致应用无响应。此时应该使用ProGuard减小DEX文件的大小。
- 由于Dalvik linearAlloc的Bug,应用可能无法在Android 4.0之前的版本启动,如果你的应用要支持这些版本就要多执行测试。
- 同样因为Dalvik linearAlloc的限制,如果请求大量内存可能导致崩溃。Dalvik linearAlloc是一个固定大小的缓冲区。在应用的安装过程中,系统会运行一个名为dexopt的程序为该应用在当前机型中运行做准备。dexopt使用LinearAlloc来存储应用的方法信息。Android 2.2和2.3的缓冲区只有5MB,Android 4.x提高到了8MB或16MB。当方法数量过多导致超出缓冲区大小时,会造成dexopt崩溃。
- Multidex构建工具还不支持指定哪些类必须包含在首个DEX文件中,因此可能会导致某些类库(例如某个类库需要从原生代码访问Java代码)无法使用。
避免应用过大、方法过多仍然是Android开发者要注意的问题。Mihai Parparita的开源项目dex-method-counts可以用于统计APK中每个包的方法数量。
通常开发者自己的代码很难达到这样的方法数量限制,但随着第三方类库的加入,方法数就会迅速膨胀。因此选择合适的类库对Android开发者来说尤为重要。
开发者应该避免使用Google Guava这样的类库,它包含了13000多个方法。尽量使用专为移动应用设计的Lite/Android版本类库,或者使用小类库替换大类库,例如用Google-gson替换Jackson JSON。而对于Google Protocol Buffers这样的数据交换格式,其标准实现会自动生成大量的方法。采用Square Wire的实现则可以很好地解决此问题。
附:在我见过的项目中引用
multidex的方法:
最后我给大家贴上一个不常见但以后会遇到的错:
这是因为java的堆内存大小被。。撑爆了。所以增加一个字段改变堆内存的大小。
在dexOptions中有一个字段用来增加java堆内存大小:
- android {
- // ...
- dexOptions {
- javaMaxHeapSize "2g" //大小可以自己设置。
- }
- }
studio中获取代码行数的方法:
1)按住Ctrl+Shift+A,在弹出的框输入‘find’,然后选择Find in Path.(或者使用快捷键Ctrl+Shift+F)
2)在弹出Find in Path的框中的Text to find输入\n,接着勾选Regular expression(正则表达式),Context选择anywhere,
Scope根据你想要统计的范围进行选择,File mask选择*.java。(在这里统计项目的Java的代码行数)
3)下图的Useages in generated code是自动生成的代码,上面那个就是项目的代码行数。