彻底解决Android 应用方法数不能超过65K的问题

本文围绕Android方法数不能超65K限制展开,分析了Conversion to Dalvik format failed和INSTALL_FAILED_DEXOPT两个异常的原因。介绍了以微信为代表的插件方案和以Facebook为代表的分包方案来解决65K问题,还分别阐述了基于gradle和Ant构建Android项目实现分包的具体步骤及加载second.dex的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作为一名Android开发者,相信你对Android方法数不能超过65K的限制应该有所耳闻,随着应用程序功能不断的丰富,总有一天你会遇到一个异常:

 

Conversion to Dalvik format failed:Unable toexecute dex: method ID not in [0, 0xffff]: 65536

可能有些同学会说,解决这个问题很简单,我们只需要在Project.proterty中配置一句话就Ok啦,

dex.force.jumbo=true

是的,加入了这句话,确实可以让你的应用通过编译,但是在一些2.3系统的机器上很容易出现

INSTALL_FAILED_DEXOPT异常

 

对于以上两个异常,我们先来分析一下原因:

1、Android系统中,一个Dex文件中存储方法id用的是short类型数据,所以导致你的dex中方法不能超过65k

2、在2.3系统之前,虚拟机内存只分配了5M

 

知道了原因,我们就来一个个的解决上面的问题,首先对于65k的问题,我们在应用层是无法改变android系统的结构的,所以我们无法将数据类型从short改变为int或者其他类型,也就是说一个dex中的方法数不能超过65k是我们无法逾越的鸿沟,我们只能减少一个dex中的方法数,首先最容易想到的方案就是去掉一些无用的Jar包,以及将一些属性设置为public,从而可以去掉get/set方法,这种方法只能临时解决问题,随着时间的推移,总有一天还是会出现方法数超过65k的,毕竟一个应用一般是在加功能,不会减功能。

 

 

下面我来向大家介绍两种主流的解决方案,一种是以微信为代表的,将一些功能做成插件,动态加载,另一种方案是以facebook为代表的分包方案,将一个apk中的dex文件分割成多个dex文件,然后动态的去加载dex文件。其实这两种方案的核心思想是一样的,插件是把未来要开发的新功能做成apk和dex动态加载,而分包方案是将已经完成的功能分成多个dex文件动态加载,其实我个人觉得插件方案比分包方案更好的解决了65k的问题,因为插件方案不仅能够解决65k问题,还能让我们的应用体积减小,而分包只能解决65k的问题。

 

关于插件开发,做成动态加载,我在很早之前一篇文章中就写过其基本思想,有兴趣的同学可以看看

 

《实现Android 动态加载APK(Fragment or Activity实现)》

http://blog.youkuaiyun.com/yuanzeyao/article/details/38565345

 

下面我们重点介绍分包机制

我们知道一个apk文件里面有一个dex文件,这个dex文件里面都是经过优化了的class文件,所谓分包,就是讲一个dex文件分成多个dex文件,这里我们约定一下,第一个dex叫做main.dex,第二个叫做second.dex,通常在分包的时候,我们需要将应用启动就需要使用的类放入到main.dex中,把不是立马就需要使用的类放入到second.dex中,对于Android系统,他只会默认加载main.dex的,second.dex对于他来说可能只是一个资源文件,它是不会主动去加载second.dex,所以我在应用启动的过程中,我们需要为second.dex创建好一个类加载器,便于我在使用second.dex中的类时,能够里面加载该类。

 

关于如何加载second.dex也有好多做法,用的比较多的主要有一下几种

1、最简单的做法就是使用DexClassLoader进行加载,并将该DexClassLoader的父加载器设置为PathClassLoader

2、使用DexClassLoader加载,并将DexClassLoader的父加载器设置成PathClassLoader的父加载器,将PahtClassLoader的父加载器设置成DexClassLoader,仔细品味一下1和2的区别

3、将second.dex的路径放入到PathClassLoader的加载路径中

 

对于第2中方案,在有一种情况下是不能使用的,比如当second.dex通过DexClassLoader加载,但是second.dex中使用了一个类,这个类在main.dex中,这个时候就会抛出类找不到的异常,所以这种方案只能拥有second.dex不会用到main.dex类的时候

 

以上说的都是理论,下面我们来实战一下

我这里会介绍两种方案,一种是基于gradle构建Android项目,一种是基于Ant构建Android项目

方案一:基于gradle构建Android项目,并实现分包

环境要求:AndroidStudio0.9以上,gradle插件0.14.2以上

 

1、如果你的工程在eclipse中,那么你需要将该工程导入到Android中,此时需要你升级adt22以上

2、打开你工程的build.gradle文件,检查gradle插件是否是0.14.2版本之后,因为0.14.2之后gradle插件才支持分包

 

3、打开工程下某一个Moudle的build.gradle文件,添加对android-support-multidex.jar的依赖

4、去掉第三方jar包中重复的类

 

5、设置虚拟机堆内存空间大小,避免在编译期间OOM

 

6、gradle构建项目时,貌似默认是不会将so库加入工程的,所以为了避免此种情况发生,我们需要制定so库目录,对于从eclipse转换过来的工程,还需要制定src和资源文件路径

 

7、如果你的项目依赖了其他库, 分别在各个库工程中加入 multiDexEnabled = true 和 jniLibs.srcDirs =['libs']两个配置即可

8、如果你的项目没有自定义Application,那么你在AndroidManifest.xml中使用MultiDexApplication即可,如果你的项目有自定义Application,并且是继承是Application,那么只需要改为继承MultiDexApplication即可,如果你的项目时继承的其他Application,那么你需要重写

attachBaseContext

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

 

  1. @Override  
  2. protected void attachBaseContext(Context base) {  
  3.     super.attachBaseContext(base);  
  4.     MultiDex.install(this);  
  5. }  


经过上述配置,你的项目应该是已经成功分包了。如果分包成功,那么你解压你的apk文件,会发现有两个dex文件,通过上述的配置过程,我们发现此方案我们无法控制哪些类在main.dex中,哪些类在second.dex中,通过此种方案配置分包,可以兼容API4-API20.其加载second.dex采用的是上述方案中的3

 

 

 

 

下面我们来看看基于Ant构建Android项目,并实现分包过程

在上述方案中,由于我们无法看到gradle构建项目的脚本,所以我们无法控制哪些类在第一个dex,哪些类在第二个dex,此方案中,我们采用Ant构建,Ant是允许用户自己定义构建方案的,比如我们可以通过自定义构建方案,将项目中某些第三方jar包放入到second.dex中,关于这个如何实现,请参考开源项目吧

https://github.com/mmin18/Dex65536.git

由于该项目加载second.dex所采用的方案是上述方案2,比如second.dex中的某些第三方jar包依赖main.dex中的某些类,这种方案就会实现,所以在此我将此方案去掉,换成了方案3,也就是将second.dex的路径设置到PathClassLoader的加载路径中

我只给出Android 4.4中的解决方案,其他系统大同小异

加载second.dex方法

 

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片

 

  1. /** 
  2.     @param loader 
  3.          PathClassLoader 
  4.     @additionalClassPathEntries  
  5.          要被加载的dex文件,这里就是我们的second.dex 
  6.     @optimizedDirectory 
  7.          就是dex文件解压的目录 
  8. */  
  9.  private static void install(ClassLoader loader, List<File> additionalClassPathEntries,  
  10.                 File optimizedDirectory)  
  11.                         throws IllegalArgumentException, IllegalAccessException,  
  12.                         NoSuchFieldException, InvocationTargetException, NoSuchMethodException {  
  13.             /* The patched class loader is expected to be a descendant of 
  14.              * dalvik.system.BaseDexClassLoader. We modify its 
  15.              * dalvik.system.DexPathList pathList field to append additional DEX 
  16.              * file entries. 
  17.              */  
  18.              //通过反射找到pathList的值  
  19.             Field pathListField = findField(loader, "pathList");  
  20.             Object dexPathList = pathListField.get(loader);  
  21.             ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();  
  22.             //将second.dex 加入到PathClassLoader的加载路径中  
  23.             expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,  
  24.                     new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,  
  25.                     suppressedExceptions));  
  26.             if (suppressedExceptions.size() > 0) {  
  27.                 for (IOException e : suppressedExceptions) {  
  28.                     Log.w(TAG, "Exception in makeDexElement", e);  
  29.                 }  
  30.                 Field suppressedExceptionsField =  
  31.                         findField(loader, "dexElementsSuppressedExceptions");  
  32.                 IOException[] dexElementsSuppressedExceptions =  
  33.                         (IOException[]) suppressedExceptionsField.get(loader);  
  34.   
  35.                 if (dexElementsSuppressedExceptions == null) {  
  36.                     dexElementsSuppressedExceptions =  
  37.                             suppressedExceptions.toArray(  
  38.                                     new IOException[suppressedExceptions.size()]);  
  39.                 } else {  
  40.                     IOException[] combined =  
  41.                             new IOException[suppressedExceptions.size() +  
  42.                                             dexElementsSuppressedExceptions.length];  
  43.                     suppressedExceptions.toArray(combined);  
  44.                     System.arraycopy(dexElementsSuppressedExceptions, 0, combined,  
  45.                             suppressedExceptions.size(), dexElementsSuppressedExceptions.length);  
  46.                     dexElementsSuppressedExceptions = combined;  
  47.                 }  
  48.   
  49.                 suppressedExceptionsField.set(loader, dexElementsSuppressedExceptions);  
  50.             }  
  51.         }  

 

分包成功后,解压apk文件,进入assert文件夹,我们看到如下结构,libs.apk就是第三方jar编译后形成的dex文件

对于上面提到的第二个问题INSTALL_FAILED_DEXOPT,根本原因就是2.3版本之前dalvik虚拟机的内存只有5M,所以无论是插件方案还是分包方案在某些手机上还是会遇到该问题,毕竟我们仅仅是减少了每个dex中包的数量,但是方法总数是没有减少的,所以解决此问题的根本方法就是修改虚拟机内存至8M,这个需求在Java层是无法实现,但是可以在c层实现,具体实现流程可以参考开源项目:

https://github.com/viilaismonster/LinearAllocFix.git

 

 

 

 

 

原创 :http://blog.youkuaiyun.com/yuanzeyao/article/details/41809423

<think>嗯,用户询问的是Android15系统中预加载类的精简问题,这涉及到系统优化层面。用户可能正在开发定制ROM或进行深度系统优化,需要明确哪些预加载类可以安全移除。 从技术角度看,Android系统预加载类主要分布在三个层面:AOSP基础类、GMS服务类和OEM定制类。其中AOSP基础类如ActivityManagerService等绝对不能移除,否则会导致系统崩溃。GMS相关类如GooglePlayServicesUtil在非谷歌设备上可考虑移除,但要注意依赖关系。OEM预装应用相关的类如MiuiSystemUI是主要精简对象。 用户特别强调"安全移除",说明ta可能有过因误删导致系统不稳定的经历。需要重点提醒备份和测试流程,建议采用分批注释的方式验证。有趣的是用户提到"不需要"的类,这暗示ta可能想最大限度精简系统,因此需要补充说明即使可移除的类也可能影响特定功能。注意到引用内容中提到了ADB卸载和定制ROM两种方式,但用户问题聚焦在"类"级别而非应用层面,说明ta需要更底层的修改方案。RecyclerView的引用虽然不直接相关,但其缓存机制的设计思路可以借鉴到系统优化中——就像RecyclerView的多级缓存,系统预加载类也可以分层处理。 需要警告的是Android15新引入的PrivacySandbox相关类看似可删,实际会破坏广告API兼容性。另外ProjectMainline模块化设计使得某些类看似独立实则存在动态依赖,这点必须重点说明。</think>在 Android 15 系统中识别哪些预加载类可以安全移除或不必要,需要极其谨慎的操作和深入分析。主要可考虑移除的类别包括: **一、 可考虑移除的预加载类 (需严格验证)** 1. **特定 OEM / 运营商定制应用类:** * **描述:** 手机厂商 (`OEM`) 或运营商 (`Carrier`) 预装的非必要应用(如推广应用、特定区域服务、品牌定制应用)。 * **示例类前缀/包名:** `com.<oem_name>.<app_name>`, `com.<carrier_name>.<app_name>` (例如 `com.example.oem.bloatware`, `com.example.carrier.videoapp`)。具体名称因设备和地区而异。 * **风险:** 相对较低,但需确认该应用无底层系统依赖(如作为系统设置的入口)。移除前**务必备份**。 2. **过时或替代的 AOSP 应用类:** * **描述:** Android 开源项目 (`AOSP`) 包含的基础应用,如果用户或 OEM 已用更优方案替代,其相关类可能冗余。 * **示例:** 旧版 `Email` 应用类(若使用 Gmail 或其他客户端)、旧版 `Browser` 应用类(若使用 Chrome 或其他浏览器)、`BasicDreams`(基础屏保,若不用)。 * **风险:** 中等。需确认替代应用功能完整,且系统无隐藏依赖(如 Intent 处理)。移除前**充分测试**核心功能。 3. **特定 GMS 组件类 (非核心/非必需):** * **描述:** Google 移动服务 (`GMS`) 中非核心或用户设备不需要的部分(主要在非谷歌设备或极度精简场景)。 * **示例:** `Play Movies & TV`, `Play Books`, `Play Games`, `Google+`(历史遗留), 特定区域服务类(如 `WalletService` 在无支付需求地区)。**核心 GMS 类(如 GSF, Play Services 框架)绝对不可移除!** * **风险:** 高。可能导致依赖这些组件的其他应用(甚至系统应用)崩溃或功能缺失。**极度不推荐普通用户操作,仅限深度定制。** **二、 绝对不可移除的预加载类 (移除将导致系统崩溃/无法启动/严重故障)** 1. **核心框架与服务类:** * **描述:** Android 系统运行的基础。 * **示例:** `android.*` 包下关键框架类 (`ActivityManagerService`, `PackageManagerService`, `WindowManagerService` 等), `SystemUI` 相关类, `Telephony` 核心类, `Wifi` 核心类, `Bluetooth` 核心类。 2. **关键 AOSP 应用类:** * **描述:** 提供基本系统功能的应用。 * **示例:** `Settings` (设置), `Launcher` (桌面), `Dialer` (电话), `Contacts` (通讯录), `Messaging` (短信), `Camera2` (相机框架)。 3. **核心 GMS 类 (如果设备预装 GMS):** * **描述:** Google 服务框架的核心。 * **示例:** `Google Services Framework (GSF)`, `Google Play Services` 核心库类 (`com.google.android.gms.*` 中的基础部分), `AccountManager` 相关集成类。 4. **硬件抽象层 (`HAL`) / 驱动相关类:** * **描述:** 与设备硬件交互的接口。 * **示例:** `android.hardware.*` 包下的关键类。 5. **启动器 (`Init`) 相关类:** * **描述:** 系统启动过程必需的类。 6. **ART 虚拟机核心类:** * **描述:** 应用运行环境的核心。 **三、 识别与移除方法 (高风险!仅适用于开发者/高级用户)** 1. **分析预加载列表:** * 获取设备 `system.img` 或 `product.img` 等分区镜像。 * 解包镜像,检查 `/system/framework/`, `/system/app/`, `/system/priv-app/`, `/product/app/`, `/product/priv-app/` 等目录下的 APK/JAR 文件。 * 使用 `dexdump`, `baksmali/smali` 或 `jadx` 等工具反编译 APK/JAR,查看其包含的类 (`classes.dex`, `classes2.dex`, ...)。 2. **依赖关系分析 (至关重要!):** * 检查 `AndroidManifest.xml`: 查看应用的 `<uses-library>` 声明、`<intent-filter>` 定义、`<permission>` 和 `<uses-permission>` 等,分析其依赖和被依赖关系。 * 代码分析: 查找对其他包名/类名的显式引用 (如 `import` 语句, `Class.forName()` 调用)。 * 动态分析: 在修改后的系统上运行,使用 `logcat` 监控错误日志(尤其是 `ClassNotFoundException`, `NoClassDefFoundError`)。 3. **移除方法:** * **ADB 卸载 (临时,重启可能恢复):** 仅对非系统级应用有效,且需设备已 `root` 或启用 `ADB` 卸载系统应用权限。 ```bash adb shell pm uninstall -k --user 0 <package.name> ``` * **定制 ROM (永久):** 在编译 Android 15 源码时,修改 `makefile` (如 `device/<vendor>/<device>/device.mk` 或 `product makefiles`) 中 `PRODUCT_PACKAGES` 变量,移除对应应用的条目。这是**最彻底、最推荐**给开发者的方法[^1]。 * **直接删除系统分区文件 (高风险!):** 在已 `root` 的设备上,挂载 `/system` 分区为可写 (`mount -o rw,remount /system`),删除对应的 APK/JAR/ODEX/VDEX 文件。**极易出错导致变砖!强烈不推荐。** **四、 重要警告与建议** * **备份!备份!备份!** 在进行任何操作前,完整备份系统和数据。 * **极高风险:** 移除系统预加载类极易导致系统不稳定、功能失效、甚至无法启动(变砖)。 * **深度技术门槛:** 需要精通 Android 系统架构、`Linux`、`Java`、`ADB`、`ROM` 编译等。 * **充分测试:** 任何移除操作后,必须进行**全面**的功能测试和稳定性测试。 * **优先考虑非系统方法:** 对于普通用户,优先使用“禁用”功能 (`adb shell pm disable-user --user 0 <package.name>`) 或第三方工具冻结应用,这比移除类更安全。 * **关注 Android 15 新特性:** Android 15 可能引入新的模块化设计或隐私沙盒相关类,移除前需特别研究其作用。 **
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值