React Native Android 生成 APK 时出现重复文件错误

71

当我尝试使用./gradlew installRelease生成 Android APK 时,我在控制台上收到此错误:

~/React-Native/mockingbird/android/app/build/intermediates/res/merged/release/drawable-mdpi-v4/src_resources_img_loading.gif: error: Duplicate file.
~/React-Native/mockingbird/android/app/build/intermediates/res/merged/release/drawable-mdpi/src_resources_img_loading.gif: Original is here. The version qualifier may be implied.

我尝试了通过Android Studio的Build->Clean Project功能,然后再次运行./gradlew installRelease命令;但是这也没有起作用。

另外,我尝试删除build文件夹,但是这也没有帮助。


这是我也遇到的一个非常奇怪的问题。其中一个快速解决方案是完全删除可绘制资源(或将其移动到项目目录之外),然后重新构建项目。 - The Dreams Wind
1
@AleksandrMedvedev 谢谢您的回答,但在这里不起作用。 - Shongsu
请检查我对重复问题的答案:https://dev59.com/uVQK5IYBdhLWcg3wGMIP#54066718 - Fareed Alnamrouti
简单解决方案:https://dev59.com/s-k5XIcBkEYKwwoY7ODv#55245362 - MAhipal Singh
10个回答

147

给你一些提示,希望能有所帮助。

更新为 "react": "16.7.0","react-native": "0.57.8"。

自定义 node_modules/react-native/react.gradle 文件来解决 重复文件错误,将以下代码添加到 当前Bundle任务的创建块中(在doFirst块之后)。

doLast {
    def moveFunc = { resSuffix ->
        File originalDir = file("${resourcesDir}/drawable-${resSuffix}");
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
            ant.move(file: originalDir, tofile: destDir);
        }
    }
    moveFunc.curry("ldpi").call()
    moveFunc.curry("mdpi").call()
    moveFunc.curry("hdpi").call()
    moveFunc.curry("xhdpi").call()
    moveFunc.curry("xxhdpi").call()
    moveFunc.curry("xxxhdpi").call()
}

你可以创建脚本来自动完成这个任务。

  1. 创建 android-react-gradle-fix 文件
  2. 创建脚本 android-release-gradle-fix.js 文件
  3. 更新 package.json 文件:

    "scripts": { "postinstall": "node ./android-release-gradle-fix.js" },

就是这样!运行 npm install 来获得惊人的效果。

注意:如果你在像 Jenkins 这样的 CI 上运行 npm install,你可能会遇到错误:postinstall: cannot run in wd %s %s (wd=%s) node => 只需使用 npm install --unsafe-perm 即可。


2
这个解决方案对我有用,但我的问题是,既然这个错误只发生在我与另一个开发人员共享的分支上,那么这个错误最初是如何发生的? - George Miranda
1
非常感谢你,你救了我一命。 虽然我有一个问题,每次运行npm install后是否需要插入这段代码, 修改node_modules中的gradle是个好主意吗?我非常怀疑。 - akaMahesh
2
这个解决方案在使用react native >=0.57时不起作用,@Cawfree的解决方案可以。 - Apperside
我修改了正则表达式,像这样 /\s\/\/ Set up inputs and outputs so gradle can cache the result/g),使其更加健壮。 - Sumi Straessle
2
这对我没有起作用,删除drawable中的文件可行。 - Sumi Straessle
显示剩余8条评论

40

在撰写本文时,React Native的最新版本(>0.57.0)已将Gradle包装器升级到4.4,并将Gradle插件升级到3.1.4,正如changelog所示。这会导致Gradle构建过程将AAPT的结果存储在不同于以前的目录中,而这些结果现在是必需的

Nhan Cao妙招而言,我们需要进行一些微小的修改,以防止重复资源冲突,因为它似乎指向了旧目录而不是应用程序的generated目录。通过更改合并这些重复文件的目标目录,我们仍然可以去重资源。

现有的react.gradle引用了以下路径:

$buildDir === <project-working-directory>/android/app/build

重复的文件路径可能出现在以下位置之间:

file("$buildDir/../src/main/res/drawable-${resSuffix}")
file("$buildDir/generated/res/react/release/drawable-${resSuffix}")

作为一种解决方法,我们可以按照以下方式更新Nhan的解决方案(请务必将其包含在react.gradledoFirst声明之后的currentBundleTask中):

作为变通方法,我们可以更新 Nhan 的解决方案如下(一定要在react.gradledoFirst声明后以currentBundleTask的形式包含此内容):

(Two possible translations depending on whether the content is meant for technical or general audience.)
doLast {
    def moveFunc = { resSuffix ->
        File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
            ant.move(file: originalDir, tofile: destDir);
        }
    }
    moveFunc.curry("ldpi").call()
    moveFunc.curry("mdpi").call()
    moveFunc.curry("hdpi").call()
    moveFunc.curry("xhdpi").call()
    moveFunc.curry("xxhdpi").call()
    moveFunc.curry("xxxhdpi").call()
}

如果您的应用程序也依赖于/raw资源,则下面概述的方法应该能够帮助您:

doLast {
    def moveFunc = { resSuffix ->
        File originalDir = file("$buildDir/generated/res/react/release/${resSuffix}");
        if (originalDir.exists()) {
            File destDir = file("$buildDir/../src/main/res/${resSuffix}");
            ant.move(file: originalDir, tofile: destDir);
        }
     }
     moveFunc.curry("drawable-ldpi").call()
     moveFunc.curry("drawable-mdpi").call()
     moveFunc.curry("drawable-hdpi").call()
     moveFunc.curry("drawable-xhdpi").call()
     moveFunc.curry("drawable-xxhdpi").call()
     moveFunc.curry("drawable-xxxhdpi").call()
     moveFunc.curry("raw").call()
}

如果您的应用程序除了 release 之外还使用自定义的“构建类型”,例如preReleasestagingRelease(Android Gradle Plugin允许您在 build.gradle 中定义它们),则应像以下更改 originalDir 变量:

# from
File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");
# to 
File originalDir = file("$buildDir/generated/res/react/${targetName}/drawable-${resSuffix}");

2
对于使用productFlavors构建apk的任何人,此答案中的originalDir应相应更新;例如我的路径为"$buildDir/generated/res/react/develop/release/drawable-${resSuffix}"。 - siripan
1
哇,谢谢你救了我的一天,这真的很有帮助和感激。 - Ayush Katuwal

37

删除你可能拥有的文件:

android/app/src/main/res/drawable-mdpi/
android/app/src/main/res/drawable-xhdpi/
android/app/src/main/res/drawable-xxhdpi/

再次运行构建,这为我解决了问题。


2
我清空了整个res/文件夹并备份了它。然后我只添加了mipmap/ic_launcher.pngvalues/,其中包括strings.xmlstyles.xml。 现在,在度过了今年最令人沮丧的一天半之后,它终于可以正常工作了。 - ThaJay
3
它完美地为我工作了...我已经解决了一个下午了...非常感谢。 - Mandar Belkunde
3
谢谢,这对我有帮助。我通常只使用命令行进行构建,在Android Studio中打开项目时没有注意到它创建了这些目录,从而导致资源重复。 - laurent
Android资源链接失败。:/ - matt
对我来说有效-删除文件夹android/app/src/main/res/raw - Oleg Reym

20

在运行./gradlew assembleRelease之前不要运行react-native bundle

就我自己而言,在运行./gradlew assembleRelease之前我运行了react-native bundle,结果遇到了类似的重复错误信息。

查看./gradlew assembleRelease 的输出,我可以看出它会自己构建JS包(感谢你在build.gradle文件中使用apply from: "../node_modules/react-native/react.gradle"),因此没有必要在手动运行react-native bundle之前进行这个操作。

如果我直接在运行./gradlew assembleRelease之前不运行react-native bundle,一切都很顺利。

我测试了发布APK,JS包正常加载,包括所有图像。

我的唯一担心是是否会创建源代码映射--sourcemap-output(用于Bugsnag)。如果没有,我确定有办法让./gradlew assembleRelease也生成它们。我只是还没有测试过。


对我没用。被接受的答案解决了我的问题,但还必须将android.enableAapt2=false放入gradle.properties文件中。 - zszep
回到我的答案。这曾经可以解决问题,但即使在assembleRelease之前删除bundle,它在React Native 0.57.8上也不再起作用。 - Joshua Pinter
3
嗯,回到这里,对我来说仍然是正确的答案。我遇到的唯一重复资源错误与 react-navigation 相关。我们使用的是旧版本 2.3.1,升级到 2.18.3 后错误消失了。但是,在运行 ./gradlew assembleRelease 之前捆绑 react-native JS 仍会导致错误。 - Joshua Pinter
现在回来,您永远不需要手动捆绑js,assembleRelease会为您完成,所以您是正确的。(另外,您可以删除index.android.bundle,没有必要,只有在通过Android Studio构建时才需要) - Hugo Gresse

6
为了让我的React Native 0.57.5构建工作正常,我使用了Mapsy的回答,并进行了轻微的改进。我需要能够为多个风格构建,并且通常尽量避免硬编码。在查看我的react.gradle文件时,我发现它定义了以下变量:
def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")

因此,不要像这样在路径中硬编码构建类型/风味:

File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");

我改用resourcesDir变量来设置originalDir路径,代码如下:

File originalDir = file("${resourcesDir}/drawable-${resSuffix}");

因此,我的 doLast 看起来像这样:
doLast {
            def moveFunc = { resSuffix ->
                File originalDir = file("${resourcesDir}/drawable-${resSuffix}");
                if (originalDir.exists()) {
                    File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
                    ant.move(file: originalDir, tofile: destDir);
                }
            }
            moveFunc.curry("ldpi").call()
            moveFunc.curry("mdpi").call()
            moveFunc.curry("hdpi").call()
            moveFunc.curry("xhdpi").call()
            moveFunc.curry("xxhdpi").call()
            moveFunc.curry("xxxhdpi").call()
        }

5

对于我来说,删除文件夹android/build并再次运行./gradlew assembleRelease有效。


4
我成功的方法是在 package.json 文件中添加这个额外的命令,并使用它来进行构建:
    "android-build-release": "cd ./android && rm -rf app/src/main/res/drawable* && ./gradlew app:assembleRelease",


非常顺利地工作,在 0.56.0 及更高版本上,所选的解决方案(更新 react.gradle)无效。这个才是真正应该选择的正确方案。 - Blue Bot

2

1.删除drawable-xxx文件夹

2.删除src->main->res文件夹中的raw文件夹

3.在终端中运行以下命令:

react-native bundle --dev false --platform android --entry-file index.js --bundle-output ./android/app/src/main/assets/index.android.bundle --assets-dest ./android/app/src/main/res_temp

4. 然后使用终端或Android Studio使用密钥库、别名和密码生成签名APK。


0

解决错误的方法是:

  • 将您的图像放在android/app/src/main/res/drawable文件夹中(如果不存在,则创建“drawable”文件夹)
  • 删除所有具有后缀的可绘制文件夹(即drawable-hdpi,drawable-mdpi等)
  • 不要捆绑资产(即不要运行:react-native bundle ...)
  • 运行Gradle的assembleRelease

然而,这并不是一个理想的解决方案,因为每个图像只会有一个分辨率适用于所有设备尺寸。对于设备过大的图像,它会留下缩小到设备大小的问题,导致下载大小和速度问题,对于设备过小的图像,它会导致图像质量降低。

我发现的一个方便的解决方案是使用名为Android Drawable Importer的Android Studio插件。安装后使用它:

  • 在Android Studio中打开您的项目,并导航到:android/app/src/main/res/drawable
  • 右键单击文件夹,选择:New > Batch Drawable Import
  • 选择和配置要导入的图像资产

该插件将处理可绘制文件夹和正确图像大小的生成。导入图像后,您应该能够运行Gradle的assembleRelease而不会出现重复资源错误。


0

如果有其他人遇到这个问题,而doLast的解决方案不起作用,那是因为在运行bundleRelease之前,你不必再运行react-native bundle了。由于我找到了很多帖子都说doLast可以解决这个问题,所以我花了几个小时来解决它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接