La distribución de middleware compilada por el NDK genera algunos problemas adicionales de los que los desarrolladores de app no necesitan preocuparse. Las bibliotecas compiladas previamente imponen algunas de sus opciones de implementación a sus usuarios.
Cómo elegir niveles de API y versiones de NDK
Tus usuarios no pueden usar una minSdkVersion inferior a la tuya. Si las apps de tus usuarios necesitan ejecutarse en API 21, no puedes compilar para API 24. Está bien que compiles la biblioteca para un nivel de API inferior al de tus usuarios. Puedes compilar para API nivel 16 y seguir siendo compatible con los usuarios de API 21.
Las versiones del NDK en su mayoría son compatibles entre sí, pero en ocasiones hay cambios que afectan la compatibilidad. Si sabes que todos los usuarios utilizan la misma versión de NDK, lo mejor es hacer lo mismo. De lo contrario, usa la versión más reciente.
Cómo usar el STL
Si escribes C++ y usas la STL, las opciones que elijas entre libc++_shared
y libc++_static
afectarán a los usuarios si distribuyes una biblioteca compartida. Si distribuyes una biblioteca compartida, debes usar libc++_shared
o garantizar que tu biblioteca no exponga los símbolos de libc++. La mejor manera de hacerlo es declarar explícitamente la superficie de tu ABI con una secuencia de comandos de la versión (esto también ayuda a mantener la privacidad de los detalles de la implementación). Por ejemplo, una biblioteca aritmética simple podría tener la siguiente secuencia de comandos de la versión:
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:
*;
};
La secuencia de comandos de la versión debe ser la opción preferida, porque es la manera más sólida de controlar la visibilidad de los símbolos. Esta es una práctica recomendada para todas las bibliotecas compartidas, el middleware o no, ya que impide que se expongan los detalles de la implementación y mejora el tiempo de carga.
Otra opción menos segura es usar -Wl,--exclude-libs,libc++_static.a
-Wl,--exclude-libs,libc++abi.a
durante la vinculación. Esto es menos sólido, ya que solo ocultará los símbolos en las bibliotecas que se nombran explícitamente, y no se informan diagnósticos en las que no se usan (un error tipográfico en el nombre de la biblioteca no es un error, y la carga estará en el usuario para mantener actualizada la lista de bibliotecas). Este enfoque tampoco oculta tus propios detalles de implementación.
Distribuye bibliotecas nativas en AAR
El complemento de Android para Gradle puede importar dependencias nativas distribuidas en AAR. La forma más fácil de que los usuarios consuman tu biblioteca es usar el complemento de Android para Gradle.
Las bibliotecas nativas se pueden empaquetar en un AAR por AGP. Esta será la opción más fácil si tu biblioteca ya fue compilada por externalNativeBuild.
Las compilaciones que no son AGP pueden usar ndkports o realizar paquetes manuales siguiendo la documentación de Prefab para crear el subdirectorio prefab/
de su AAR.
Middleware de Java con bibliotecas JNI
Las bibliotecas Java que incluyen bibliotecas JNI (en otras palabras, AAR que contienen jniLibs
) deben estar atentos a que las bibliotecas JNI que incluyan no entren en conflicto con otras bibliotecas de la app del usuario. Por ejemplo, si el AAR incluye libc++_shared.so
, pero una versión diferente de libc++_shared.so
que usa la app, solo se instalará una en el APK y podría llevar a un comportamiento no confiable.
La solución más confiable es que las bibliotecas Java incluyan no más de una biblioteca JNI (también es un buen consejo para apps). Todas las dependencias, incluida la STL, deben estar vinculadas de forma estática a la biblioteca de implementación y se debe usar una secuencia de comandos de la versión para aplicar la superficie de ABI. Por ejemplo, una biblioteca com.example.foo
de Java que incluye la biblioteca libfooimpl.so
de JNI debe usar la siguiente secuencia de comandos de la versión:
LIBFOOIMPL {
global:
JNI_OnLoad;
local:
*;
};
En este ejemplo, se usa registerNatives
a través de JNI_OnLoad
como se describe en Sugerencias de JNI para garantizar que se exponga la superficie mínima de la ABI y se minimice el tiempo de carga de la biblioteca.