Tencent / tinker

Tinker is a hot-fix solution library for Android, it supports dex, library and resources update without reinstall apk.
Other
17.14k stars 3.33k forks source link

编译报主dex超过65536错误 #1310

Open alen252 opened 4 years ago

alen252 commented 4 years ago

异常类型:app编译异常

手机型号:如:Nexus 5(如是编译异常,则可以不填)

手机系统版本:如:Android 5.0 (如是编译异常,则可以不填)

tinker版本:1.9.14.4

gradle版本:5.6

是否使用热更新SDK: 使用了TinkerPatch SDK

系统:如:Windows

'com.android.tools.build:gradle:3.2.1'

堆栈/日志: AGPBI: {"kind":"error","text":"Cannot fit requested classes in the main-dex file (# methods: 76780 \u003e 65536 ; # fields: 75405 \u003e 65536)","sources":[{}],"tool":"D8"}

Task :ezvizRetail:transformDexArchiveWithDexMergerForDebug FAILED java.lang.RuntimeException: java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: G:\Code\V3.22

erge1223 commented 4 years ago

I have same error

erge1223 commented 4 years ago

如果已经分包了在主工程的gradle中加入这两句试试 android.enableD8.desugaring = false android.useDexArchive = false

YummyLau commented 4 years ago

@erge1223 问题解决了吗

erge1223 commented 4 years ago

已经解决了我的事as 的版本问题 按照我上面的配置就解决了,但是我不知道你的是什么情况

YummyLau commented 4 years ago

tinker版本 1.9.14.5 gradle 插件版本 3.2.1 as版本 3.5.1 @erge1223 我在工程的gradle.properties 中申明了

android.enableD8.desugaring = false
android.useDexArchive = false

依然会有一下merge jar的异常

Not including classes with runtime retention annotations in the main dex.
This can cause issues with reflection in older platforms.
:main:transformClassesWithDexForRelease FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':main:transformClassesWithDexForRelease'.
> com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: com.android.dex.DexException: Too many classes in --main-dex-list, main dex capacity exceeded

请指教。

erge1223 commented 4 years ago

分包有没有做

YummyLau commented 4 years ago

分包有没有做

分包指的是 Multidex还是分模块,目前我是多模块,tinker独立模块,也开启 MultiDex

YummyLau commented 4 years ago

@erge1223 我们minSdk为19,低于5.0要自己干预分包吗

erge1223 commented 4 years ago

minSdk低于21手动分包大于21自动分包

erge1223 commented 4 years ago

Multidex分包吧你的tinker 有些优先加载的类要放到主dex文件中 文档中好像有介绍

YummyLau commented 4 years ago

@erge1223 MultiDex.install 不是会自动兼容吗? 你的意思是在MultiDex.install之上,tinker会把一些类主动放入dex里面,可能出现溢出吗?

erge1223 commented 4 years ago

是的

YummyLau commented 4 years ago

是的

好的,感谢答复,我先尝试下。

erge1223 commented 4 years ago

android.enableD8.desugaring = false android.useDexArchive = false关掉d8试一下其他的我也没有什么好的解决方案

YummyLau commented 4 years ago

这两个关闭也不好使了。 @erge1223

com.android.dex.DexException: Too many classes in --main-dex-list, main dex capacity exceeded
YummyLau commented 4 years ago

@erge1223 @alen252 总算可以了,留下踩过坑的记录。 开发环境背景

tinker版本 1.9.14.5
gradle 插件版本 3.2.1
as版本 3.5.1

出现问题

问题1. android所有版本下,项目使用了java8特性,D8处理失败
问题2. 5.0以下版本默认使用官方 MultiDex 处理,出现 “Too mang classes...” 编译失败 

解决问题

解决问题1:关闭D8脱糖,关闭增量处理dex
解决问题2:自主删除 maindexlist文件下的classes

具体解决实现: 主工程下 gradle.properties 添加解决问题1的配置

android.enableD8.desugaring = false
android.useDexArchive = false

主模块下目录(与build.gradle 同级,可自主调整)新增文件 “main_dex_split_for_tinker.gradle” 用于修改maindexlist文件内容。 文件内容如下

project.afterEvaluate {
    //解决开启tinker时打包失败问题  Too many classes in --main-dex-list, main dex capacity exceeded。 exclude_class.txt中配置排除的类
    //一定要验证5.0以下android启动时是否崩溃!
    if (android.defaultConfig.minSdkVersion.getApiLevel() >= 21) {
        return
    }
    if (project.hasProperty("tinkerPatch") == false) {
        return
    }
    def configuration = project.tinkerPatch

    if (!configuration.tinkerEnable) {
        return
    }

    android.applicationVariants.all { variant ->

        def variantName = variant.name.capitalize()

        def multidexTask = project.tasks.findByName("transformClassesWithMultidexlistFor${variantName}")
        if (multidexTask != null) {
            def splitTask = createSplitDexTask(variant);
            multidexTask.finalizedBy splitTask
        }
    }
}

def createSplitDexTask(variant) {
    def variantName = variant.name.capitalize()

    return task("replace${variantName}MainDexClassList").doLast {

        //从主dex移除的列表
        def excludeClassList = []
        File excludeClassFile = new File("${project.projectDir}/exclude_class.txt")
        if (excludeClassFile.exists()) {
            excludeClassFile.eachLine { line ->
                if (!line.trim().isEmpty() && line.startsWith("#") == false) {
                    excludeClassList.add(line.trim())
                }
            }
            excludeClassList.unique()
        }
        def mainDexList = []
        File mainDexFile = new File("${project.buildDir}/intermediates/legacy_multidex_main_dex_list/${variant.dirName}/transformClassesWithMultidexlistFor${variantName}/maindexlist.txt")
        println "${project.buildDir}/intermediates/legacy_multidex_main_dex_list/${variant.dirName}/transformClassesWithMultidexlistFor${variantName}/maindexlist.txt exist : ${mainDexFile.exists()}"
        if (mainDexFile.exists()) {
            mainDexFile.eachLine { line ->
                if (!line.isEmpty()) {
                    mainDexList.add(line.trim())
                }
            }
            mainDexList.unique()
            if (!excludeClassList.isEmpty()) {
                def newMainDexList = mainDexList.findResults { mainDexItem ->
                    def isKeepMainDexItem = true
                    for (excludeClassItem in excludeClassList) {
                        if (mainDexItem.contains(excludeClassItem)) {
                            isKeepMainDexItem = false
                            break
                        }
                    }
                    if (isKeepMainDexItem) mainDexItem else null
                }
                if (newMainDexList.size() < mainDexList.size()) {
                    mainDexFile.delete()
                    mainDexFile.createNewFile()
                    mainDexFile.withWriterAppend { writer ->
                        newMainDexList.each {
                            writer << it << '\n'
                            writer.flush()
                        }
                    }
                }
            }
        }
    }
}

在同级目录新增文件 “exclude_class.txt” 用于指明哪些class需要剔除。(可自定删减,我只写我需要删除的)

#类路径包含如下的,都会建议打包工具不要打到主dex中,但可能还会被打到主dex中。由于所有的业务代码都会在multidex.install后执行,理论上所有的业务代码都可以不在主dex中
com/facebook/fresco
com/facebook/drawee
com/facebook/imageformat
com/facebook/imagepipeline

#暂时只剔除Facebook下代码
com/alibaba
com/taobao
com/eclipsesource/v8

net/sourceforge/pinyin4j/
com/amitshekhar/
net/sqlcipher/
android/arch/persistence/room/
com/tencent/qqlive/
com/alibaba/
okhttp3/
com/github/mikephil/charting/
com/squareup/haha/
com/github/tony19/
org/jetbrains/anko/
kotlin/
com/squareup/
com/airbnb/
android/support/v
com/facebook/fresco/
com/facebook/stetho/
com/google/android/flexbox/
com/sixgod/pluginsdk/

最后在主目录的build.gradle apply下这个上面的脚本就行了。

apply from: 'main_dex_split_for_tinker.gradle'

经过测试,在5.0以上及5.0以下都正常运行了。

erge1223 commented 4 years ago

其实你在debug模式删除无用资源开启混淆应该也可以

YummyLau commented 4 years ago

其实你在debug模式删除无用资源开启混淆应该也可以

目前我测试的都是在release,因为后续项目 jenkins 只针对release版本处理了。 debug模式有区别吗

erge1223 commented 4 years ago

因为我们项目的release版本与debug版本的配置是不一样的,我们项目只在debug模式下才会出现这种问题

YummyLau commented 4 years ago

@erge1223 soga。debug模式我们就没有关注了。一般版本回归之后打发布版本的时候留下个母包就可以了。debug模式下加入tinker可能会让编译速度收到影响呢。

erge1223 commented 4 years ago

可以问题解决了就行

baoxin commented 4 years ago

这问题烦躁的很啊...

alen252 commented 4 years ago

@YummyLau 我的解决办法是: tinker版本 1.9.14 gradle 插件版本 3.2.1 as版本 3.5.1

android.enableD8.desugaring = true android.useDexArchive = true 编译环境是java8,上面两个需要开启 multiDexKeepProguard file("tinkerMultidexKeep.pro") //keep specific classes using proguard syntax tinkerMultidexKeep.pro里面去除了几个类

//禁掉tinker的自动添加multiDexKeepProguard的task gradle.taskGraph.whenReady { tasks.each { task -> if (task.name.equals("tinkerProcessDebugMultidexKeep") || task.name.equals("tinkerProcessReleaseMultidexKeep")) { task.enabled = false } } }

YummyLau commented 4 years ago

@alen252 感谢,后续可以试一下

YummyLau commented 4 years ago

@alen252 tinkerMultidexKeep.pro能提供一下吗

rosuH commented 4 years ago

@erge1223 @alen252 总算可以了,留下踩过坑的记录。 开发环境背景

tinker版本 1.9.14.5
gradle 插件版本 3.2.1
as版本 3.5.1

出现问题

问题1. android所有版本下,项目使用了java8特性,D8处理失败
问题2. 5.0以下版本默认使用官方 MultiDex 处理,出现 “Too mang classes...” 编译失败 

解决问题

解决问题1:关闭D8脱糖,关闭增量处理dex
解决问题2:自主删除 maindexlist文件下的classes

具体解决实现: 主工程下 gradle.properties 添加解决问题1的配置

android.enableD8.desugaring = false
android.useDexArchive = false

主模块下目录(与build.gradle 同级,可自主调整)新增文件 “main_dex_split_for_tinker.gradle” 用于修改maindexlist文件内容。 文件内容如下

project.afterEvaluate {
    //解决开启tinker时打包失败问题  Too many classes in --main-dex-list, main dex capacity exceeded。 exclude_class.txt中配置排除的类
    //一定要验证5.0以下android启动时是否崩溃!
    if (android.defaultConfig.minSdkVersion.getApiLevel() >= 21) {
        return
    }
    if (project.hasProperty("tinkerPatch") == false) {
        return
    }
    def configuration = project.tinkerPatch

    if (!configuration.tinkerEnable) {
        return
    }

    android.applicationVariants.all { variant ->

        def variantName = variant.name.capitalize()

        def multidexTask = project.tasks.findByName("transformClassesWithMultidexlistFor${variantName}")
        if (multidexTask != null) {
            def splitTask = createSplitDexTask(variant);
            multidexTask.finalizedBy splitTask
        }
    }
}

def createSplitDexTask(variant) {
    def variantName = variant.name.capitalize()

    return task("replace${variantName}MainDexClassList").doLast {

        //从主dex移除的列表
        def excludeClassList = []
        File excludeClassFile = new File("${project.projectDir}/exclude_class.txt")
        if (excludeClassFile.exists()) {
            excludeClassFile.eachLine { line ->
                if (!line.trim().isEmpty() && line.startsWith("#") == false) {
                    excludeClassList.add(line.trim())
                }
            }
            excludeClassList.unique()
        }
        def mainDexList = []
        File mainDexFile = new File("${project.buildDir}/intermediates/legacy_multidex_main_dex_list/${variant.dirName}/transformClassesWithMultidexlistFor${variantName}/maindexlist.txt")
        println "${project.buildDir}/intermediates/legacy_multidex_main_dex_list/${variant.dirName}/transformClassesWithMultidexlistFor${variantName}/maindexlist.txt exist : ${mainDexFile.exists()}"
        if (mainDexFile.exists()) {
            mainDexFile.eachLine { line ->
                if (!line.isEmpty()) {
                    mainDexList.add(line.trim())
                }
            }
            mainDexList.unique()
            if (!excludeClassList.isEmpty()) {
                def newMainDexList = mainDexList.findResults { mainDexItem ->
                    def isKeepMainDexItem = true
                    for (excludeClassItem in excludeClassList) {
                        if (mainDexItem.contains(excludeClassItem)) {
                            isKeepMainDexItem = false
                            break
                        }
                    }
                    if (isKeepMainDexItem) mainDexItem else null
                }
                if (newMainDexList.size() < mainDexList.size()) {
                    mainDexFile.delete()
                    mainDexFile.createNewFile()
                    mainDexFile.withWriterAppend { writer ->
                        newMainDexList.each {
                            writer << it << '\n'
                            writer.flush()
                        }
                    }
                }
            }
        }
    }
}

在同级目录新增文件 “exclude_class.txt” 用于指明哪些class需要剔除。(可自定删减,我只写我需要删除的)

#类路径包含如下的,都会建议打包工具不要打到主dex中,但可能还会被打到主dex中。由于所有的业务代码都会在multidex.install后执行,理论上所有的业务代码都可以不在主dex中
com/facebook/fresco
com/facebook/drawee
com/facebook/imageformat
com/facebook/imagepipeline

#暂时只剔除Facebook下代码
com/alibaba
com/taobao
com/eclipsesource/v8

net/sourceforge/pinyin4j/
com/amitshekhar/
net/sqlcipher/
android/arch/persistence/room/
com/tencent/qqlive/
com/alibaba/
okhttp3/
com/github/mikephil/charting/
com/squareup/haha/
com/github/tony19/
org/jetbrains/anko/
kotlin/
com/squareup/
com/airbnb/
android/support/v
com/facebook/fresco/
com/facebook/stetho/
com/google/android/flexbox/
com/sixgod/pluginsdk/

最后在主目录的build.gradle apply下这个上面的脚本就行了。

apply from: 'main_dex_split_for_tinker.gradle'

经过测试,在5.0以上及5.0以下都正常运行了。

@YummyLau 任务名和产物路径貌似有所变动。

我的环境是:

MultiDex Task 为:

transformClassesWithDexBuilderForDebug
transformClassesAndDexWithShrinkResForRelease

产物路径为:

app/build/intermediates/legacy_multidex_main_dex_list/debug/maindexlist.txt
listen2code commented 3 years ago

so, is this bug fix?

duoduo2012 commented 3 years ago

@alen252

下面这段 加 哪里 ? // //禁掉tinker的自动添加multiDexKeepProguard的任务 gradle.taskGraph.whenReady {task.each { task-> if(task.name.equals(“ tinkerProcessDebugMultidexKeep”)|| task.name.equals(“ tinkerProcessReleaseMultidexKeep”)) { task.enabled = false } } }