查看 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");
}
现在,假设一个本机库依赖于另一个库。您必须(如果将最小 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 中,他们修复了一个长期存在的架构问题,该问题阻止本机 lib 加载器“自动”加载依赖项,因为它不知道应用程序的 lib 目录(安全原因等)。在 API 18 及更高版本中,如果 myNative 依赖于上面的 myDependency,您只需调用 loadLibrary("myNative"),操作系统将处理加载 myDependency。不过,在运行 API 17 及以下版本的设备的市场渗透率达到您可以接受的较低水平之前,请不要依赖这一点。
明确地从源代码构建 NDK 库在当前版本的Android Studio中,您可以执行以下操作:
将 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 /路径/到/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 的 resources/cxx-stl/stlport/libs 目录复制到 APK 的 lib 目录。它还处理编译器中的包含路径(从技术上讲,gradle 并不执行所有这些操作,而是由 Android NDK 构建系统执行)。因此,不要将您自己的 stlport 副本放入 jniLibs 目录中。
最后,我认为 cFlags 非常明显。
您无法在 Mac OSX 上设置 ANDROID_NDK_HOME (见下文),但从我所做的一些研究来看,这可能仍然适用于其他操作系统。但它将被删除。
我想发表评论,但还没有声誉。丹尼斯,环境变量被完全忽略,而不仅仅是被覆盖。事实上,您没有获得任何环境变量。据我所知,Android Studio IDE 仅使用一些特定的环境变量创建自己的环境(检查 System.getenv() 并从 gradle 脚本中将其打印出来)。
我在这里将其写为错误,因为使用环境变量可以从 cmd 行构建良好:
https://code.google.com/p/android/issues/detail?id=65213 https://code.google.com/p/android/issues/detail?id=65213
但正如您所看到的,Google 决定根本不希望 IDE 使用环境变量;我仍然对这个决定持观望态度。必须更新 local.properties 以指向可以在我的 gradle 脚本中加载的绝对路径,这让我的生活很痛苦,我还没有弄清楚如何做到这一点(但还没有看起来那么难)。这意味着我要么强迫我的团队成员使用与我相同的路径,使用链接,让他们在每次拉取存储库时都输入它们,要么添加自动化脚本。我认为这是一个错误的决定,对于任何依赖环境变量的开发人员来说都会花费时间,环境变量在微观层面可能很小,但在宏观层面却很大。
groundloop,我相信 IDE 很快就会更新,能够将 NDK 文件夹路径添加到您的项目中,并且它将自动生成 local.properties 文件(如果他们没有想到的话,至少这是没有意义的)这)。
有关 Google 的更详细示例,以下是最新示例(搜索 jni 或 ndk):
使用 NDK 的跨平台胖 APK:
最后,使用 gradle 存在一个缺点,无法提供您自己的 Android.mk 文件,因此您只能将 3rd 方本机库从单一架构链接到您的 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 和本机第 3 方库创建一个胖二进制文件,我终于得出结论,Google Play 对 APK 的内置多架构支持确实是最好的途径,所以创建每个架构都有单独的 APK。
因此,我创建了多个 buildType,没有产品风格,并添加了以下代码来生成每种类型的版本代码。
// 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 的优先顺序。它有点黑客或硬编码,但它可以完成工作。请注意,这为您提供了最多 999 个版本,因此如果您需要更多版本,请将上面的数字乘以 10,不确定您可以为版本代码输入的最大值是多少。
就我而言,我们有一个相当复杂的构建系统。我们为 9 种架构构建 CPython,其中 3 种是 Android,然后构建一堆我们自己的库,并将它们全部链接到每个架构的单个库中。我们使用 ndk 命令行构建工具、automake 和 python 来构建所有内容,而不是 Android.mk 文件。然后,最终的库将链接到单个 JNI 接口 cpp 文件(上面称为 myNativeCPPModule)。一键点击按钮,一切就全部构建完毕,非常好的 Android Studio。