查看 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 中使用。我已经要求谷歌开发人员添加额外的功能,以允许将您自己的 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。