Использование пользовательских вкладок в 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;
}

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

Затем запросите приложения, которые обрабатывают данные для конкретного 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 для определения того, какие браузеры поддерживают пользовательские вкладки на устройстве. Обычными вариантами использования этого являются установка пакета в Intent, чтобы избежать диалогового окна устранения неоднозначности приложения, или выбор браузера, к которому следует подключаться при подключении к службе пользовательских вкладок .

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

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

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

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

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

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

Заключение

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

Дайте нам знать, если у вас есть какие-либо вопросы или отзывы!