分发使用 NDK 构建的中间件会引发一些应用程序开发者不需要担心的额外问题。预构建库会将它们的一些实现选择强加于用户。
选择 API 等级和 NDK 版本
您的用户无法使用比您低的 minSdkVersion。如果您的用户应用程序需要在 API 21 上运行,则您无法为 API 24 构建。为比您的用户更低的 API 等级构建是可以的。您可以为 API 16 构建,并与您的 API 21 用户保持兼容。
NDK 版本在很大程度上彼此兼容,但偶尔会出现破坏兼容性的更改。如果您知道所有用户都使用相同版本的 NDK,最好使用与他们相同的版本。否则,请使用最新版本。
使用 STL
如果您正在编写 C++ 并使用 STL,那么在 libc++_shared
和 libc++_static
之间进行选择会影响您的用户,如果您分发的是共享库。如果您分发的是共享库,则必须使用 libc++_shared
或确保 libc++ 的符号未被您的库公开。实现这一点的最佳方法是使用版本脚本显式声明您的 ABI 表面(这也有助于保持您的实现细节私密)。例如,一个简单的算术库可能具有以下版本脚本
LIBMYMATH {
global:
add;
sub;
mul;
div;
# C++ symbols in an extern block will be mangled automatically. See
# https://stackoverflow.com/a/21845178/632035 for more examples.
extern "C++" {
"pow(int, int)";
}
local:
*;
};
版本脚本应该是首选选项,因为它是最稳健的方式来控制符号可见性。这是针对所有共享库(无论是否是中间件)的最佳实践,因为它可以防止您的实现细节被公开并提高加载时间。
另一个不太稳健的选项是在链接时使用 -Wl,--exclude-libs,libc++_static.a -Wl,--exclude-libs,libc++abi.a
。这种方式不太稳健,因为它只会在显式命名的库中隐藏符号,并且不会对未使用的库报告任何诊断信息(库名称中的拼写错误不会导致错误,并且维护库列表的责任在于用户)。这种方法也不会隐藏您自己的实现细节。
在 AAR 中分发本机库
Android Gradle 插件可以导入在 AAR 中分发的 本机依赖项。如果您的用户使用的是 Android Gradle 插件,这将是他们使用您的库的最简单方法。
本机库可以 通过 AGP 打包到 AAR 中。如果您的库已由 externalNativeBuild 构建,这将是最简单的选择。
非 AGP 构建可以使用 ndkports,或通过遵循 Prefab 文档进行手动打包,以创建其 AAR 的 prefab/
子目录。
包含 JNI 库的 Java 中间件
包含 JNI 库的 Java 库(换句话说,包含 jniLibs
的 AAR)需要注意,它们包含的 JNI 库不会与用户应用程序中的其他库冲突。例如,如果 AAR 包含 libc++_shared.so
,但应用程序使用的 libc++_shared.so
版本不同,则只会安装一个到 APK 中,这可能会导致不可靠的行为。
最可靠的解决方案是 Java 库不包含超过 **一个** JNI 库(这对于应用程序也是很好的建议)。所有依赖项(包括 STL)都应该静态链接到实现库中,并且应该使用版本脚本来强制执行 ABI 表面。例如,包含 JNI 库 libfooimpl.so
的 Java 库 com.example.foo
应该使用以下版本脚本
LIBFOOIMPL {
global:
JNI_OnLoad;
local:
*;
};
此示例使用 registerNatives
通过 JNI_OnLoad
,如 JNI 技巧 中所述,以确保暴露最小的 ABI 表面并最大程度地减少库加载时间。