面向中间件供应商的建议

分发使用 NDK 构建的中间件会引发一些应用程序开发者不需要担心的额外问题。预构建库会将它们的一些实现选择强加于用户。

选择 API 等级和 NDK 版本

您的用户无法使用比您低的 minSdkVersion。如果您的用户应用程序需要在 API 21 上运行,则您无法为 API 24 构建。为比您的用户更低的 API 等级构建是可以的。您可以为 API 16 构建,并与您的 API 21 用户保持兼容。

NDK 版本在很大程度上彼此兼容,但偶尔会出现破坏兼容性的更改。如果您知道所有用户都使用相同版本的 NDK,最好使用与他们相同的版本。否则,请使用最新版本。

使用 STL

如果您正在编写 C++ 并使用 STL,那么在 libc++_sharedlibc++_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 表面并最大程度地减少库加载时间。