Использование пользовательских вкладок в Android 11

Android 11 внес изменения в то, как приложения могут взаимодействовать с другими приложениями, которые пользователь установил на устройстве. Подробнее об этих изменениях можно прочитать в документации Android .

Когда приложение Android, использующее пользовательские вкладки, нацелено на уровень SDK 30 или выше, могут потребоваться некоторые изменения. В этой статье рассматриваются изменения, которые могут потребоваться для таких приложений.

В простейшем случае пользовательские вкладки можно запустить с помощью такой однострочной команды:

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

Приложения, запускающие приложения с использованием этого подхода или даже добавляющие настройки пользовательского интерфейса, такие как изменение цвета панели инструментов или добавление кнопки действия , не потребуют внесения каких-либо изменений в приложение.

Предпочитая нативные приложения

Однако если вы следовали лучшим практикам, могут потребоваться некоторые изменения.

Первая важная передовая практика заключается в том, что приложения должны отдавать предпочтение собственному приложению для обработки намерения вместо пользовательской вкладки, если установлено приложение, способное его обрабатывать.

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

Решение — попытаться запустить Intent и использовать 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;
}

Подход, используемый здесь, заключается в запросе Package Manager на наличие приложений, поддерживающих generic http intent. Такими приложениями, скорее всего, являются браузеры.

Затем запросите приложения, которые обрабатывают элементы для конкретного URL, который мы хотим запустить. Это вернет настройки браузеров и собственных приложений для обработки этого URL.

Теперь удалите из второго списка все браузеры, найденные в первом списке, и у нас останутся только нативные приложения.

Если список пуст, мы знаем, что нет собственных обработчиков, и возвращаем 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 с новым подходом. В противном случае мы пробуем запустить со старым подходом.

Если запуск собственного приложения не удается, мы запускаем пользовательские вкладки.

В этой передовой практике есть некоторая шаблонность. Мы работаем над тем, чтобы сделать это проще, инкапсулируя сложность в библиотеку. Следите за обновлениями библиотеки поддержки android-browser-helper .

Обнаружение браузеров, поддерживающих пользовательские вкладки

Другой распространенный шаблон — использование PackageManager для определения того, какие браузеры поддерживают Custom Tabs на устройстве. Распространенные варианты использования для этого — настройка пакета в Intent, чтобы избежать диалогового окна устранения неоднозначности приложения или выбор браузера для подключения при подключении к службе Custom Tabs .

При нацеливании на уровень API-уровня 30 разработчики должны будут добавить раздел запросов в свой манифест Android, объявив скопление намерения, которое соответствует браузерам с пользовательской поддержкой вкладок.

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

После добавления разметки существующий код, используемый для запроса браузеров, поддерживающих пользовательские вкладки, будет работать так, как и ожидалось.

Часто задаваемые вопросы

В: Код, который ищет поставщиков пользовательских вкладок, запрашивает приложения, которые могут обрабатывать намерения https:// , но фильтр запросов объявляет только запрос android.support.customtabs.action.CustomTabsService . Разве не должен быть объявлен запрос для намерений https:// ?

A: При объявлении фильтра запроса он будет фильтровать ответы на запрос к PackageManager, а не сам запрос. Поскольку браузеры, поддерживающие Custom Tabs, объявляют обработку CustomTabsService, они не будут отфильтрованы. Браузеры, не поддерживающие Custom Tabs, будут отфильтрованы.

Заключение

Это все изменения, необходимые для адаптации существующей интеграции пользовательских вкладок к работе с Android 11. Чтобы узнать больше об интеграции пользовательских вкладок в приложение Android, начните с руководства по внедрению , а затем ознакомьтесь с передовыми методами создания первоклассной интеграции.

Если у вас есть вопросы или пожелания, сообщите нам!