Benutzerdefinierte Tabs unter Android 11 verwenden

Mit Android 11 wurde die Interaktion von Apps mit anderen auf dem Gerät installierten Apps geändert. Weitere Informationen zu diesen Änderungen finden Sie in der Android-Dokumentation.

Wenn eine Android-App mit benutzerdefinierten Tabs auf SDK-Level 30 oder höher ausgerichtet ist, sind möglicherweise einige Änderungen erforderlich. In diesem Artikel werden die Änderungen beschrieben, die für diese Apps erforderlich sind.

Im einfachsten Fall können benutzerdefinierte Tabs mit einem Einzeiler wie folgt geöffnet werden:

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

Für Anwendungen, die Anwendungen auf diese Weise starten, oder sogar UI-Anpassungen wie das Ändern der Farbe der Symbolleiste oder das Hinzufügen einer Aktionsschaltfläche hinzugefügt werden, müssen keine Änderungen in der Anwendung vorgenommen werden.

Native Apps bevorzugen

Wenn Sie die Best Practices befolgt haben, sind möglicherweise einige Änderungen erforderlich.

Die erste relevante Best Practice besteht darin, dass Anwendungen eine native App für die Verarbeitung des Intents anstelle eines benutzerdefinierten Tabs bevorzugen, wenn eine entsprechende App installiert ist.

Android 11 und höher

In Android 11 wird das neue Intent-Flag FLAG_ACTIVITY_REQUIRE_NON_BROWSER eingeführt. Dies ist die empfohlene Methode zum Öffnen einer nativen App, da die App keine Paketmanagerabfragen deklarieren muss.

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;
    }
}

Die Lösung besteht darin, den Intent zu starten und Android mithilfe von FLAG_ACTIVITY_REQUIRE_NON_BROWSER aufzufordern, Browser beim Starten zu vermeiden.

Wenn keine native App gefunden wird, die diesen Intent verarbeiten kann, wird ein ActivityNotFoundException ausgelöst.

Vor Android 11

Auch wenn die App auf Android 11 oder API-Level 30 ausgerichtet sein kann, verstehen frühere Android-Versionen das Flag FLAG_ACTIVITY_REQUIRE_NON_BROWSER nicht. Daher müssen wir in diesen Fällen den Paketmanager abfragen:

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;
}

Dabei wird der Paketmanager nach Anwendungen abgefragt, die einen generischen http-Intent unterstützen. Diese Anwendungen sind wahrscheinlich Browser.

Fragen Sie dann nach Anwendungen ab, die die Elemente für die spezifische URL verarbeiten, die gestartet werden soll. Dadurch werden sowohl Browser als auch native Anwendungen zurückgegeben, die für diese URL eingerichtet sind.

Entfernen Sie nun alle Browser aus der ersten Liste aus der zweiten Liste. Es werden dann nur noch native Anwendungen behandelt.

Wenn die Liste leer ist, wissen wir, dass es keine nativen Handler gibt, und geben „false“ zurück. Andernfalls starten wir den Intent für den nativen Handler.

Zusammenfassung

Wir müssen darauf achten, für jeden Anlass die richtige Methode zu verwenden:

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 stellt die benötigten Informationen bereit. Ist sie gleich oder größer als 30, kennt Android die FLAG_ACTIVITY_REQUIRE_NON_BROWSER und wir können versuchen, eine Naativa-App mit dem neuen Ansatz auf den Markt zu bringen. Andernfalls versuchen wir, mit dem alten Ansatz zu beginnen.

Wenn das Starten einer nativen App fehlschlägt, veröffentlichen wir benutzerdefinierte Tabs.

Diese Best Practice beinhaltet auch Textbausteine. Wir arbeiten daran, dies zu vereinfachen, indem wir die Komplexität in einer Bibliothek bündeln. Wir halten Sie auch bezüglich der android-browser-helper-Supportbibliothek auf dem Laufenden.

Browser erkennen, die benutzerdefinierte Tabs unterstützen

Ein weiteres gängiges Muster ist die Verwendung von PackageManager, um zu erkennen, welche Browser benutzerdefinierte Tabs auf dem Gerät unterstützen. Häufige Anwendungsfälle dafür sind das Festlegen des Pakets auf den Intent, um das Dialogfeld zur Unterscheidung der Anwendung zu vermeiden, oder die Auswahl des Browsers, zu dem eine Verbindung hergestellt werden soll, wenn Sie eine Verbindung zum Dienst für benutzerdefinierte Tabs herstellen.

Bei der Ausrichtung auf API-Level 30 müssen Entwickler ihrem Android-Manifest einen Bereich für Abfragen hinzufügen und einen Intent-Filter deklarieren, der Browser mit Unterstützung für benutzerdefinierte Tabs abgleicht.

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

Nachdem das Markup implementiert ist, funktioniert der vorhandene Code zur Abfrage von Browsern, die benutzerdefinierte Tabs unterstützen, wie erwartet.

Häufig gestellte Fragen

F: Der Code, mit dem nach benutzerdefinierten Tabs gesucht wird, führt zu Abfragen von Anwendungen, die https://-Intents verarbeiten können. Der Abfragefilter deklariert jedoch nur eine android.support.customtabs.action.CustomTabsService-Abfrage. Sollte keine Abfrage für https://-Intents deklariert werden?

A: Wenn Sie einen Abfragefilter deklarieren, werden die Antworten auf eine Abfrage an den PackageManager gefiltert, nicht an die Abfrage selbst. Da Browser, die benutzerdefinierte Tabs unterstützen, die Verarbeitung des CustomTabsService angeben, werden sie nicht herausgefiltert. Browser, die keine benutzerdefinierten Tabs unterstützen, werden herausgefiltert.

Fazit

Das sind alle Änderungen, die erforderlich sind, um eine bestehende Integration von benutzerdefinierten Tabs an Android 11 anzupassen. Wenn du mehr über die Integration von benutzerdefinierten Tabs in eine Android-App erfahren möchtest, lies zuerst den Implementierungsleitfaden und sieh dir dann die Best Practices an, um zu erfahren, wie du eine erstklassige Integration erstellst.

Bei Fragen oder Feedback kannst du dich gern an uns wenden.