32

我想使用 Gradle 为 4 个不同的 Android CPU 处理器架构(armeabi armeabi-v7a x86 mips)构建 4 个单独的 apk。

我在libs文件夹中有为 4 个 CPU 架构构建的原生 OpenCV 库。

libs
    -armeabi
    -armeabi-v7a
    -x86
    -mips

我希望每个 apk 只包含对应正确 CPU 架构的 OpenCV 库。

当前的构建脚本如下:

apply plugin: 'android'

dependencies {
    compile fileTree(dir: 'libs', include: '*.jar')
    compile project(':workspace:OpenCV4Android:sdk:java')
}

android {
    compileSdkVersion 11
    buildToolsVersion "18.1.0"

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        // Move the tests to tests/java, tests/res, etc...
        instrumentTest.setRoot('tests')

        debug.setRoot('build-types/debug')
        release.setRoot('build-types/release')

        flavorGroups "abi", "version"
        productFlavors {
            x86 {
                flavorGroup "abi"
            }
            arm {
                flavorGroup "abi"
            }
            mips {
                flavorGroup "abi"
            }
        }

    }
}

有人可以帮我解决这个问题吗?

干杯,

4

4 回答 4

25

从 Android Gradle 插件版本 13 开始,您现在可以使用新的“拆分”机制生成单独的 APK。你可以在这里阅读。

放置 .so 文件的默认文件结构是:

src
-main
  -jniLibs
    -armeabi
      -arm.so
    -armeabi-v7a
      -armv7.so
    -x86
      -x86.so
    -mips
      -mips.so

请注意,.so 文件的名称并不重要,只要它具有 .so 扩展名即可。

然后在您的 Gradle 构建文件中:

android {
...
splits {
abi {
  enable true
  reset()
  include 'x86', 'armeabi-v7a', 'mips', 'armeabi'
  universalApk false
  }
 }
}

// map for the version code
ext.versionCodes = ['armeabi-v7a':1, mips:2, x86:3]

import com.android.build.OutputFile

android.applicationVariants.all { variant ->
    // assign different version code for each output
    variant.outputs.each { output ->
        output.versionCodeOverride =
            project.ext.versionCodes.get(output.getFilter(OutputFile.ABI)) * 1000000 + android.defaultConfig.versionCode
    }
}

请注意,上面 ext.versionCodes 中的版本代码在很大程度上是不相关的,这是为每种 ABI 类型添加唯一的偏移量,因此版本代码不会冲突。

于 2014-09-30T20:25:23.340 回答
15

The split ABI APK solution for gradle is the simplest I have found so far. @withoutclass has a good writeup here: https://stackoverflow.com/a/26129447/254573 I had to reference the Android documentation since this is a new feature that can still change: http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits

However, I ended up having to abandon this simple implementation since I needed to support both a fat build and architecture specific builds. You might encounter this same issue if you support both the Google Play store (which supports architecture specific APKs) and the Amazon Appstore (which supports only fat APKs).

It might be possible to do this with split APKs if you can add a flavor component, but as of now split+flavor is not yet supported: https://code.google.com/p/android/issues/detail?id=76469

I ended up using the abiFilter, see sample code below:

android {
    flavorDimensions "abi"

    productFlavors {
        fat {
            flavorDimension "abi"
            ndk {
                abiFilters "x86", "armeabi-v7a", "armeabi"
                versionCode = 0;
            }
        }
        arm {
            flavorDimension "abi"
            ndk {
                abiFilter "armeabi"
                versionCode = 1;
            }
        }
        armv7a {
            flavorDimension "abi"
            ndk {
                abiFilter "armeabi-v7a"
                versionCode = 3;
            }
        }
        x86 {
            flavorDimension "abi"
            ndk {
                abiFilter "x86"
                versionCode = 6;
            }
        }
    }
}

// Each APK needs a different version code when submitted to Google,
// bump the versionCode we set in defaultConfig
android.applicationVariants.all { variant ->
    // Ugly hard coded flavorDimensions position
    // If you have more than one flavorDimension, make sure to target the position for "abi"
    def abiVersion = variant.productFlavors.get(0).versionCode

    variant.mergedFlavor.versionCode = abiVersion * 1000 + android.defaultConfig.versionCode
}

Update Using universalApk set to true solves this solution, simply adds time to build each apk.

android {
    // Rest of Gradle file
        splits {
            abi {
            enable true
            reset()
            include 'armeabi', 'armeabi-v7a', 'x86'
            universalApk true
        }
    }
}

//Ensures architecture specific APKs have a higher version code
//(otherwise an x86 build would end up using the arm build, which x86 devices can run)
ext.versionCodes = [armeabi:1, 'armeabi-v7a':3, x86:6]

android.applicationVariants.all { variant ->
    // assign different version code for each output
    variant.outputs.each { output ->
        int abiVersionCode = project.ext.versionCodes.get(output.getFilter(OutputFile.ABI)) ?: 0
        output.versionCodeOverride = (abiVersionCode * 1000) + android.defaultConfig.versionCode
    }
}
于 2014-12-03T22:46:41.943 回答
4

更新 - 自从发布这篇文章以来,gradle 构建过程已经取得了很大进展,因此这个答案可能不是推荐的最佳实践,新的变化甚至可能会破坏它。使用你自己的判断力。

为此,首先,您必须将本机库分别放在以下文件夹层次结构中

lib
 -armeabi
  -arm.so
  -*.so

-

lib
 -x86
  -x86.so
  -*.so

然后压缩lib(没有's')文件夹(例如arm.zip和x86.zip)并将'zip'扩展名重命名为'jar'(例如arm.jar和x86.jar)。将这些罐子放在适当的文件夹中(例如 armeabi/libs 和 x86/libs)。现在我们将包括每种风味的依赖关系。但是我们不能使用“编译文件'....'”。我们必须使用“flavorCompile 文件'...'”

例如

    flavorGroups 'abi'
        productFlavors {
            arm {
                flavorGroup 'abi'
                dependencies {
                    armCompile files('arm/libs/armeabi.jar')
                }
            }
            x86 {
                flavorGroup 'abi'
                dependencies {
                    x86Compile files('x86/libs/x86.jar')
                }
            }

    }

====

这里是更复杂的环境。您不仅拥有处理器架构变体,而且还拥有处理器的调试库(.jar、 .so)。这里的例子有用于 Arm 调试的 Debug.jar 和用于 Arm 发布的 NonDebug.jar;和 *.so 适用于 Arm 和 X86。这种配置可以通过使用 gradle ExtraPropertiesExtension来实现请在此处阅读我的 SO 答案https://stackoverflow.com/a/19941684/319058,以了解如何构建调试文件夹。

android {
compileSdkVersion 18
buildToolsVersion "19.0.0"

final DEBUG_ROOT = "build-types/debug"
final RELEASE_ROOT = "build-types/release"
project.ext.set("projRoot", "")
buildTypes {
    debug {
        project.projRoot = DEBUG_ROOT

        dependencies {
            debugCompile files(DEBUG_ROOT+"/libs/Debug.jar")
        }
    }

    release {
        project.projRoot = RELEASE_ROOT
        dependencies {
            releaseCompile files(RELEASE_ROOT+"/libs/NonDebug.jar")
        }
        runProguard true
        proguardFile 'proguard.cfg'
    }
}
sourceSets {

    final PROJ_ROOT = project.ext.get("projRoot")
    final BUILD_TYPE_RES = PROJ_ROOT + "/res"
    main {
        manifest.srcFile 'src/main/AndroidManifest.xml'
        java.srcDirs = ['src/main/java']
        //resources.srcDirs = ['src/main']
        //aidl.srcDirs = ['src/main']
        //renderscript.srcDirs = ['src/main']
        res.srcDirs = ['src/main/res',BUILD_TYPE_RES]
        assets.srcDirs = ['src/main/assets']
    }

    flavorGroups 'abi'
    productFlavors {
        arm {
            flavorGroup 'abi'
            final ARM_LIB_PATH = PROJ_ROOT + "/arm/libs/armeabi.jar"
            dependencies {
                armCompile files(ARM_LIB_PATH)
            }
        }
        x86 {
            flavorGroup 'abi'
            final X86_LIB_PATH = PROJ_ROOT + "/x86/libs/x86.jar"
            dependencies {
                x86Compile files(X86_LIB_PATH)
            }
        }

    }

    // Move the tests to tests/java, tests/res, etc...
    instrumentTest.setRoot('tests')

    // Move the build types to build-types/<type>
    // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
    // This moves them out of them default location under src/<type>/... which would
    // conflict with src/ being used by the main source set.
    // Adding new build types or product flavors should be accompanied
    // by a similar customization.
    debug.setRoot(DEBUG_ROOT)
    release.setRoot(RELEASE_ROOT)
}

}

于 2013-11-12T00:39:17.273 回答
3

我没有 gradle 答案,但我想我现在对任何 Android 构建工具都有一个通用的答案。这是我关于如何为每个支持的处理器架构创建单独的 APK 文件的想法:

  1. 使用您使用的任何工具构建您的 APK,包含您支持的所有本机代码库,例如 armeabi、armeabi-v7a、x86 和 mips。我将其称为“原始”APK 文件。

  2. 使用任何 zip/unzip 实用程序将您的原始 APK 解压缩到一个空文件夹中,最好使用命令行工具,以便您以后可以使用 shell 脚本或批处理文件将其自动化。

  3. 在原始APK解压到的文件夹中,删除META-INF子文件夹(这个包含签名,我们需要在所有修改后重新签名APK,所以必须删除原始META-INF)。

  4. 更改为 lib 子文件夹,并删除新 APK 文件中不需要的任何处理器架构的子文件夹。例如,只保留“x86”子文件夹来为英特尔凌动处理器制作 APK。

  5. 重要提示:针对不同架构的每个 APK,在 AndroidManifest.xml 中必须有不同的“versionCode”编号,例如 armeabi-v7a 的版本代码必须略高于 armeabi 的版本代码(请阅读此处创建多个 APK 的 Google 说明:http://developer.android.com/google/play/publishing/multiple-apks.html)。不幸的是,清单文件在 APK 中是编译后的二进制形式。我们需要一个特殊的工具来修改那里的 versionCode。见下文。

  6. 使用新版本代码修改清单并删除不必要的目录和文件后,重新压缩、签名并对齐较小的 APK(使用 Android SDK 中的 jarsigner 和 zipalign 工具)。

  7. 对您需要支持的所有其他架构重复该过程,创建版本代码略有不同(但版本名称相同)的较小 APK 文件。

唯一突出的问题是在二进制清单文件中修改“versionCode”的方法。很长一段时间我都找不到解决方案,所以最后不得不坐下来编写自己的代码来做到这一点。作为起点,我使用了 Prasanta Paul 的 APKExtractor,http://code.google.com/p/apk-extractor/,它是用 Java 编写的。我是老派,对 C++ 更熟悉,所以我用 C++ 编写的小实用程序“aminc”现在在 GitHub 上:

https://github.com/gregko/aminc

我发布了整个 Visual Studio 2012 解决方案,但整个程序是一个 .cpp 文件,可能可以在任何平台上编译。这是一个示例 Windows .bat 文件,我用它将名为 atVoice.apk 的“胖”apk 拆分为 4 个较小的文件,分别名为 atVoice_armeabi.apk、atVoice_armeabi-v7a.apk、atVoice_x86.apk 和 atVoice_mips.apk。我实际上将这些文件提交到 Google Play(请参阅我在https://play.google.com/store/apps/details?id=com.hyperionics.avar上的应用程序)并且一切正常。另请参阅Jorge Suárez de Lis 的这个 Github 项目,他发布了一个类似的 Linux 脚本。

@echo off
REM    My "fat" apk is named atVoice.apk. Change below to whatever or set from %1
set apkfile=atVoice
del *.apk

REM    My tools build atVoice-release.apk in bin project sub-dir. 
REM    Copy it here for splitting.
copy ..\bin\%apkfile%-release.apk %apkfile%.apk

zip -d %apkfile%.apk META-INF/*

REM ------------------- armeabi ------------------------
unzip %apkfile%.apk AndroidManifest.xml
copy/y %apkfile%.apk %apkfile%.zip
zip -d %apkfile%.zip lib/armeabi-v7a/* lib/x86/* lib/mips/*
aminc AndroidManifest.xml 1
zip -f %apkfile%.zip
ren %apkfile%.zip %apkfile%_armeabi.apk
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore d:\users\greg\.android\Hyperionics.keystore -storepass MyPass %apkfile%_armeabi.apk MyKeyName
zipalign 4 %apkfile%_armeabi.apk %apkfile%_armeabi-aligned.apk
del %apkfile%_armeabi.apk
ren %apkfile%_armeabi-aligned.apk %apkfile%_armeabi.apk

REM ------------------- armeabi-v7a ---------------------
copy/y %apkfile%.apk %apkfile%.zip
zip -d %apkfile%.zip lib/armeabi/* lib/x86/* lib/mips/*
aminc AndroidManifest.xml 1
zip -f %apkfile%.zip
ren %apkfile%.zip %apkfile%_armeabi-v7a.apk
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore d:\users\greg\.android\Hyperionics.keystore -storepass MyPass %apkfile%_armeabi-v7a.apk MyKeyName
zipalign 4 %apkfile%_armeabi-v7a.apk %apkfile%_armeabi-v7a-aligned.apk
del %apkfile%_armeabi-v7a.apk
ren %apkfile%_armeabi-v7a-aligned.apk %apkfile%_armeabi-v7a.apk

REM ------------------- x86 ---------------------
copy/y %apkfile%.apk %apkfile%.zip
zip -d %apkfile%.zip lib/armeabi/* lib/armeabi-v7a/* lib/mips/*
aminc AndroidManifest.xml 9
zip -f %apkfile%.zip
ren %apkfile%.zip %apkfile%_x86.apk
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore d:\users\greg\.android\Hyperionics.keystore -storepass MyPass %apkfile%_x86.apk MyKeyName
zipalign 4 %apkfile%_x86.apk %apkfile%_x86-aligned.apk
del %apkfile%_x86.apk
ren %apkfile%_x86-aligned.apk %apkfile%_x86.apk

REM ------------------- MIPS ---------------------
copy/y %apkfile%.apk %apkfile%.zip
zip -d %apkfile%.zip lib/armeabi/* lib/armeabi-v7a/* lib/x86/*
aminc AndroidManifest.xml 10
zip -f %apkfile%.zip
ren %apkfile%.zip %apkfile%_mips.apk
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore d:\users\greg\.android\Hyperionics.keystore -storepass MyPass %apkfile%_mips.apk MyKeyName
zipalign 4 %apkfile%_mips.apk %apkfile%_mips-aligned.apk
del %apkfile%_mips.apk
ren %apkfile%_mips-aligned.apk %apkfile%_mips.apk


del AndroidManifest.xml
del %apkfile%.apk
:done

格雷格

于 2013-10-23T23:44:36.483 回答