Skip to main content
 首页 » 编程设计

android-ndk之如何使用 Android Gradle 插件 0.7 配置 NDK

2024年05月10日10JustinYoung

新的 Android gradle 插件 (0.7) 似乎包含对 NDK 的新支持,但在文档中几乎没有提及它(我找到的唯一引用是一个名为 ndkSanAngeles 的测试)。

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

  • What went wrong: Execution failed for task ':OGLTests:compileDefaultFlavorDebugNdk'. NDK not configured


如何在 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' 
        } 
    } 
} 

谢谢。

请您参考如下方法:

查看 gradle 插件代码,我发现以下帮助我使用 NDK 和预构建的 native 库:

要简单地将 Link in Prebuilt Native Libraries ,只需将 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 目录中搜索 native 库。 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"); 
} 

现在,假设一个本地库依赖于另一个。您必须(如果将您的最小 API 设置为 API 17 或更低)首先加载依赖库:
static 
{ 
    loadLibrary("myDependency"); 
    loadLibrary("myNative"); 
} 

您还可以将 ndk {} 部分放在您的 defaultConfig 或 buildType(例如调试或发布或您可能使用的任何其他内容)中。例如:
buildTypes { 
    debug { 
        ndk { 
            abiFilters "armeabi", "armeabi-v7a" 
        } 
    } 
} 

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

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

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

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

在您的 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 "日志 -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 库从 NDK 的 sources/cxx-STL/STLport/libs 目录复制到您的 APK 的 lib 目录中。它还处理编译器中的包含路径(从技术上讲,gradle 并没有完成所有这些,而是​​ Android NDK 构建系统)。所以不要把你自己的 STLport 副本放到你的 jniLibs 目录中。

最后,我认为 cFlags 非常明显。

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

我想发表评论,但还没有声誉。 Dennis,环境变量被完全忽略,而不仅仅是被覆盖。事实上,您没有获得任何环境变量。据我所知,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 时都输入它们,或者添加自动化脚本。我相信这是一个错误的决定,对于依赖环境变量的任何开发人员来说都会花费时间,环境变量在微观层面可能很小,但在宏观层面却很大。

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

从谷歌获取更详细的示例,这里是最新的示例(搜索 jni 或 ndk):
https://docs.google.com/viewer?a=v&pid=sites&srcid=YW5kcm9pZC5jb218dG9vbHN8Z3g6NDYzNTVjMjNmM2YwMjhhNA

使用 NDK 的跨平台胖 APK:

最后,使用 gradle 有一个缺点,它无法提供您自己的 Android.mk 文件,因此您只能将 3rd 方 native 库从单一架构链接到您的 NDK。注意我说的是“链接”。您可以使用“abiFilters”命令在多个架构中构建 NDK 模块(上面的 moduleName),它们将被放置在您的应用程序中,以便同一个 APK 可以在多个架构上使用。如果您需要链接您自己的第 3 方库,或者甚至根据您的架构为 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 和 native 3rd 方库在干净的庄园中创建一个胖二进制文件后,我终于得出结论,Google Play 对 APK 的内置多架构支持确实是最好的选择,所以创建每个架构的单独 APK。

所以我创建了多个buildTypes,没有product flavors,并添加了以下代码来生成每个types的版本代码。
// 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 接口(interface) cpp 文件(上面称为 myNativeCPPModule)。一键点击,一切都在一次构建,非常棒的 Android Studio。