Android中65536问题剖析

article/2025/11/8 13:17:55

问题出现的原因是因为导入融云通信的包后,突然提示:

Error:The number of method references in a .dex file cannot exceed 64K. 
Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html。

解决办法:

在build.gradle里面加入multiDexEnabled true

    defaultConfig {...minSdkVersion 14targetSdkVersion 21...//加入multidex支持multiDexEnabled true}

在Application里面重写 attachBaseContext 方法

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

运行期间又出现:Error:(6, 32) 错误: 程序包android.support.multidex不存在

在6.0以上的系统打包就会遇到这个问题,但是在6.0以下的系统打包没问题。

解决方案如下:

在build.gradle文件里加上

compile 'com.android.support:multidex:1.0.1'

搞定收工。

既然遇到这个问题了,就剖析下为什么Android会有65536的问题。

一,Android中65536的来源

一个 dex 文件的方法引用数不能大于 64k,64k 的准确值是(64 * 1024 = 65536)。

65536的限制是因为Android应用以DEX文件的形式存储字节码文件,在Dalvik字节编码规范里,方法引用索引method referenceindex只有16位,即(2^16)65536个。method reference,这里限制的是自己代码、Android框架、第三方库三者方法数量的总和。Android打包Dex的过程如下:

Main.java里执行:

-> main() -> run() ->不分包执行runMonoDex()(或者分包执行runMultiDex())-> writeDex()

DexFile执行:

->toDex() -> toDex0()

Section:

->Section 的prepare() -> UniformItemSection的prepare0() ->MemberIdsSection的orderItems() -> getTooManyMembersMessage()

在MemberIdsSection里执行了这样一段方法:

protected void orderItems() {int idx = 0;if (items().size() >DexFormat.MAX_MEMBER_IDX + 1) {throw newDexIndexOverflowException(getTooManyMembersMessage());}for (Object i : items()) {((MemberIdItem) i).setIndex(idx);idx++;}}

getTooManyMembersMessage核心代码如下:

private String getTooManyMembersMessage() {try {String memberType = this instanceofMethodIdsSection ? "method" : "field";formatter.format("Too many %s references:%d; max is %d.%n" +Main.getTooManyIdsErrorMessage() + "%n" +"References bypackage:",memberType, items().size(),DexFormat.MAX_MEMBER_IDX + 1);return formatter.toString();}}
}

当代码里检测到方法数量的上限后,就会报错,这里的限制是:DexFormat.MAX_MEMBER_IDX,下面代码找到它的出处:

public final classDexFormat {/*** Maximum addressable field or methodindex.* The largest addressable member is0xffff, in the "instruction formats" spec as field@CCCC or* meth@CCCC.*/public static final int MAX_MEMBER_IDX =0xFFFF;
}

这个MAX_MEMBER_IDX的值是一个int类型定值0xFFFF,转化为10进制就是65535,所以这里大小的限制是不能超过65536的。被设定为65536的原因是因为:invoke-kind (调用各类方法)指令中,方法引用索引数是 16 位的,也就是最多调用 2^16 = 65536 个方法。

二,MultiDex 工作流程:

Multidex在构建打包阶段将Class拆分到多个Dex,使之不超过单Dex最大方法数的限制,是Google官方对64K方法数问题的一种补救措施。即超越限制后,用多个Dex进行补救。下面是他的工作流程。

在运行阶段,Multidex提取别的非主Dex出来,然后动态装载执行。

三,使用 MultiDex 可能会造成的问题以及解决方案

    1. 分拆导致的crash

     问题:除了报VerifyError外,还有可能报Could not find class,NoClassDefFoundError, Could not find method等。这种错误是因为我们在main dex中调用的函数或类被放在了classes2.dex中,而在classes2.dex还没有被完全加载前,调用这些api就会导致这种问题。

     要确认是否是这个问题导致的错误,我们可以查看:
     app\build\intermediates\multi-dex\debug\maindexlist.txt 这个文本文件,这里列出来的类都会被放在主dex中。

      解决方案:编译过程中,multidex有一生成maindexlist.txt的步骤:createDebugMainDexClassList就是这里生成maindexlist.txt 的,每次编译都会重新生成一次,我们可以使用自定义的方式:multiDexKeepFile file(‘multiDexKeep.txt’)

android {compileSdkVersion XXbuildToolsVersion "XX"defaultConfig {applicationId "x.x.x"minSdkVersion XXtargetSdkVersion XXversionCode XXversionName "XX"multiDexEnabled true//添加此行代码multiDexKeepFile file('multiDexKeep.txt')}}

内容和上面提到的createDebugMainDexClassList生成的maindexlist.txt一样,把这个multiDexKeep.txt文件放在app目录 下。multiDexKeep.txt内容可以如下:

com/test/Util.class
com/test/help/b.class

这样,被keep的class全都留在了classes.dex中。

     2. 首次启动可能出现ANR

         问题:无响应或者卡顿,因为把multidex的install放在了attachBaseContext中,而这个调用又是在MainActivity的onCreate之前的,所以如果2.dex,3.dex第一次加载时间很长,生成odex文件会耗费一定的时间, 就有可能会导致第一次启动出现ANR。

    解决方案:APP第一次启动,卸载、重装时都会做一遍2odex,具体可以查看

/data/data//code_cache/secondary-dexes/目录下的odex文件。把install放到异步线程里去做,写一个类似initAfterDex2Installed方法,来保证2.dex里的类不会提前被调用到,或者输出一个启动界面,停留几秒继进行加载。很多APP启动都有开机广告,或者开机画面,用来来解决app在2.dex加载之前部分功能无法使用的问题,保证某些耗时的操作在首屏启动不进行加载(一些避免ANR的思路)。

   如果MultiDex.install(this),放在后面或者异步来做的话,在MainActivity里的onCreate函数:
setContentView这里就出错了:

java.lang.NoClassDefFoundError: android.support.v7.appcompat.R$attrat android.support.v7.app.AppCompatDelegateImplV7.ensureSubDecor(AppCompatDelegateImplV7.java:289)at android.support.v7.app.AppCompatDelegateImplV7.setContentView(AppCompatDelegateImplV7.java:246)at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:106)at com.cn.x.x.MainActivity.onCreate(MainActivity.java:86)

android.support.v7.appcompat.R$attr在classes2.dex中,在调用时,还没有完成classes2.dex的加载,所以如果要解决的话,或者把这个类放到maindex中,或者让MainActivity的onCreate函数延迟调用。使用插件化的方式来解决maindex的问题,把一些功能做成插件,保证这些dex在首屏启动时不需要被加载。

    或者,自己实现多dex框架,例如微信的实现框架,没有使用MultiDex,而是使用自己的Tinker动态加载dex的方案,也被用于热更新。QQ里有classes6.dex,也就是总共有6个dex,基本上也是在手Q启动界面还没出来时,所有的dex会全部完成2odex的转换,在手机上第一次运行还是会花费不少时间的。

    所以针对ANR还是不建议使用异步加载,合理设计和插件化。

   四,如何将指定的 class 打进 mainDex

        1.Gradle中的配置

        在Gradle中增加afterEvaluate区域。配置如下:

apply plugin: 'com.android.application'android {compileSdkVersion 23buildToolsVersion "22.0.1"defaultConfig {applicationId "com.example.text"minSdkVersion 17targetSdkVersion 23versionCode 1versionName "1.0"multiDexEnabled true}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}
}
afterEvaluate {tasks.matching {it.name.startsWith('dex')}.each { def listFile = project.rootDir.absolutePath+'/app/maindexlist.txt'if (dx.additionalParameters == null) { dx.additionalParameters = []}//方法数越界时生成多个dex文件dx.additionalParameters += '--multi-dex'//指定listFile中的类打包到主dex中dx.additionalParameters += '--main-dex-list=' +listFile//-main-dex-list所指定的类才能打包到主dex中,没有这个选项,上个选项就会失效dx.additionalParameters += '--minimal-main-dex'}
}dependencies {compile fileTree(dir: 'libs', include: ['*.jar'])compile 'com.android.support:appcompat-v7:24.0.0-alpha1'compile 'com.android.support:multidex:1.0.1'
}

        2,创建一个maindexlist.txt

            根据上面builde.gradle中的配置,在app目录下创建一个maindexlist.txt,在这个txt里将想要放在主dex中的类写进去。(在\app\build\intermediates\multi-dex\debug目录下可以找到一个maindexlist.txt文件在它的基础上更改)。

搞定完工!

转载于:https://my.oschina.net/u/3761887/blog/1647272


http://chatgpt.dhexx.cn/article/G8eJW3PS.shtml

相关文章

数据类型

强类型语言 强类型语言是一种强制类型定义的语言,一旦某一个变量被定义类型,如果不经过强制转换,则它永远就是该数据类型了,强类型语言包括Java、.net 、Python、C等语言,要求变量的使用要严格符合规定,所有…

C语言自己写代码实现的strcmp函数

我们在面试的时候&#xff0c;经常会被笔试实现C语言系统函数&#xff0c;比如strcmp函数&#xff0c;主要考察大家的C语言功底&#xff01; #include <assert.h> #include <stdio.h>int MyStrCmp(const char* str1, const char* str2) {assert((str1 ! NULL) &a…

用指针实现strcmp函数功能

strcmp函数的原理&#xff1a; 将两个字符串&#x1f50d;&#xff08;s1&#xff0c;s2&#xff09;的元素ascii码依次比较&#xff0c;直到遇到最短字符串的‘\0’&#xff0c;返回最后一个元素ascii码比较结果。 指针实现方式&#xff1a; 通过定义两个char*指针&#x1f5…

C语言中的strcmp函数的作用是什么,c语言strcmp函数用法是什么?

c语言strcmp函数用法&#xff1a;语法结构为【int strcmp(char *str1, char *str2)】&#xff0c;比较字符串str1和str2是否相同&#xff0c;如果相同则返回0&#xff0c;如果不同&#xff0c;在不同的字符处如果str1的字符大于str2字符&#xff0c;则返回负1。 c语言strcmp函数…

strcmp函数及模拟

strcmp包含在<string.h>的头文件中&#xff0c;作用是比较两个字符串。将 C 字符串 str1 与 C 字符串 str2 进行比较。 1.strcmp函数的参数和返回值、 参数&#xff1a;是两个待比较字符串的首地址 返回值&#xff1a;此函数开始比较每个字符串的第一个字符。如果它们彼此…

自定义strcmp函数

不调用#include<string.h>实现strcmp函的功能&#xff1a; 先说一下strcmp的功能&#xff0c;是一个字符串处理函数&#xff0c;是一个用于对两组字符串进行比较的函数&#xff0c;它的返回值是int类型。 例如&#xff1a; int strcmp(char s1 , char s2) char s1[ ]…

一文吃透strcmp函数

年轻是我们唯一拥有权利去编织梦想的时光&#xff01;&#x1f493;&#x1f493;&#x1f493; 文章目录 •&#x1f319;写在前面• &#x1f34b;1.函数介绍• &#x1f330;1.1.函数接口• &#x1f330;1.2.函数分析• &#x1f330;1.3.函数的简单使用• &#x1f330;1.…

比较函数大合集:strcmp,strncmp,memcmp

前言&#xff1a; 接着奏乐接着舞&#xff0c;我们继续来看字符串函数和内存函数。今天要讲的是比较函数大合集&#xff0c;话不多说&#xff0c;开肝&#xff01;&#xff01;&#xff01; strcmp函数&#xff1a; 什么是strcmp函数&#xff1f; strcmp即string compare&#…

Linux 字符串截取命令

Linux 字符串截取&#xff0c;一般用在 shell 脚本中&#xff0c;本篇写几个简单的 demo 跟大家分享一下 首先&#xff0c;定义一个变量 demohttps://blog.csdn.net/ 1.使用 # 号截取&#xff0c;删除左边字符&#xff0c;保留右边字符 echo ${demo#*//} 其中&#xff0c;demo…

bat字符串截取

举例&#xff1a;输入hello world和-2&#xff0c;输出ld 这个简单。代码如下 echo off&Setlocal enabledelayedexpansion :标签1 set 文本hello world set 开头-2 set 结尾 set /p 文本请输入文本&#xff0c;留空使用默认值hello world set /p 开头请输入取值开头位…

批处理字符串截取

批处理字符串截取 在批处理中&#xff0c;set的功能有点繁杂&#xff1a;设置变量、显示环境变量的名及值、做算术运算、等待用户的输入、字符串截取、替换字符串&#xff0c;是我们常用的命令之一。 在字符串截取方面&#xff0c;新手因为没能注意到偏移量的问题&#xff0c;…

整理了几种字符串截取方法

一、 (Substring);(Remove);(Replace) 1、取字符串的前i个字符 (1)string str1str.Substring(0,i);(2)string str1str.Remove(i,str.Length-i); 2、去掉字符串的前i个字符 string str1str.Remove(0,i);string str1str.SubString(i); 3、从右边开始取i个字符 string str1str.Sub…

JSTL标签的使用详情

if 标签 choose标签 forEach标签 这里是JSTL的标签文档&#xff0c;我们主要介绍几个常用的 https://www.runoob.com/jsp/jsp-jstl.html if 标签 if标签为判断标签&#xff0c;没有else标签&#xff0c;多个分支就用个if判断 例子 访问结果 choose标签 choose标签为选择标签…

JSTL标签库的使用及其常用标签

目录 什么是JSTL标签库&#xff1f; 使用JSTL标签库的步骤&#xff1a; 第一步&#xff1a;引入JSTL标签库对应的jar包。 第二步&#xff1a;在JSP中引入要使用标签库。&#xff08;使用taglib指令引入标签库。&#xff09; 第三步&#xff1a;在需要使用标签的位置使用即可。…

JSP之自定义jstl标签

目录 一&#xff0c;什么是JSP&#x1f351; 二&#xff0c;什么是JSTL&#xff08;JSTL标签库&#xff09;&#x1f353; 三&#xff0c;如何使用JSTL&#x1f345; 在项目中如何使用JSTL标签&#xff1f;在发开中使用JSTL标签库需要执行如下两个步骤。 &#xff08;1&a…

什么是JSTL标签?常用的标签库有哪些?

从JSP1.1规范开始&#xff0c;JSP就支持使用自定义标签&#xff0c;使用自定义标签大大降低了JSP页面的复杂度&#xff0c;同时增强了代码的重用性。为此&#xff0c;许多Web应用厂商都定制了自身应用的标签库&#xff0c;然而同一功能的标签由不同的Web应用厂商制定可能是不同…

jsp--JSTL标签库

目录 1.JSTL标签库介绍 2.JSTL 标签库的使用步骤 3.core核心库使用 3.1 <c:set> 3.2 <c:if /> 3.3 <c:choose> <c:when> <c:otherwise >标签 3.4 <c:forEach /> 1.JSTL标签库介绍 JSTL 标签库&#xff0c;全称是指 JSP Standard …

【Java Web】JSTL标签库的引入

在使用JSTL标签前首先要引入JSTL标签库 引入&#xff1a; <% taglib prefix"c" uri"http://java.sun.com/jsf/core" %> taglib是JSP指令&#xff0c;功能是用来引入标签库&#xff1b; prefix意思是前缀&#xff0c;指的就是使用标签时的前缀&a…

常见JSTL标签详解

JSP标准标签库&#xff08;JSTL&#xff09; jsp标准标签库&#xff08;jstl&#xff09;是一个JSP标签集合&#xff0c;它封装了jsp应用的通用核心功能。 JSTL支持通用的、格式化的任务。比如&#xff1a;迭代、条件判断、XML文档操作、国际化标签、SQL标签。除了这些它还提供…

JSTL标签库之核心标签

一、JSTL标签库介绍   JSTL标签库的使用是为弥补html标签的不足&#xff0c;规范自定义标签的使用而诞生的。使用JSLT标签的目的就是不希望在jsp页面中出现java逻辑代码 二、JSTL标签库的分类 核心标签(用得最多)国际化标签(I18N格式化标签)数据库标签(SQL标签&#xff0c;很…