Android 11 でのカスタムタブの使用

Android 11 では、アプリがデバイスにインストールされている他のアプリを操作する方法が変更されました。変更内容について詳しくは、Android のドキュメントをご覧ください。

カスタムタブを使用する Android アプリが SDK レベル 30 以降をターゲットとしている場合、一部の変更が必要になることがあります。この記事では、そのようなアプリに必要となる変更について説明します。

最も単純なケースでは、カスタムタブは次のような 1 行のコマンドで起動できます。

new CustomTabsIntent.Builder().build()
        .launchUrl(this, Uri.parse("https://www.example.com"));

この方法でアプリを起動するアプリや、ツールバーの色の変更アクション ボタンの追加などの UI カスタマイズを追加するアプリは、アプリを変更する必要はありません。

ネイティブ アプリの優先

ただし、ベスト プラクティスに沿って作成した場合は、変更が必要になることがあります。

関連する最初のベスト プラクティスは、インテントを処理できるアプリがインストールされている場合は、カスタムタブではなくネイティブ アプリを優先することです。

Android 11 以降の場合

Android 11 では、新しいインテント フラグ FLAG_ACTIVITY_REQUIRE_NON_BROWSER が導入されました。これは、アプリがパッケージ マネージャー クエリを宣言する必要がないため、ネイティブ アプリを開く際に推奨される方法です。

static boolean launchNativeApi30(Context context, Uri uri) {
    Intent nativeAppIntent = new Intent(Intent.ACTION_VIEW, uri)
            .addCategory(Intent.CATEGORY_BROWSABLE)
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                    Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
    try {
        context.startActivity(nativeAppIntent);
        return true;
    } catch (ActivityNotFoundException ex) {
        return false;
    }
}

解決策としては、インテントを起動しようとし、FLAG_ACTIVITY_REQUIRE_NON_BROWSER を使用して、起動時にブラウザを使用しないように Android に指示します。

このインテントを処理できるネイティブ アプリが見つからない場合、ActivityNotFoundException がスローされます。

Android 11 より前

アプリが Android 11 または API レベル 30 をターゲットとしている場合でも、それ以前の Android バージョンでは FLAG_ACTIVITY_REQUIRE_NON_BROWSER フラグが認識されないため、そのような場合はパッケージ マネージャーにクエリを実行する必要があります。

private static boolean launchNativeBeforeApi30(Context context, Uri uri) {
    PackageManager pm = context.getPackageManager();

    // Get all Apps that resolve a generic url
    Intent browserActivityIntent = new Intent()
            .setAction(Intent.ACTION_VIEW)
            .addCategory(Intent.CATEGORY_BROWSABLE)
            .setData(Uri.fromParts("http", "", null));
    Set<String> genericResolvedList = extractPackageNames(
            pm.queryIntentActivities(browserActivityIntent, 0));

    // Get all apps that resolve the specific Url
    Intent specializedActivityIntent = new Intent(Intent.ACTION_VIEW, uri)
            .addCategory(Intent.CATEGORY_BROWSABLE);
    Set<String> resolvedSpecializedList = extractPackageNames(
            pm.queryIntentActivities(specializedActivityIntent, 0));

    // Keep only the Urls that resolve the specific, but not the generic
    // urls.
    resolvedSpecializedList.removeAll(genericResolvedList);

    // If the list is empty, no native app handlers were found.
    if (resolvedSpecializedList.isEmpty()) {
        return false;
    }

    // We found native handlers. Launch the Intent.
    specializedActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(specializedActivityIntent);
    return true;
}

ここで使用されるアプローチは、汎用の http インテントをサポートするアプリケーションを Package Manager にクエリすることです。このようなアプリはブラウザである可能性が高いです。

次に、起動する特定の URL のアイテムを処理するアプリをクエリします。これにより、その URL を処理するように設定されたブラウザとネイティブ アプリケーションの両方が返されます。

次に、最初のリストに表示されたブラウザをすべて 2 番目のリストから削除します。残るのはネイティブ アプリのみです。

リストが空の場合、ネイティブ ハンドラがないことがわかるので、false を返します。それ以外の場合は、ネイティブ ハンドラのインテントを起動します。

すべてを組み合わせる

状況に応じて適切な方法を使用する必要があります。

static void launchUri(Context context, Uri uri) {
    boolean launched = Build.VERSION.SDK_INT >= 30 ?
            launchNativeApi30(context, uri) :
            launchNativeBeforeApi30(context, uri);

    if (!launched) {
        new CustomTabsIntent.Builder()
                .build()
                .launchUrl(context, uri);
    }
}

Build.VERSION.SDK_INT には、必要な情報が提供されます。30 以上の場合、Android は FLAG_ACTIVITY_REQUIRE_NON_BROWSER を認識し、新しいアプローチでネイティブアプリを起動できます。問題が解決しない場合は、古い方法でリリースを試みます。

ネイティブアプリの起動に失敗した場合は、カスタムタブが起動されます。

このベスト プラクティスには、いくつかのボイラープレートが含まれています。Google では、複雑さをライブラリにカプセル化して、このプロセスを簡素化できるよう取り組んでいます。android-browser-helper サポート ライブラリの最新情報にご注目ください。

カスタムタブをサポートするブラウザの検出

別の一般的なパターンは、PackageManager を使用して、デバイスで Custom Tabs をサポートしているブラウザを検出することです。たとえば、アプリの不明確化ダイアログを回避するためにインテントにパッケージを設定する、カスタムタブ サービスに接続するときに接続するブラウザを選択する、といったユースケースがあります。

API レベル 30 をターゲットとする場合は、Android マニフェストにクエリ セクションを追加し、カスタムタブをサポートするブラウザに一致するインテント フィルタを宣言する必要があります。

<queries>
    <intent>
        <action android:name=
            "android.support.customtabs.action.CustomTabsService" />
    </intent>
</queries>

マークアップが設定されると、カスタムタブをサポートするブラウザをクエリするために使用される既存のコードが想定どおりに機能します。

よくある質問

Q: カスタムタブ プロバイダを探すコードは、https:// インテントを処理できるアプリケーションをクエリしますが、クエリフィルタは android.support.customtabs.action.CustomTabsService クエリのみを宣言します。https:// インテントのクエリを宣言すべきではないですか?

A: クエリフィルタを宣言すると、クエリ自体ではなく、PackageManager へのクエリへのレスポンスがフィルタされます。カスタムタブをサポートするブラウザは、CustomTabsService の処理を宣言するため、フィルタされません。カスタムタブに対応していないブラウザは除外されます。

まとめ

以上が、既存のカスタムタブ統合を Android 11 で動作するように適応させるために必要な変更です。カスタム タブを Android アプリに統合する方法について詳しくは、まず実装ガイドをご覧になり、次にベスト プラクティスをご覧になって、ファーストクラスの統合を構築する方法をご覧ください。

ご不明な点やご意見がございましたら、お気軽にお問い合わせください。