Android 的65535放法数超限问题解决方案-AS方式、apk解析合并多dex、smali文件问题

  • 我们在开发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放法数问题,我们只是讨论了一些可行的方案,大家可以自己手动去实现,也许还有更好的方法

猜你喜欢

转载自blog.csdn.net/qq_19942717/article/details/124514929