Android应用开发之Android 方法引用数超过 65535 优雅解决
白羽 2018-08-10 来源 :网络 阅读 867 评论 0

摘要:本文将带你了解Android应用开发之Android 方法引用数超过 65535 优雅解决,希望本文对大家学Android有所帮助

        本文将带你了解Android应用开发之Android 方法引用数超过 65535 优雅解决,希望本文对大家学Android有所帮助


随着应用不断迭代更新,业务线的扩展,应用越来越大(比如:集成了各种第三方SDK或者公共开源的Library文件、jar文件)这样一来,项目耦合性就很高,重复作用的类就越来越多了,SO:问题就来了。
相信大家在做自己公司项目时,都有机会遇到下面的错误:

UNEXPECTED TOP-LEVEL EXCEPTION:  
Java代码  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)  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)
没错,你的应用中的Dex文件方法数超过了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有一个问题,也就是这篇文章想要说明并解决的问题。DexOpt会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能够超过65536个。当一个项目足够大的时候,显然这个方法数的上限是不够的。尽管在新版本的Android系统中,DexOpt修复了这个问题,但是我们仍然需要对低版本的Android系统做兼容.
目前比较常用的方法:
(1) 应用插件化,比如使用我正在参与开发的插件化框架 : https://github.com/singwhatiwanna/dynamic-load-apk ,如果有建议或者相关的问题,欢迎到Github上积极参与.
(2) 分割Dex,多工程: 把所需要的.class文件或者是Jar文件和一些源码一起编译生成一个Jar文件。然后使用Android SDK提供的dx工具把Jar文件转成Dex文件。我们可以提前对它进行ODex操作,让它在被DexClassLoader加载的时候,跳过DexOpt的部分工作,从而加快加载的过程.(可参考facebook: https://www.facebook.com/notes/facebook-engineering/under-the-hood-dalvik-patch-for-facebook-for-android/10151345597798920,这里边还可以看到在2.3上动态改变LinearAlloc缓冲的解决思路) 这两种方法并不冲突,插件化除了解决应用爆棚,还有很多其他的优点,可以看我之前的文章,不再复述.
当然,Google看来也意识到了目前应用方法数爆棚的问题, 目前在已经在API 21中提供了通用的解决方案,那就是android-support-multidex.jar. 这个jar包最低可以支持到API 4的版本(Android L及以上版本会默认支持mutidex).
让我们看一下如何应用android-support-multidex.jar(以下都以在Anroid studio中的使用为例,使用eclipse开发需要安装gradle插件,其他基本上相同):
首先在app/build.gradle文件中配置,如下:
Java代码  android {      compileSdkVersion 24      buildToolsVersion "24.0.1"        defaultConfig {          ...          minSdkVersion 14          targetSdkVersion 24          ...            // 设置支持multidex          multiDexEnabled true      }      ...   }     dependencies {      compile 'com.android.support:multidex:1.0.1'   }  android {
    compileSdkVersion 24
    buildToolsVersion "24.0.1"

    defaultConfig {
        ...
        minSdkVersion 14
        targetSdkVersion 24
        ...

        // 设置支持multidex
        multiDexEnabled true
    }
    ...
 }

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

接下来的配置,有两种配置方式:
(1) : 在application的子类中配置(如果你需要有Application子类的情况)

Java代码  package com.example;    import android.app.Application;  import android.content.Context;    /**  *   */  public class MyApplication extends Application {        @Override      protected void attachBaseContext(Context base) {          super.attachBaseContext(base);          MultiDex.install(this);      }  }  package com.example;

import android.app.Application;
import android.content.Context;

/**
 * 
 */
public class MyApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }
}
(2) : 如果你不需要application的子类的情况,那就直接集成MultiDexApplication

Java代码  package com.example;    import android.support.multidex.MultiDexApplication;  import android.content.Context;    /**  * Extended MultiDexApplication   */  public class MyApplication extends MultiDexApplication {         // 不需要重写attachBaseContext()         //..........  }  package com.example;

import android.support.multidex.MultiDexApplication;
import android.content.Context;

/**
 * Extended MultiDexApplication 
 */
public class MyApplication extends MultiDexApplication {

     // 不需要重写attachBaseContext()

     //..........
}
好了,经过上面两步的解决,Android 65535的问题,也得到了解决。

使用MutiDex的注意事项:
一、如果你继承了MutiDexApplication或者覆写了Application中的attachBaseContext()方法。
Application类中的逻辑的注意事项:
Application中的静态全局变量会比MutiDex的instal()方法优先加载,所以建议避免在Application类中使用静态变量引用main classes.dex文件以外dex文件中的类,可以根据如下所示的方式进行修改:

Java代码  @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();        }  @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的实现则可以很好地解决此问题。

常见问题
问题1:DexException: Library dex files are not supported in multi-dex mode,你可能会见到如下的错误:
Java代码  Error:Execution failed for task ':app:dexDebug'.    > com.android.ide.common.internal.LoggedErrorException: Failed to run command:        $ANDROID_SDK/build-tools/android-4.4W/dx --dex --num-threads=4 --multi-dex        ...      Error Code:        2      Output:        UNEXPECTED TOP-LEVEL EXCEPTION:        com.android.dex.DexException: Library dex files are not supported in multi-dex mode            at com.android.dx.command.dexer.Main.runMultiDex(Main.java:322)            at com.android.dx.command.dexer.Main.run(Main.java:228)            at com.android.dx.command.dexer.Main.main(Main.java:199)            at com.android.dx.command.Main.main(Main.java:103)  Error:Execution failed for task ':app:dexDebug'.  
> com.android.ide.common.internal.LoggedErrorException: Failed to run command:  
    $ANDROID_SDK/build-tools/android-4.4W/dx --dex --num-threads=4 --multi-dex  
    ...  
  Error Code:  
    2  
  Output:  
    UNEXPECTED TOP-LEVEL EXCEPTION:  
    com.android.dex.DexException: Library dex files are not supported in multi-dex mode  
        at com.android.dx.command.dexer.Main.runMultiDex(Main.java:322)  
        at com.android.dx.command.dexer.Main.run(Main.java:228)  
        at com.android.dx.command.dexer.Main.main(Main.java:199)  
        at com.android.dx.command.Main.main(Main.java:103)


解决方法:对于dex 的--multi-dex 选项设置与预编译的library工程有冲突,因此如果你的应用中包含引用的lirary工程,需要将预编译设置为false:

Java代码  android {        // ...        dexOptions {            preDexLibraries = false        }    }  android {  
    // ...  
    dexOptions {  
        preDexLibraries = false  
    }  
}
问题2:OutOfMemoryError: Java heap space.当运行时如果看到如下错误:

Java代码  UNEXPECTED TOP-LEVEL ERROR:    java.lang.OutOfMemoryError: Java heap space  UNEXPECTED TOP-LEVEL ERROR:  
java.lang.OutOfMemoryError: Java heap space
解决方法:在dexOptions中有一个字段用来增加java堆内存大小:

Java代码  android {        // ...        dexOptions {            javaMaxHeapSize "2g"        }    }  android {  
    // ...  
    dexOptions {  
        javaMaxHeapSize "2g"  
    }  
   

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之Android频道!

本文由 @白羽 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程