미들웨어 공급업체를 위한 조언

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(Application Binary Interface) 표면을 명시적으로 선언하는 것입니다. 이를 통해 구현 세부정보를 비공개로 유지할 수도 있습니다. 예를 들어 간단한 연산 라이브러리에는 다음의 버전 스크립트가 있을 수 있습니다.

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)을 사용 중인 경우 라이브러리를 사용하기에 가장 간편한 방법입니다.

AGP를 사용하여 네이티브 라이브러리를 Android 보관 파일(AAR)로 패키징할 수 있습니다. 이는 라이브러리가 externalNativeBuild로 이미 빌드된 경우 가장 쉬운 옵션입니다.

AGP 이외의 빌드는 ndkports를 사용하거나 AAR의 prefab/ 하위 디렉터리를 만드는 방법을 안내하는 Prefab 도움말에 따라 직접 패키징할 수 있습니다.

JNI 라이브러리를 포함하는 자바 미들웨어

JNI 라이브러리를 포함하는 자바 라이브러리(jniLibs를 포함하는 AAR)는 포함된 JNI 라이브러리가 사용자가 설치한 앱의 다른 라이브러리와 충돌하지 않도록 주의해야 합니다. 예를 들어 AAR에 libc++_shared.so가 포함되어 있지만 앱이 사용하는 것과 다른 libc++_shared.so 버전이라면 APK에 둘 중 하나만 설치되고 동작이 불안정해질 수 있습니다.

가장 안정적인 해결 방법은 자바 라이브러리에 하나의 JNI 라이브러리만 포함하는 것입니다. 이는 앱에도 적용되는 사항입니다. STL을 포함한 모든 종속 항목은 구현 라이브러리에 정적으로 연결해야 하며, 버전 스크립트를 사용하여 ABI 표면을 적용해야 합니다. 예를 들어 JNI 라이브러리 libfooimpl.so를 포함하는 자바 라이브러리 com.example.foo는 다음 버전 스크립트를 사용해야 합니다.

LIBFOOIMPL {
global:
    JNI_OnLoad;
local:
    *;
};

이 예에서는 JNI 도움말의 설명대로 JNI_OnLoad를 통해 registerNatives를 사용하여 최소 ABI 표면이 노출되고 라이브러리 로드 시간이 최소화되도록 합니다.