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

Android 11 では、ユーザーがデバイスにインストールした他のアプリとの連携方法が変更されました。この変更の詳細については、Android のドキュメントをご覧ください。

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

最もシンプルなケースでは、カスタムタブを次のように 1 行で起動できます。

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

このアプローチを使用してアプリを起動するアプリの場合、またはツールバーの色の変更などの UI のカスタマイズを追加する場合、アクション ボタンを追加する場合、アプリに変更を加える必要はありません。

ネイティブ アプリを優先する

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

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

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 フラグが認識されないため、そのような場合は Package Manager にクエリを実行する必要があります。

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 を認識し、新しいアプローチで nativa アプリを起動できます。それ以外の場合は、古いアプローチでリリースを試みます。

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

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

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

もう 1 つの一般的なパターンは、PackageManager を使用して、デバイス上のカスタムタブをサポートするブラウザを検出することです。一般的なユースケースとしては、アプリの曖昧さ回避ダイアログを回避するためにインテントにパッケージを設定することや、カスタムタブ サービスに接続するときに接続するブラウザを選択することが挙げられます。

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 アプリに統合する方法については、まず実装ガイドをご覧ください。次に、ベスト プラクティスを確認して、優れた統合の構築方法についてご確認ください。

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