- 我们在开发android应用的时候,如果依赖了很多三方库,应该会遇到65535放法数超限的问题,比如使用Android Studio进行构建apk的时候失败了,并报出如下错误日志
AGPBI: {"kind":"error","text":"Cannot fit requested classes in a single dex file (# methods: 102169 > 65536)","sources":[{}],"tool":"D8"} com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: 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 at com.android.builder.dexing.D8DexArchiveMerger.getExceptionToRethrow(D8DexArchiveMerger.java:132) at com.android.builder.dexing.D8DexArchiveMerger.mergeDexArchives(D8DexArchiveMerger.java:119) at com.android.build.gradle.internal.transforms.DexMergerTransformCallable.call(DexMergerTransformCallable.java:102) at com.android.build.gradle.internal.tasks.DexMergingTaskRunnable.run(DexMergingTask.kt:445) at com.android.build.gradle.internal.tasks.Workers$ActionFacade.run(Workers.kt:348) at org.gradle.workers.internal.AdapterWorkAction.execute(AdapterWorkAction.java:50) at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:47) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1$1.create(NoIsolationWorkerFactory.java:65) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1$1.create(NoIsolationWorkerFactory.java:61) at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:98) at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.execute(NoIsolationWorkerFactory.java:61) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:416) at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:406) at org.gradle.internal.operations.DefaultBuildOperationExecutor$1.execute(DefaultBuildOperationExecutor.java:165) at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:250) at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:158) Cannot fit requested classes in a single dex file (# methods: 102169 > 65536) at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:102) at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36) at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:56) at org.gradle.workers.internal.DefaultWorkerExecutor$3.call(DefaultWorkerExecutor.java:215) at org.gradle.workers.internal.DefaultWorkerExecutor$3.call(DefaultWorkerExecutor.java:210) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:215) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:131) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) at java.lang.Thread.run(Thread.java:748) Caused by: com.android.tools.r8.CompilationFailedException: Compilation failed to complete at com.android.tools.r8.utils.O.a(:65) at com.android.tools.r8.D8.run(:11) at com.android.builder.dexing.D8DexArchiveMerger.mergeDexArchives(D8DexArchiveMerger.java:117) Caused by: com.android.tools.r8.CompilationFailedException: Compilation failed to complete ... 34 more Caused by: com.android.tools.r8.utils.b: Error: null, Cannot fit requested classes in a single dex file (# methods: 102169 > 65536) at com.android.tools.r8.utils.y0.a(:21) at com.android.tools.r8.dex.K.a(:56) at com.android.tools.r8.dex.K$h.a(:5) at com.android.tools.r8.dex.b.b(:15) at com.android.tools.r8.dex.b.a(:38) Caused by: com.android.tools.r8.utils.b: Error: null, Cannot fit requested classes in a single dex file (# methods: 102169 > 65536) at com.android.tools.r8.D8.d(:87) at com.android.tools.r8.D8.b(:1) at com.android.tools.r8.utils.O.a(:30) ... 36 more Execution failed for task ':app:mergeDexDebug'. > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade > com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: 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 * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
- 这个问题很好解决的,只需要在build.gradle中配置支持多dex参数
android { ... defaultConfig { ... multiDexEnabled true//解决方法数超65536限制问题 } }
- 然后再自己的Application里重写attachBaseContext(),加入MultiDex.install(context)即可
... import androidx.multidex.MultiDex; public class MyApplication extends Application { @Override protected void attachBaseContext(Context context) { MultiDex.install(context); super.attachBaseContext(context); } }
- 然后将自己的MyApplication注册到AndroidManifest.xml中
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.xx.xx"> <application android:name="com.xx.xx.MyApplication" > </application> </manifest>
- 通过Android Studio处理65535放法数超限的问题是很好解决的,如果我们是逆向开发,比如有一个游戏应用接入了我们的SDK,给了一个apk给我们,我们需要拿到这个apk替换掉我们SDK的代码,并加入一些其他的插件SDK代码,我们应该怎么办呢
- 首先接入我们SDK的应用不管他们出包的时候是否有放法数超限的问题,都进行上述处理65535放法数超限的解决方案去处理,这样处理过后,当我们合并到apk中的代码如果超限制了,也不用在合并的时候在Application里面去加入 MultiDex.install(context)了,没有超放法数也不会有什么影响
- 合并代码的时候需要将母包通过apktool进行解析,然后将旧的代码或资源删除,然后加入新的代码和资源,加入新的代码就会引入65535放法数超限的问题,因为一个dex文件最多只能有65535个方法,多了的话,运行时会奔溃,合并的策略有多种,下面我们来具体说下吧
- 方案一:直接合并到第一个smali文件夹中,然后对这个smali文件中的smali代码文件进行遍历计算它的方法数,当即将超过限制的放法数(最大方法数可以自己定义大小,不一定是65535,最好方法数不要太满了)后,就进行分包,比如分了一个smali_classes2,但是如何计算samli中的方法数呢,这个就需要去研究下smali的语法了,smali类里面的方法都是.method开头的,可以计算.method的个数来确定一个smali文件的方法数
- 方案二:将需要合并的SDK代码文件,在生成smali文件的时候,就将其方法数算出来,比如我们合并一个apk、aar、jar等可以通过第三方开源dex-method-counts工具计算得到里面的方法数,也可以使用Android SDK自带的dexdump工具执行命令dexdump -f classes.dex | findstr method_ids_size 得到,dexdump工具位于sdk的build-tools/28.0.3下。得到方法数后就保存起来,下次合并的时候就不用再计算了,接着我们遍历解析后母包的samli目录,如果只有smali一个文件,就创建一个smali_classes2目录,然后将SDK的smali合并进去,并记录当前的方法数,这样一个一个进行合并,直到下一个SDK的放法数+待合并的smali目录放法数大于限制数,再创建一个smali_classesX,这样循环合并下去,保证新增的smali目录不超限制,这样有个缺点就是某些smali目录可能放法数不是很多,母包自带的第一个也没有利用起来,但是这个合并的效率很高,不用遍历每个smali代码文件去计算方法数,有点简单粗暴
H:\Sdk\build-tools\28.0.3>dexdump -f classes.dex | findstr method_ids_size method_ids_size : 1388
- 虽然我们合并代码的多dex问题解决了,但是还有一个问题就是,我们在运行的时候可能会出现找不到类的异常ClassNotFindException,程序一运行就奔溃,上面我们不是已经配置了MultiDex.install(context);吗,原因很简单,就是我们的MyApplication 也许继承的是我们SDK的Application,而我们的Application也可能会集成其他的Application,而合并SDK里面的代码可能不在第一个dex里面,这时候我们就需要将MyApplication继承的父类,或者父类的父类找到,然后合并到第一个dex中去
- 首先我们需要解析AndroidManifest.xml 文件得到里面配置的Application类路径,然后定位到smali代码文件中去,然后去查找其父类,如果父类不在第一个smali目录中则移动到一个smali目录中,接着再看移动的这个Application类的父类,一直寻找、移动下去,直到父类是java/lang/Object,就结束了。这样就能保证运行是不会报ClassNotFindException了
- 对于逆向开发的多dex超65535放法数问题,我们只是讨论了一些可行的方案,大家可以自己手动去实现,也许还有更好的方法
Android 的65535放法数超限问题解决方案-AS方式、apk解析合并多dex、smali文件问题
于 2022-05-14 18:48:59 首次发布