최신 API 사용

이 페이지에서는 앱이 새로운 이전 기기와의 호환성을 유지하는 한편 OS 버전

기본적으로 애플리케이션의 NDK API 참조는 강력한 참조입니다. Android의 동적 로더는 라이브러리가 있습니다. 기호를 찾을 수 없는 경우 앱이 중단됩니다. 이는 누락된 API가 호출될 때까지 예외가 발생하지 않는 합니다.

이런 이유로 NDK에서는 앱의 minSdkVersion보다 최신인 API입니다. 이렇게 하면 테스트 중에 작동했지만 로드할 수 없는 코드가 실수로 배송됨 이전 버전에서는 (UnsatisfiedLinkErrorSystem.loadLibrary()에서 발생함) 기기에서 사용할 수 있습니다. 반면에 API를 사용하는 코드는 작성하기가 더 어렵습니다. 이는 앱의 minSdkVersion보다 최신 버전이어야 합니다. 일반적인 함수 호출이 아닌 dlopen()dlsym()입니다.

강력한 참조 대신 약한 참조를 사용할 수 있습니다. 약함 참조가 발견되지 않은 경우 기호가 로드에 실패하지 않고 nullptr로 설정됩니다. 여전히 안전하게 호출될 수 없지만, 호출 시 코드를 사용할 수 없는 경우에는 나머지 코드만 실행할 수 있으며 dlopen()dlsym()를 사용할 필요 없이 정상적으로 API를 호출할 수 있습니다.

약한 API 참조는 동적 링커의 추가 지원이 필요하지 않습니다. 모든 Android 버전에서 사용할 수 있습니다.

빌드에서 취약한 API 참조 사용 설정

CMake

CMake를 실행할 때 -DANDROID_WEAK_API_DEFS=ON를 전달합니다. 다음을 통해 CMake를 사용하는 경우 externalNativeBuild이면 다음을 build.gradle.kts (또는 여전히 build.gradle를 사용하는 경우 Groovy에 해당합니다.

android {
    // Other config...

    defaultConfig {
        // Other config...

        externalNativeBuild {
            cmake {
                arguments.add("-DANDROID_WEAK_API_DEFS=ON")
                // Other config...
            }
        }
    }
}

ndk-build

Application.mk 파일에 다음을 추가합니다.

APP_WEAK_API_DEFS := true

아직 Application.mk 파일이 없다면 동일한 파일에 이 파일을 만듭니다. 디렉터리를 Android.mk 파일로 저장합니다. 그 밖의 ndk-build에는 build.gradle.kts (또는 build.gradle) 파일이 필요하지 않습니다.

기타 빌드 시스템

CMake 또는 ndk-build를 사용하지 않는 경우 빌드 문서를 참고하세요. 해당 기능을 사용하도록 설정하는 권장 방법이 있는지 확인합니다. 빌드가 시스템에서 이 옵션을 기본적으로 지원하지 않는 경우 컴파일할 때 다음 플래그를 전달합니다.

-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability

첫 번째는 약한 참조를 허용하도록 NDK 헤더를 구성합니다. 두 번째 턴 안전하지 않은 API 호출에 대한 경고를 오류로 표시합니다.

자세한 내용은 빌드 시스템 유지관리자 가이드를 참고하세요.

보호되는 API 호출

이 기능은 마법처럼 새 API 호출을 안전하게 실행하지는 않습니다. 유일한 것은 로드 시간 오류를 호출 시간 오류로 연기하는 것입니다. 이 도구의 장점은 는 사용자에게 앱의 해당 기능이 사용 중지되었음을 알리는 해당 코드 경로를 완전히 피할 수 있습니다.

비보호 모드를 만들 때 Clang에서 경고 (unguarded-availability)를 내보낼 수 있습니다. 앱의 minSdkVersion에 사용할 수 없는 API 호출 만약 ndk-build나 CMake 도구 모음 파일을 사용하면 이 경고가 자동으로 이 기능을 사용 설정하면 오류가 표시됩니다.

다음은 dlopen()dlsym()를 사용하여 이 기능이 사용 설정되었습니다.

void LogImageDecoderResult(int result) {
    void* lib = dlopen("libjnigraphics.so", RTLD_LOCAL);
    CHECK_NE(lib, nullptr) << "Failed to open libjnigraphics.so: " << dlerror();
    auto func = reinterpret_cast<decltype(&AImageDecoder_resultToString)>(
        dlsym(lib, "AImageDecoder_resultToString")
    );
    if (func == nullptr) {
        LOG(INFO) << "cannot stringify result: " << result;
    } else {
        LOG(INFO) << func(result);
    }
}

읽기가 약간 어지럽고 함수 이름이 중복됩니다. C, 서명도 작성 중임)는 성공적으로 빌드되지만 항상 전달된 함수 이름에 실수로 오타가 있는 경우 런타임에 대체를 수행합니다. dlsym로 변환하고 모든 API에 이 패턴을 사용해야 합니다.

API 참조가 약한 경우 위의 함수를 다음과 같이 다시 작성할 수 있습니다.

void LogImageDecoderResult(int result) {
    if (__builtin_available(android 31, *)) {
        LOG(INFO) << AImageDecoder_resultToString(result);
    } else {
        LOG(INFO) << "cannot stringify result: " << result;
    }
}

내부적으로 __builtin_available(android 31, *)android_get_device_api_level(), 결과를 캐시하고 31와 비교합니다. AImageDecoder_resultToString()를 도입한 API 수준입니다.

__builtin_available에 사용할 값을 결정하는 가장 간단한 방법은 다음과 같습니다. 건물을 가드나 방어해야 하는 __builtin_available(android 1, *))을 클릭하고 오류 메시지에 표시된 대로 실행합니다. 예를 들어 AImageDecoder_createFromAAsset()에 대한 비보호 호출을 minSdkVersion 24는 다음을 생성합니다.

error: 'AImageDecoder_createFromAAsset' is only available on Android 30 or newer [-Werror,-Wunguarded-availability]

이 경우 호출은 __builtin_available(android 30, *)에 의해 보호되어야 합니다. 빌드 오류가 없으면 minSdkVersion이고 가드가 필요하지 않거나 빌드가 잘못 구성되었고 unguarded-availability 경고가 사용 중지되었습니다.

또는 NDK API 참조에서는 "API 30에 도입되었습니다." 지정할 수 있습니다 이 텍스트가 없으면 지원되는 모든 API 수준에서 API를 사용할 수 있습니다.

API 가드 반복 피하기

이 API를 사용한다면 앱에 충분한 새 기기에서만 사용할 수 있습니다. 전체 과정을 반복하기보다는 __builtin_available() 함수를 점검하면 주석을 달 수 있습니다. 특정 API 수준을 필요로 하는 것으로 정의할 수 있습니다 예를 들어 ImageDecoder API는 API 30에 추가되었으므로 이러한 API를 많이 사용하는 함수의 경우 다음과 같은 작업을 할 수 있습니다.

#define REQUIRES_API(x) __attribute__((__availability__(android,introduced=x)))
#define API_AT_LEAST(x) __builtin_available(android x, *)

void DecodeImageWithImageDecoder() REQUIRES_API(30) {
    // Call any APIs that were introduced in API 30 or newer without guards.
}

void DecodeImageFallback() {
    // Pay the overhead to call the Java APIs via JNI, or use third-party image
    // decoding libraries.
}

void DecodeImage() {
    if (API_AT_LEAST(30)) {
        DecodeImageWithImageDecoder();
    } else {
        DecodeImageFallback();
    }
}

API 가드의 특이점

Clang은 __builtin_available의 사용 방법에 매우 한정됩니다. 리터럴만 (매크로 대체될 수도 있지만) if (__builtin_available(...))가 작동합니다. 균등 if (!__builtin_available(...))와 같은 사소한 작업은 작동하지 않습니다 (Clang). unsupported-availability-guard 경고를 방출하고 unguarded-availability)을 입력합니다. 이는 Clang의 향후 버전에서 개선될 것입니다. 자세한 내용은 자세한 내용은 LLVM 문제 33161을 참고하세요.

unguarded-availability 검사는 다음 함수가 되는 함수 범위에만 적용됩니다. 사용됩니다. Clang은 API 호출을 포함하는 함수가 보호된 범위 내에서만 호출됩니다. 방호막의 반복을 피하기 위해 API 가드 반복 방지를 참고하세요.

이것이 기본값이 아닌 이유는 무엇인가요?

올바르게 사용하지 않는 경우 강력한 API 참조와 약한 API의 차이점 전자는 빠르고 명확하게 실패하지만 사용자는 누락된 API를 일으키는 작업을 수행할 때까지 오류가 발생하지 않습니다. 있습니다. 이 경우 오류 메시지가 명확하게 compile-time 'AFoo_bar()를 사용할 수 없음' segfault가 됩니다. 다음으로 바꿉니다. 오류 메시지가 훨씬 더 명확하며, 빠른 실패는 보다 안전한 기본 환경입니다.

새로운 기능이기 때문에 코드를 처리할 기존 코드가 거의 없습니다. 안전하게 보호할 수 있습니다 Android를 염두에 두고 작성되지 않은 서드 파티 코드 이 문제가 항상 발생할 가능성이 높으므로 현재로서는 이 문제를 해결할 계획이 없습니다. 기본 동작을 변경할 수 없습니다.

이 방법을 사용하는 것이 좋지만 문제가 더 많아집니다. 탐지 및 디버그하기 어렵다면 이러한 위험을 고의로 감수해야 합니다. 행동이 바뀌는 것을 막을 수 있습니다.

주의사항

이 기능은 대부분의 API에서 작동하지만 몇 가지 경우에는 작동하지 않습니다. 있습니다

최신 libc API는 문제가 발생할 가능성이 가장 적습니다. 나머지 부분과는 달리 헤더에서 #if __ANDROID_API__ >= X로 보호되는 Android API __INTRODUCED_IN(X)뿐만 아니라 약한 선언도 확인할 수 있습니다. 가장 오래된 API 수준의 최신 NDK 지원은 r21이므로 일반적으로 필요한 libc API가 이미 제공되고 있습니다. 새로운 libc API가 각각 추가됨 최신 버전 (status.md 참고)이지만 최신 버전일수록 극소수의 개발자에게 필요한 극단적인 케이스가 될 수 있습니다. 하지만 지금은 dlsym()를 계속 사용하여 API(minSdkVersion가 API보다 오래된 경우) 이건 해결 가능한 문제이지만 그렇게 하면 모든 앱의 소스 호환성이 손상될 위험이 있습니다. libc API의 polyfill이 포함된 코드는 libc와 로컬 선언에서 availability 속성이 일치하지 않으므로 언제 고칠지 확신할 수 없습니다.

더 많은 개발자가 마주할 가능성이 높은 경우는 라이브러리 포함된 새 API가 minSdkVersion보다 최신 버전입니다. 이 기능만 약한 기호 참조를 사용 설정합니다. 허술한 라이브러리 같은 것은 없으니까요. 참조 예를 들어 minSdkVersion이 24인 경우 libvulkan.so를 호출하고 안전하게 vkBindBufferMemory2를 호출합니다. libvulkan.so는 API 24부터 기기에서 사용할 수 있습니다. 반면에 minSdkVersion이 23이었다면 dlopendlsym로 되돌아가야 합니다. 왜냐하면 이 라이브러리는 API 23 이 케이스를 해결할 수 있는 좋은 방법은 알지 못하지만, Google에서 가능한 한 더 이상 새로운 광고를 허용하지 않기 때문에 API를 사용하여 새 라이브러리를 만듭니다.

도서관 저자용

Android 애플리케이션에서 사용할 라이브러리를 개발하는 경우 공개 헤더에 이 기능을 사용하지 않도록 하세요. Kubernetes는 하지만 어떤 코드에서든 __builtin_available를 사용하는 경우 헤더(예: 인라인 함수 또는 템플릿 정의)를 포함하는 경우 모든 이 기능을 사용 설정할 수 있습니다. 같은 이유로 이 기능을 사용할 수 없습니다. 기능을 기본으로 제공하는 경우 대신 이러한 선택을 하는 것을 피해야 합니다. 파악할 수 있습니다.

공개 헤더에 이러한 동작이 필요한 경우, 이 기능을 사용 설정해야 한다는 사실을 사용자가 알 수 있고 그렇게 할 경우의 위험을 인지하고 있어야 합니다.