React Native:错误:重复资源 - Android

Jef*_*jan 11 android-gradle-plugin react-native react-native-android

我试图从Android创建一个发布apk文件,但当我创建一个PNG图像的发布apk时,我收到Duplicate Resource错误.最初我认为这种情况正在发生,因为我在现有项目中犯了一个错误,但是当我用单个Image组件本身创建一个新项目时,我收到了Duplicate Resource错误.以下是我遵循的步骤

  1. 创建一个应用程序 react-native init demo
  2. 在项目根文件夹中创建资产文件夹.
  3. PNG在assets文件夹中添加图像.
  4. 现在Image用上面的PNG图像实现组件.
  5. 现在使用cmd捆绑它

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

  6. 然后使用Generate Signed APKfrom 生成release apk Android Studio.

这将抛出以下错误:

[drawable-mdpi-v4/assets_mario] /Users/jeffreyrajan/Tutorials/RN/errorCheck/android/app/src/main/res/drawable-mdpi/assets_mario.png [drawable-mdpi-v4/assets_mario] /Users/jeffreyrajan/Tutorials/RN/errorCheck/android/app/build/generated/res/react/release/drawable-mdpi-v4/assets_mario.png: Error: Duplicate resources
:app:mergeReleaseResources FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:mergeReleaseResources'.
> [drawable-mdpi-v4/assets_mario] /Users/jeffreyrajan/Tutorials/RN/errorCheck/android/app/src/main/res/drawable-mdpi/assets_mario.png   [drawable-mdpi-v4/assets_mario] /Users/jeffreyrajan/Tutorials/RN/errorCheck/android/app/build/generated/res/react/release/drawable-mdpi-v4/assets_mario.png: Error: Duplicate resources

* 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.

* Get more help at https://help.gradle.org

BUILD FAILED in 22s
Run Code Online (Sandbox Code Playgroud)

注意:当你生成一个release apk没有任何PNG图像时,你不会得到任何错误,它会创建你release apk.

这是其他文件代码.

App.js

import React, {Component} from 'react';
import {Platform, StyleSheet, Image, View} from 'react-native';

export default class App extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Image source={require('./assets/mario.png')} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});
Run Code Online (Sandbox Code Playgroud)

的package.json

{
  "name": "errorCheck",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest"
  },
  "dependencies": {
    "react": "16.6.0-alpha.8af6728",
    "react-native": "0.57.4"
  },
  "devDependencies": {
    "babel-jest": "23.6.0",
    "jest": "23.6.0",
    "metro-react-native-babel-preset": "0.49.0",
    "react-test-renderer": "16.6.0-alpha.8af6728"
  },
  "jest": {
    "preset": "react-native"
  }
}
Run Code Online (Sandbox Code Playgroud)

对此有何解决方案?

更新:

这是其他细节

classpath 'com.android.tools.build:gradle:3.1.4'

ext {
        buildToolsVersion = "27.0.3"
        minSdkVersion = 16
        compileSdkVersion = 27
        targetSdkVersion = 26
        supportLibVersion = "27.1.1"
    }

distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
Run Code Online (Sandbox Code Playgroud)

试过 Android Studio 3.0, 3.0.1, 3.1, 3.1.4 & 3.2

lim*_*o93 45

用于生成调试 apk

"debug-build": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/ && cd android && ./gradlew assembleDebug && cd .."
Run Code Online (Sandbox Code Playgroud)

用于生成发布 apk

"release-build": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/build/intermediates/res/merged/release/ && rm -rf android/app/src/main/res/drawable-* && rm -rf android/app/src/main/res/raw/* && cd android && ./gradlew assembleRelease && cd .."
Run Code Online (Sandbox Code Playgroud)

通过在package.json文件的脚本部分使用上述代码,对于我的react-native >= 0.61.2项目工作正常。

  • 这种方法应该是公认的答案。但是,您也许可以使用“./gradlew clean”来代替。 (2认同)
  • windows`“debug-build”:“react-native包--platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle - -assets-dest android/app/src/main/res/ &amp;&amp; cd android &amp;&amp; ./gradlew assembleDebug &amp;&amp; cd app/src/main/res/ &amp;&amp; rmdir /S /Q raw &amp;&amp; rmdir /S /Q drawable-mdpi"` `“release-build”:“react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --资产目标 android/app/build/intermediates/res/merged/release/ &amp;&amp; cd android &amp;&amp; ./ (2认同)

Jef*_*jan 44

在尝试了很多解决方案后,我发现只有两个解决方案正在运行.他们来了

解决方案1:

捆绑后drawable从中删除文件夹Android Studio.你可以找到这个android/app/src/main/res/drawable

解决方案2:

在此解决方案中,您无需删除任何可绘制文件夹.只需在您可以在路径下找到的react.gradle文件中添加以下代码node_modules/react-native/react.gradle

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()
}
Run Code Online (Sandbox Code Playgroud)

作为参考,我将在此处添加完整的react.gradle文件代码

import org.apache.tools.ant.taskdefs.condition.Os

def config = project.hasProperty("react") ? project.react : [];

def cliPath = config.cliPath ?: "node_modules/react-native/local-cli/cli.js"
def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
def entryFile = config.entryFile ?: "index.android.js"
def bundleCommand = config.bundleCommand ?: "bundle"
def reactRoot = file(config.root ?: "../../")
def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ;


afterEvaluate {
    android.applicationVariants.all { def variant ->
        // Create variant and target names
        def targetName = variant.name.capitalize()
        def targetPath = variant.dirName

        // React js bundle directories
        def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
        def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")

        def jsBundleFile = file("$jsBundleDir/$bundleAssetName")

        // Additional node and packager commandline arguments
        def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
        def extraPackagerArgs = config.extraPackagerArgs ?: []

        def currentBundleTask = tasks.create(
            name: "bundle${targetName}JsAndAssets",
            type: Exec) {
            group = "react"
            description = "bundle JS and assets for ${targetName}."

            // Create dirs if they are not there (e.g. the "clean" task just ran)
            doFirst {
                jsBundleDir.deleteDir()
                jsBundleDir.mkdirs()
                resourcesDir.deleteDir()
                resourcesDir.mkdirs()
            }

            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()
            }

            // Set up inputs and outputs so gradle can cache the result
            inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
            outputs.dir jsBundleDir
            outputs.dir resourcesDir

            // Set up the call to the react-native cli
            workingDir reactRoot

            // Set up dev mode
            def devEnabled = !(config."devDisabledIn${targetName}"
                || targetName.toLowerCase().contains("release"))

            def extraArgs = extraPackagerArgs;

            if (bundleConfig) {
                extraArgs = extraArgs.clone()
                extraArgs.add("--config");
                extraArgs.add(bundleConfig);
            }

            if (Os.isFamily(Os.FAMILY_WINDOWS)) {
                commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
                    "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
            } else {
                commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
                    "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, *extraArgs)
            }

            enabled config."bundleIn${targetName}" ||
                config."bundleIn${variant.buildType.name.capitalize()}" ?:
                targetName.toLowerCase().contains("release")
        }

        // Expose a minimal interface on the application variant and the task itself:
        variant.ext.bundleJsAndAssets = currentBundleTask
        currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
        currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)

        // registerGeneratedResFolders for Android plugin 3.x
        if (variant.respondsTo("registerGeneratedResFolders")) {
            variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
        } else {
            variant.registerResGeneratingTask(currentBundleTask)
        }
        variant.mergeResources.dependsOn(currentBundleTask)

        // packageApplication for Android plugin 3.x
        def packageTask = variant.hasProperty("packageApplication")
            ? variant.packageApplication
            : tasks.findByName("package${targetName}")

        def resourcesDirConfigValue = config."resourcesDir${targetName}"
        if (resourcesDirConfigValue) {
            def currentCopyResTask = tasks.create(
                name: "copy${targetName}BundledResources",
                type: Copy) {
                group = "react"
                description = "copy bundled resources into custom location for ${targetName}."

                from resourcesDir
                into file(resourcesDirConfigValue)

                dependsOn(currentBundleTask)

                enabled currentBundleTask.enabled


            }

            packageTask.dependsOn(currentCopyResTask)
        }

        def currentAssetsCopyTask = tasks.create(
            name: "copy${targetName}BundledJs",
            type: Copy) {
            group = "react"
            description = "copy bundled JS into ${targetName}."

            if (config."jsBundleDir${targetName}") {
                from jsBundleDir
                into file(config."jsBundleDir${targetName}")
            } else {
                into ("$buildDir/intermediates")
                into ("assets/${targetPath}") {
                    from jsBundleDir
                }

                // Workaround for Android Gradle Plugin 3.2+ new asset directory
                into ("merged_assets/${targetPath}/merge${targetName}Assets/out") {
                    from jsBundleDir
                }
            }

            // mergeAssets must run first, as it clears the intermediates directory
            dependsOn(variant.mergeAssets)

            enabled currentBundleTask.enabled
        }

        packageTask.dependsOn(currentAssetsCopyTask)
    }
}
Run Code Online (Sandbox Code Playgroud)

图片 来源: ZeroCool00 mkchx

  • @Jeffrey-Rajan - 我想给你(以及 ZeroCool00 和 mkchx)信用作为我对最近合并的 react-native 所做 PR 的灵感 - https://github.com/facebook/react-native/ commit/962437fafd02c936754d1e992479056577cafd05 - 我扩展了你上面提出的补丁来处理口味,现在它在 react-native@master 上,所以希望这不会在未来咬人。 (2认同)
  • 对我来说,修改 React 文件(`node_modules` 文件夹咳咳......)似乎很极端。在使用流氓解决方案之前,请确保检查自己的设置。可能很简单:/sf/answers/3471273151/ (2认同)
  • 请不要使用解决方案 #2。开发人员不应在“node_modules”中编辑代码,因为此文件夹中安装的软件包是生成的,并且当软件包升级/卸载时,添加的代码将被覆盖/丢失。 (2认同)

tol*_*tra 11

这个解决方案对我有用

rm -rf ./android/app/src/main/res/drawable-*

rm -rf ./android/app/src/main/res/raw
Run Code Online (Sandbox Code Playgroud)

  • 解释如何/为什么解决问题将使这成为一个更好的答案...... (9认同)
  • 看起来这只是删除了您的资产。 (7认同)
  • 如果我在那里使用资产怎么办?我不知道为什么我必须删除里面的东西? (5认同)
  • 为什么我们必须删除这些目录?这些是 nativ android 项目所需资产的有效目录。如果我删除它,那么使用这些可绘制对象的任何布局都会被破坏 (2认同)
  • 删除“drawable-*”有效并解决了问题。对于我们来说,删除“/raw”会导致构建失败。我们那里有两个 ssl 证书,在下一个版本中不会自动复制。 (2认同)

DrC*_*ord 9

接受的答案将起作用,但是它没有考虑到修改节点包意味着如果您更新您的更改将丢失(以及违反最佳实践,您应该以某种方式扩展模块)。

这最初来自React-native android 发布错误:重复资源

  1. 在项目的“android”文件夹({project-root}/android/fixAndroid)中创建文件夹“fixAndroid”。

  2. 在项目的“fixAndroid”文件夹中创建文件 android-gradle-fix ({project-root}/android/fixAndroid/android-gradle-fix)。

            doLast {
        def moveFunc = { resSuffix ->
            File originalDir = file("${resourcesDir}/drawable-${resSuffix}")
            if (originalDir.exists()) {
                File destDir = file("${resourcesDir}/drawable-${resSuffix}-v4")
                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()
    }
    
    // Set up inputs and outputs so gradle can cache the result
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在您创建的“fixAndroid”文件夹中创建文件 android-release-fix.js:

            const fs = require('fs')
    
        try {
                var curDir = __dirname
                var rootDir = process.cwd()
    
                var file = `${rootDir}/node_modules/react-native/react.gradle`
                var dataFix = fs.readFileSync(`${curDir}/android-gradle-fix`, 'utf8')
                var data = fs.readFileSync(file, 'utf8')
    
                var doLast = "doLast \{"
                if (data.indexOf(doLast) !== -1) {
                    throw "Already fixed."
                }
    
                var result = data.replace(/\/\/ Set up inputs and outputs so gradle can cache the result/g, dataFix);
                fs.writeFileSync(file, result, 'utf8')
                console.log('Android Gradle Fixed!')
            } catch (error) {
                console.error(error)
            }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 将脚本添加到 package.json 脚本部分:

    "postinstall": "node ./android/fixAndroid/android-release-fix.js"
    
    Run Code Online (Sandbox Code Playgroud)

这将找到“android-gradle-fix”文件的内容并将其插入到 node_modules/react-native/react.gradle 中。

  1. 从项目的根目录运行 npm install。
  2. 从项目的根目录运行 rm -rf android/app/src/main/res/drawable-* 。

现在,您可以在控制台或 Android Studio 中将版本与 React Native 捆绑在一起:

反应本机命令行

  1. cd {project-root}/android
  2. ./gradlew/bundleRelease

安卓工作室

  1. 在 Android Studio 中打开文件夹 android 并构建项目。
  2. 选择构建/生成签名的 APK 以构建发布。


小智 9

谁在 RN 中面临同样的问题!我认为这个问题已经存在这么长时间真是太糟糕了,但我想在研究了不同的解决方案后分享解决它的方法。

Jeffrey Rajan 关于这里可能的解决方案是绝对正确的/sf/answers/3728236571/

我认为更改react.gradle文件非常糟糕,node_modules并且会导致此 RN 项目的维护出现许多不同的问题。所以我建议选择第一个选项 - 在运行构建之前使用 bash 命令删除该文件夹。

我想分享我在我的项目中所做的事情,也许您可​​以重用相同的方法:

// ./package.json

...
scripts: {
   "build": "react-native bundle --platform android 
             --dev false
             --entry-file index.js
             --bundle-output android/app/src/main/assets/index.android.bundle
             --assets-dest android/app/src/main/res/
          && rm -rf ./android/app/src/main/res/drawable-mdpi/
          && rm -rf ./android/app/src/main/res/raw/",

   "release": "yarn build && cd ./android && ./gradlew bundleRelease"
}
...
Run Code Online (Sandbox Code Playgroud)

并且通过执行发布正在运行yarn release

这些行非常重要:

...
&& rm -rf ./android/app/src/main/res/drawable-mdpi/
&& rm -rf ./android/app/src/main/res/raw/
...
Run Code Online (Sandbox Code Playgroud)

它们从运行build之前的步骤中删除重复的资源bundleRelease。该解决方案使用 RN 0.57、0.58、0.59 和 0.60 进行测试

享受!


小智 6

tolotrasmile的答案对我有用。

我将它包含在我的小 bash 脚本中,每当我想要构建和安装 Android 时我都会运行该脚本

cd "$PROJECT_DIRECTORY"
react-native bundle \
  --platform android \
  --dev false \
  --entry-file index.js \
  --bundle-output android/app/src/main/assets/index.android.bundle \
  --assets-dest android/app/src/main/res
cd ..

rm -rf "$PROJECT_DIRECTORY"/android/app/src/main/res/drawable-*
rm -rf "$PROJECT_DIRECTORY"/android/app/src/main/res/raw

cd "$PROJECT_DIRECTORY"/android/
./gradlew clean
./gradlew assembleRelease
cd ../../

adb install -r "$PROJECT_DIRECTORY"/android/app/build/outputs/apk/release/app-release.apk
Run Code Online (Sandbox Code Playgroud)


Aij*_*ang 6

  1. 删除drawable-xxx文件夹
  2. 删除原始

src -> main -> res文件夹内然后

  1. 在终端中运行此命令:

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

  1. 然后使用终端或 android studio 使用密钥库、别名和密码生成签名的 apk


小智 5

对于最新版本的 React-Native 和 gradle,你不需要捆绑你的资产。完成代码后,只需 cd 进入 android 文件夹并运行:

./gradlew assembleRelease
Run Code Online (Sandbox Code Playgroud)

执行上述命令时,资产会自动捆绑。出现重复资源错误是因为您之前已明确捆绑并再次运行上述命令包,因此出现错误。


Ish*_*san 5

就我而言,在Jaffrey 的答案中添加几行后它就起作用了

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()

    File originalDir = file("$buildDir/generated/res/react/release/raw");
    if (originalDir.exists()) {
        File destDir = file("$buildDir/../src/main/res/raw");
        ant.move(file: originalDir, tofile: destDir);
    }
}
Run Code Online (Sandbox Code Playgroud)