如何使用 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