新しい API の使用

このページでは、アプリが新しい OS の機能を新しい OS の 古いデバイスとの互換性を維持しながら、OS のバージョンを管理できます。

デフォルトでは、アプリ内の NDK API への参照は、強い参照になっています。 Android のダイナミック ローダは、ライブラリが更新されたときに、積極的にエラーを解決しようとします。 確認できます。シンボルが見つからない場合、アプリは中止されます。これは次の問題に反しています。 例外はスローされませんが、不足している API が呼び出されるまでは例外がスローされません。 呼び出すことができます。

このため、NDK では、 アプリの minSdkVersion より新しい API。これにより テスト中に機能したものの、読み込みに失敗するコードを誤ってリリースした場合 (UnsatisfiedLinkErrorSystem.loadLibrary() からスローされます)古いバージョン できます。その一方で、API を使用するコードを記述することはより困難です。 これは、アプリの minSdkVersion よりも新しい時間です。これは、API を呼び出す必要があるためです。 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 ファイルとして宣言します。キャンペーンに build.gradle.kts(または build.gradle)ファイルは、ndk-build には必要ありません。

他のビルドシステム

CMake や ndk-build を使用していない場合は、ビルドのドキュメントをご覧ください。 この機能を有効にするための推奨方法があるかどうかを確認します。ビルドが サポートされていない場合は、次の手順で有効にできます。 コンパイル時に次のフラグを渡します。

-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability

まず、弱参照を許可するように NDK ヘッダーを構成します。2 回目のターン 安全でない API 呼び出しに関する警告をエラーに変更できます。

詳しくは、ビルドシステム メンテナンス ガイドをご覧ください。

保護された API 呼び出し

この機能では、新しい API の呼び出しが魔法のように安全に行われるわけではありません。唯一、 読み込み時のエラーが呼び出し時のエラーより遅れることはありません。この方法のメリットは、 実行時のその呼び出しを保護し、適切な代替手段として 代替の実装や、アプリのその機能が そのコードパスを完全に回避することもできます。

Clang は、保護されていない状態を許可すると、警告(unguarded-availability)を出力できます。 アプリの minSdkVersion で使用できない API を呼び出している。もし ndk-build または CMake ツールチェーン ファイルを使用すると、この警告は自動的に この機能を有効にするとエラーになります。

API を条件付きで使用するコードの例を以下に示します。 この機能を有効にするには、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, *) によって保護される必要があります。 ビルドエラーがなければ、API を minSdkVersion でガードが不要なか、ビルドが正しく構成されておらず、 unguarded-availabilityの警告は無効になっています。

NDK API リファレンスには、 API 30 で導入確認します。このテキストがない場合は、 サポートされているすべての API レベルで使用できます。

API ガードの繰り返しの回避

これを使用すると、おそらくアプリ内に 新しいデバイスでしか使用できないためです。同じことを何度も繰り返すのではなく、 __builtin_available(): 各関数をチェックインし、 独自のコードを作成できますたとえば、ImageDecoder API は API 30 で追加されたため、これらを多用する関数では、 次のようなことができます。

#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 が見つからない原因となる操作をユーザーが行うまで失敗しません。 渡されます。この場合、エラー メッセージがはっきりとわからず、 コンパイル時 "AFoo_bar() は利用できません"セグメンテーション違反になりますあり エラー メッセージはより明確になります。 より安全です。

これは新機能であるため、処理するための既存のコードがほとんど作成されていない 安全を確保する必要があります。Android を想定していないサードパーティのコード この問題が常に発生する可能性が高いため、現時点では 変更できます。

この方法をおすすめしますが、問題が発生しやすくなります。 リスクを意識して受け入れることが重要になります。 知らない間に行動が変わるのを防げます。

注意点

この機能はほとんどの API で機能しますが、そうでない場合もあります 説明します。

最も問題が発生する可能性が最も低いのは、新しい libc API です。他の Pod の Android API では、ヘッダーで #if __ANDROID_API__ >= X によって保護されます。 そして __INTRODUCED_IN(X) だけでなく、 できます。最新の NDK がサポートする最も古い API レベルは r21 であるため、 必要な libc API がすでに提供されています。新しい libc API がそれぞれ追加され、 リリースする(status.md を参照)が、新しいほど、リリースする可能性は ほとんどの開発者には必要ないエッジケースになります。とは言え、 これらのデベロッパーに対して呼び出すには、引き続き dlsym() を使用してこれらを呼び出す必要があります。 minSdkVersion が API より古い場合は、API を使用します。これは解決可能な問題です ただし、使用すると、すべてのアプリでソースの互換性が損なわれるリスクが libc API のポリフィルを含むコードは、次の原因によりコンパイルに失敗します。 libc 宣言と local 宣言の availability 属性が一致しないため)。 いつ修正するかは不明です

多くのデベロッパーが遭遇する可能性が高いのは、ライブラリが は、新しい API が minSdkVersion より新しいものです。この機能のみ 弱いシンボル参照が可能になります。ライブラリが弱いことは ご覧ください。たとえば、minSdkVersion が 24 の場合、 libvulkan.so し、vkBindBufferMemory2 への保護された呼び出しを行う。理由は次のとおりです。 libvulkan.so は API 24 以降を搭載したデバイスで使用できます。一方 minSdkVersion が 23 の場合は、dlopendlsym にフォールバックする必要があります。 サポートされているのは、 API 23。この問題を解決するための適切な解決策はわかりませんが、しばらくすると 新しいキーワードによる広告配信は(可能な限り)許可されなくなるため、 新しいライブラリを作成するための API。

図書館の著者向け

Android アプリで使用するライブラリを開発する場合は、 公開ヘッダーでこの機能を使用しないでください。また、kubectl の「get」コマンドや 使用できますが、Java プログラミング言語で __builtin_available に依存する場合は、 使用すると、すべてのエンドポイントを強制的に 使用する必要があります。同じ理由で、Google はこの機能を有効にしていません。 NDK にデフォルトで組み込まれているため、代わりにその選択を行うことは避けるべきです。 消費者の興味を引き付けます

この動作を公開ヘッダーで必要な場合は、 そうすれば、機能を有効にする必要があることと、 そのリスクを認識する必要があります。