Benutzerdefinierte Tabs unter Android 11 verwenden

Mit Android 11 wurden Änderungen daran eingeführt, wie Apps mit anderen Apps interagieren können, die der Nutzer auf dem Gerät installiert hat. Weitere Informationen zu diesen Änderungen finden Sie in der Android-Dokumentation.

Wenn eine Android-App, die benutzerdefinierte Tabs verwendet, 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 möglicherweise erforderlich sind.

Im einfachsten Fall können benutzerdefinierte Tabs mit einer Einzeiler-Anweisung gestartet werden:

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

Bei Anwendungen, die Anwendungen mit diesem Ansatz starten oder sogar UI-Anpassungen wie Ändern der Symbolleistenfarbe oder Hinzufügen einer Aktionsschaltfläche vornehmen, sind keine Änderungen an der Anwendung erforderlich.

Bevorzugung nativer Apps

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 bevorzugen sollten, um den Intent zu verarbeiten, anstatt eine benutzerdefinierte Registerkarte zu verwenden, wenn eine App installiert ist, die ihn verarbeiten kann.

Ab Android 11

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 FLAG_ACTIVITY_REQUIRE_NON_BROWSER zu verwenden, um Android zu bitten, beim Starten Browser zu vermeiden.

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

Vor Android 11

Auch wenn die Anwendung auf Android 11 oder API-Level 30 ausgerichtet ist, wird das FLAG_ACTIVITY_REQUIRE_NON_BROWSER-Flag von früheren Android-Versionen nicht erkannt. In diesen Fällen müssen wir den Package Manager 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;
}

Bei diesem Ansatz wird der Paketmanager nach Anwendungen abgefragt, die einen generischen http-Intent unterstützen. Bei diesen Anwendungen handelt es sich wahrscheinlich um Browser.

Fragen Sie dann nach Anwendungen, die Intents für die bestimmte URL verarbeiten, die wir starten möchten. Dadurch werden sowohl Browser als auch native Anwendungen zurückgegeben, die für die Verarbeitung dieser URL eingerichtet sind.

Entfernen Sie nun alle Browser aus der ersten Liste aus der zweiten Liste. Es bleiben nur noch native Apps übrig.

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 sicherstellen, dass wir für jeden Anlass die richtige Methode 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 enthält die benötigten Informationen. Wenn der Wert 30 oder höher ist, kennt Android die FLAG_ACTIVITY_REQUIRE_NON_BROWSER und wir können versuchen, eine native App mit dem neuen Ansatz zu starten. Andernfalls versuchen wir, die App mit dem alten Ansatz zu starten.

Wenn das Starten einer nativen App fehlschlägt, wird ein benutzerdefinierter Tab gestartet.

Diese Best Practice erfordert etwas Boilerplate-Code. Wir arbeiten daran, dies zu vereinfachen, indem wir die Komplexität in einer Bibliothek kapseln. Wir halten Sie über Updates 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 sind das Festlegen des Pakets für den Intent, um das Dialogfeld zur App-Eindeutigkeit zu vermeiden, oder die Auswahl des Browsers, mit dem eine Verbindung hergestellt werden soll, wenn eine Verbindung zum Custom Tabs-Dienst hergestellt wird.

Wenn Entwickler API-Level 30 als Ziel festlegen, müssen sie ihrem Android-Manifest einen „queries“-Abschnitt hinzufügen, in dem ein Intent-Filter deklariert wird, der mit Browsern mit Unterstützung für benutzerdefinierte Tabs übereinstimmt.

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

Wenn das Markup vorhanden ist, funktioniert der vorhandene Code, mit dem nach Browsern gesucht wird, die benutzerdefinierte Tabs unterstützen, wie erwartet.

Häufig gestellte Fragen

F: Der Code, der nach Custom Tabs-Anbietern sucht, fragt nach Anwendungen, die https://-Intents verarbeiten können. Der Abfragefilter deklariert jedoch nur eine android.support.customtabs.action.CustomTabsService-Abfrage. Muss nicht eine Anfrage für https://-Intents deklariert werden?

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

Fazit

Das sind alle Änderungen, die erforderlich sind, um eine vorhandene Custom Tabs-Integration für Android 11 anzupassen. Weitere Informationen zur Integration von benutzerdefinierten Tabs in eine Android-App finden Sie im Implementierungsleitfaden. Best Practices helfen Ihnen, eine erstklassige Integration zu entwickeln.

Wenn Sie Fragen oder Feedback haben, können Sie sich gern an uns wenden.