如何使用 Android Gradle 插件 0.7 配置 NDK

Posted

技术标签:

【中文标题】如何使用 Android Gradle 插件 0.7 配置 NDK【英文标题】:How to configure NDK with Android Gradle plugin 0.7 【发布时间】:2014-01-07 14:32:03 【问题描述】:

新的 android gradle 插件 (0.7) 似乎包含对 NDK 的新支持,但在文档中几乎没有提及它(我发现的唯一参考是一个名为 ndkSanAngeles 的测试)。

看起来 gradle 正在寻找我已包含在我的 PATH 中的 NDK。但是,构建项目失败了

出了什么问题: 任务“:OGLTests:compileDefaultFlavorDebugNdk”执行失败。 NDK 未配置

如何在 gradle 中配置 NDK?

我当前的 build.gradle 如下所示:

task nativeLibsToJar(type: Zip, description: 'create a jar with native libs') 
    destinationDir file("$buildDir/native-libs")
    baseName 'native-libs'
    extension 'jar'
    from fileTree(dir: 'src/main/libs', include: '**/*.so')
    from fileTree(dir: 'src/main/libs', include: '**/gdb*')
    into 'lib/'


tasks.withType(JavaCompile) 
    compileTask -> compileTask.dependsOn nativeLibsToJar


dependencies 
    compile fileTree(dir: "$buildDir/native-libs", include: '*.jar')


android 
    compileSdkVersion 19
    buildToolsVersion '19.0.0'

    defaultConfig 
        minSdkVersion 14
        targetSdkVersion 19
        versionCode 1
        versionName "0.1"

    
    buildTypes 
        release 
            runProguard false
        
        debug 
           // jniDebugBuild true
            runProguard false
            debuggable true
        
    
    productFlavors 
        defaultFlavor 
            proguardFile 'proguard-rules.txt'
        
    

谢谢。

【问题讨论】:

【参考方案1】:

查看 gradle 插件代码,我发现以下内容有助于我同时使用 NDK 和预构建的原生库:

要简单地在预建的原生库中链接,只需在您的任务中添加一个 ndk 部分。例如,我在下面的 productFlavors 中添加了它。 abiFilter 是存储库的文件夹名称。abiFilters 表示逗号分隔列表中的两个库都将添加到您的最终 APK(因此理论上您可以拥有“armeabi”、“armeabi-v7a”、“x86”和“ mips”全部在一个 APK 中,操作系统会在安装时选择支持的架构库):

productFlavors 
    arm 
        ndk 
            abiFilters "armeabi", "armeabi-v7a"
        
    
    x86 
        ndk 
            abiFilter "x86"
        
    

在此示例中,arm 构建将创建一个包含 V5 和 V7A arm 库的 APK,而 x86 构建将创建一个仅包含 x86 库的 APK。这将在您的项目 jniLibs 目录中搜索本机库。 jniLibs 目录应该是旧 jni 目录的结构,即:

[project]/[app]/src/main/jniLibs/armeabi/libmyNative.so
[project]/[app]/src/main/jniLibs/armeabi-v7a/libmyNative.so
[project]/[app]/src/main/jniLibs/x86/libmyNative.so

然后你可以按如下方式在Java中加载它:

static

    loadLibrary("myNative");

现在,假设一个本地库依赖于另一个。您必须(如果将您的 min API 设置为 API 17 或更低版本)首先加载依赖库:

static

    loadLibrary("myDependency");
    loadLibrary("myNative");

您还可以将 ndk 部分放在您的 defaultConfig 或 buildType(例如 debug 或 release 或您可能使用的任何其他内容)中。例如:

buildTypes 
    debug 
        ndk 
            abiFilters "armeabi", "armeabi-v7a"
        
    

预构建是指您下载的第 3 方库或您使用 NDK 工具链或您自己的 ARM 工具链(而不是 ndk-build 脚本本身)构建的库。

在 API 18 中,他们修复了一个长期存在的架构问题,该问题阻止本机 lib 加载器“自动”加载依赖项,因为它不知道应用程序的 lib 目录(安全原因等)。在 API 18 及更高版本中,如果 myNative 依赖于上面的 myDependency,您只需调用 loadLibrary("myNative") 并且操作系统将处理加载 myDependency。不过,在运行 API 17 及更低版本的设备的市场渗透率处于您可以接受的低水平之前,请不要依赖这一点。

要在当前版本的 Android Studio 中明确从源代码构建 NDK 库,您可以执行以下操作:

如前所述,将 local.properties 中的 ndk.dir 值设置为指向 NDK 主目录。有谁知道您是否可以直接在 local.properties 中使用环境变量? :)

在您的 build.gradle 文件中,将类似这样的内容添加到您的任务中(同样,可以是 defaultConfig、debug、release、productFlavor 等):

ndk 
    moduleName "myNDKModule"
    stl "stlport_shared"
    ldLibs "log", "z", "m"
    cFlags "-I/some/include/path"

这是当前支持的类型(moduleName、stl、ldLibs 和 cFlags)的基本结构。我看了看,没有发现更多。我认为 ldLibs 存在一个问题,因为它会自动在上面每个字段的前面添加“-l”。你可以通过说(我不得不)来欺骗它: ldLibs "log -lz -lm -Wl,-whole-archive -l/path/to/someOtherLib -Wl,-no-whole-archive"

在这一行中,您只是在第一个参数的末尾添加标签以添加不以 -l 开头的参数,因此您现在可以通过。在上面的例子中,我将整个静态库链接到我的 NDK 模块中,以便在 Java 中使用。我已经要求 google 开发人员添加额外的功能,以允许甚至能够将您自己的 Android.mk 文件合并到 NDK 构建过程中,但由于这是全新的,可能需要一段时间。

目前,无论您在 build.gradle 中放置什么,都会删除临时构建目录并每次重新创建它,所以除非您想下载和修改 gradle android 插件源代码(这会很有趣),否则会有一些“make due” “就像这样需要将你的东西复制到构建中。提供此ndk支持的android gradle脚本本质上会生成一个Android.mk文件,并在临时目录中使用NDK系统构建。

偏离了几秒钟。 moduleName 应该与项目中 jni 目录下的 c 或 cpp 文件匹配,例如:

[project]/[app]/src/main/jni/myNDKModule.cpp

如果您想使用 C++ 的 stlport 库,stl 值应设置为“stlport_shared”或“stlport_static”的值。如果您不需要扩展的 C++ 支持,可以将 stl 排除在外。请记住,Android 默认提供非常基本的 C++ 支持。对于其他受支持的 C++ 库,请在您下载的 NDK 中查看您的 NDK 文档指南。请注意,通过在此处将其设置为 stlport_shared,gradle 会将 libstlport_shared.so lib 从 NDK 的 sources/cxx-stl/stlport/libs 目录复制到 APK 的 lib 目录。它还处理编译器中的包含路径(从技术上讲,gradle 并没有做所有这些,而是​​ Android NDK 构建系统)。所以不要把你自己的 stlport 副本放到你的 jniLibs 目录中。

最后,我认为 cFlags 很明显。

您不能在 Mac OSX 上设置 ANDROID_NDK_HOME(见下文),但从我所做的一些研究看来,这可能仍然适用于其他操作系统。但它会被删除。

我想发表评论,但还没有声望。丹尼斯,环境变量被完全忽略,而不仅仅是被覆盖。事实上,你没有得到任何环境变量。据我所知,Android Studio IDE 只用几个特定的​​环境变量创建了自己的环境(检查 System.getenv() 并从 gradle 脚本中打印出来)。

我在这里把它写成一个错误,因为使用 env vars 可以从 cmd 行构建良好:https://code.google.com/p/android/issues/detail?id=65213

但正如您所见,Google 决定他们根本不希望 IDE 使用环境变量;我仍然对那个决定持观望态度。不得不更新 local.properties 以指向可以加载到我的 gradle 脚本中的绝对路径,这让我的生活很痛苦,我还没有弄清楚该怎么做(但看起来并不难)。这意味着我要么强迫我的团队成员使用与我相同的路径,使用链接,让他们在每次提取 repo 时都输入它们,或者添加自动化脚本。我认为这是一个糟糕的决定,对于任何依赖 env vars 的开发人员来说都是一个错误的决定,这些环境变量在微观层面可能很小,但在宏观层面却很大。

groundloop,我相信 IDE 很快就会更新,能够将 NDK 文件夹路径添加到您的项目中,并且它会自动生成 local.properties 文件(至少如果他们有没想到这一点)。

有关来自 Google 的更详细示例,以下是最新示例(搜索 jni 或 ndk): https://docs.google.com/viewer?a=v&pid=sites&srcid=YW5kcm9pZC5jb218dG9vbHN8Z3g6NDYzNTVjMjNmM2YwMjhhNA

使用 NDK 的跨平台胖 APK:

最后,使用 gradle 存在一个缺点,并且无法提供您自己的 Android.mk 文件,因此您只能将 3rd 方原生库从单一架构链接到您的 NDK。注意我说的是“链接”。您可以使用“abiFilters”命令在多个架构中构建 NDK 模块(上面的模块名称),它们将被放置在您的应用程序中,以便可以在多个架构上使用相同的 APK。如果您需要链接自己的 3rd 方库,甚至根据您的架构为 cFlags 设置不同的值,那么没有简单的方法。 我尝试了以下方法,起初它似乎可以工作,但后来我发现它只是通过将两个 ndk 部分的所有内容附加在一起来构建 NDK(或类似的东西,它确实以某种方式构建了多个架构图书馆):

android 
    compileSdkVersion 23
    buildToolsVersion '23.0.1'
    defaultConfig 
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 28
        versionName "3.0"
    
    buildTypes 
        def commonLibs = " -lfoo -lbar -lwhatever"
        def armV7LibsDir = "/whatever/armv7a/libs"
        def armX86LibsDir = "/whatever/x86/libs"
        def armV7IncDir = "/whatever/armv7a/include"
        def x86IncDir = "/whatever/x86/include"
        debug 
            ndk 
                cFlags = "-I" + armV7IncDir
                moduleName "myNativeCPPModule"
                stl "stlport_shared"
                abiFilter "armeabi-v7a"
                ldLibs "log -L" + armV7LibsDir + commonLibs
            
            ndk 
                cFlags = "-I" + armX86IncDir
                moduleName "myNativeCPPModule"
                stl "stlport_shared"
                abiFilter "x86"
                ldLibs "log -L" + armX86LibsDir + commonLibs
            
        
    

在尝试使用 gradle 和本地 3rd 方库在干净的庄园中创建胖二进制文件后,我终于得出结论,Google Play 对 APK 的内置多架构支持确实是最好的选择,因此为每个架构创建单独的 APK。

所以我创建了多个 buildTypes,没有产品风格,并添加了以下代码来生成每种类型的版本代码。

// This is somewhat nasty, but we need to put a "2" in front of all ARMEABI-V7A builds, a "3" in front of 64-bit ARM, etc.
// Google Play chooses the best APK based on version code, so if a device supports both X86 and
// ARM, it will choose the X86 APK (preferred because Inky ARM running on an X86 with Houdini ARM Emulator crashes in our case)
android.applicationVariants.all  variant ->
    if (variant.buildType.name.equals('release')) 
        variant.mergedFlavor.versionCode = 2000 + defaultConfig.versionCode
     else if (variant.buildType.name.equals('debug')) 
        variant.mergedFlavor.versionCode = 2000 + defaultConfig.versionCode
     else if (variant.buildType.name.equals('debugArmV8a')) 
        variant.mergedFlavor.versionCode = 3000 + defaultConfig.versionCode
     else if (variant.buildType.name.equals('releaseArmV8a')) 
        variant.mergedFlavor.versionCode = 3000 + defaultConfig.versionCode
     else if (variant.buildType.name.equals('debugMips')) 
        variant.mergedFlavor.versionCode = 5000 + defaultConfig.versionCode
     else if (variant.buildType.name.equals('releaseMips')) 
        variant.mergedFlavor.versionCode = 5000 + defaultConfig.versionCode
     else if (variant.buildType.name.equals('debugMips64')) 
        variant.mergedFlavor.versionCode = 6000 + defaultConfig.versionCode
     else if (variant.buildType.name.equals('releaseMips64')) 
        variant.mergedFlavor.versionCode = 6000 + defaultConfig.versionCode
     else if (variant.buildType.name.equals('debugX86')) 
        variant.mergedFlavor.versionCode = 8000 + defaultConfig.versionCode
     else if (variant.buildType.name.equals('releaseX86')) 
        variant.mergedFlavor.versionCode = 8000 + defaultConfig.versionCode
     else if (variant.buildType.name.equals('debugX86_64')) 
        variant.mergedFlavor.versionCode = 9000 + defaultConfig.versionCode
     else if (variant.buildType.name.equals('releaseX86_64')) 
        variant.mergedFlavor.versionCode = 9000 + defaultConfig.versionCode
    

现在您所要做的就是在您的 defaultConfig 对象中设置 versionCode 的值,就像您通常所做的那样,这会根据构建类型将其附加到特定于体系结构的版本字符串的末尾。版本字符串对于所有构建都保持相同,但会改变代码以提供从 ARM 一直到 X86_64 的优先顺序。这有点hackish或硬编码,但它完成了工作。请注意,这为您提供了多达 999 个版本,因此如果您需要更多,请将上面的数字乘以 10,不确定您可以为版本代码输入的最大值是多少。

就我而言,我们有一个相当复杂的构建系统。我们为 9 个架构构建了 CPython,其中 3 个是 Android,然后构建了一堆我们自己的库,并将它们全部链接到每个架构的单个库中。我们使用 ndk 命令行构建工具、automake 和 python 来构建所有内容,而不是 Android.mk 文件。然后将最终的库链接到单个 JNI 接口 cpp 文件(上面称为 myNativeCPPModule)。一键搞定,一键搞定,非常棒的Android Studio。

【讨论】:

非常有帮助。确实谢谢。顺便说一句,您知道用户静态库的默认搜索目录吗?如果可能,我选择不在 ldLibs 中使用“-I”的技巧。 如何浏览/下载此处列出的文件?:docs.google.com/… 找到了示例下载,但现在我遇到了 Gradle 1.11 的另一个问题。生成的 Androud.mk 文件使用绝对路径:***.com/questions/23344567/… 感谢 ldlibs 的欺骗 - 我刚刚发现 ldlibs 没有按照您输入的顺序保存,这使得使用多个静态库几乎完全无法使用。除非它们是“一个”论点,否则它会起作用。太棒了! 这是我见过的关于ndk设置的最详细的分步介绍。 Google 应该聘请您重写他们的所有文档。【参考方案2】:

找到答案。在 local.properties 文件中包含 ndk.dir=path/to/ndk 就可以了。

更新: 在最新版本的 Android Studio 上,您可以直接在 Project Structure > SDK location 中设置值。

【讨论】:

手动将某些内容放入local.properties 文件是否是个好主意,因为它说“此文件由 Android Studio 自动生成。请勿修改此文件——您的更改将被删除! " ?【参考方案3】:

你也可以设置ANDROID_NDK_HOME环境变量

【讨论】:

我已经完成了,但它如何反映在应用程序中?我仍然遇到同样的错误。【参考方案4】:

我花了很多时间在 build.gradle 中配置 ndk。我有一个good blog 解决了我的问题。

【讨论】:

【参考方案5】:

如前所述,在 local.properties 中添加 ndk.dir= 会有所帮助。有趣的是,我发现 local.properties 会覆盖为环境变量 ANDROID_NDK_HOME 设置的任何值,即使您没有在 local.properties 中配置 ndk.dir。 (至少使用 gradle android plugin v 0.7.3)。

这很令人困惑,因为 Android Studio 可以覆盖 local.properties,而且它似乎没有提供配置 ndk.dir 的方法:(

【讨论】:

我不明白你在说什么未指定。 “local.properties 中的 ndk.dir 会覆盖任何值……即使您没有在 local.properties 中配置 ndk.dir” --- 说,什么?!?!未指定的值会覆盖环境变量......你真的是说环境变量被忽略了吗?或者我错过了什么? 是的,我同意@Dennis,将其排除在 local.properties 之外没有问题。【参考方案6】:

Android studio 建议在 local.properties 中包含 ndk 的路径

【讨论】:

以上是关于如何使用 Android Gradle 插件 0.7 配置 NDK的主要内容,如果未能解决你的问题,请参考以下文章

使用android gradle插件3.0.0比gradle插件2.3.3慢

Android Studio 如何快速改插件与Gradle的版本号

错误:Android Gradle 插件仅支持 Kotlin Gradle 插件版本 1.3.0 及更高版本

使用 gradle 7.0.0 与 Android 中的华为 HMS 插件冲突

如何使用来自 Android Gradle 插件的 Variant API 的新方法 variant.getGenerateBuildConfigProvider()?

我们如何将CMake或ndk-build集成到Android Studio 3.0.1中? Gradle版本是4.1,android插件版本3.0.1