在 Android 11 中使用自定义标签页

Android 11 引入了一些变更,涉及应用与用户已在设备上安装的其他应用交互的方式。如需详细了解相关变更,请参阅 Android 文档

当使用自定义标签页的 Android 应用以 SDK 级别 30 或更高级别为目标平台时,可能需要进行一些更改。本文介绍了相关应用可能需要做出的更改。

在最简单的情况下,可以使用一行代码启动自定义标签页,如下所示:

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

应用使用此方法启动应用,甚至添加界面自定义(例如更改工具栏颜色添加操作按钮)时,无需在应用中进行任何更改。

首选本机应用

不过,如果您遵循了最佳实践,则可能需要进行一些更改。

第一个相关最佳实践是,如果安装了能够处理 intent 的应用,则应用应首选原生应用(而不是自定义标签页)来处理 intent。

在 Android 11 及更高版本上

Android 11 引入了新的 intent 标志 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 在启动时避开浏览器。

如果找不到能够处理此 intent 的原生应用,则会抛出 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 intent 的应用。这些应用可能是浏览器。

然后,针对我们要启动的特定网址,查询可处理相关请求的应用。这将返回设置以处理该网址的浏览器和原生应用。

现在,从第二个列表中移除在第一个列表中找到的所有浏览器,这样就只剩下原生应用了。

如果列表为空,就表示没有原生处理程序,系统会返回 false。否则,我们会启动原生处理程序的 intent。

两种方式对比

我们需要确保针对每种情况使用正确的方法:

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 清单中添加一个查询部分,以声明一个 intent 过滤器,以便与支持自定义标签页的浏览器相匹配。

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

添加标记后,用于查询支持自定义标签页的浏览器的现有代码就会按预期运行。

常见问题解答

问:查找自定义标签页提供程序的代码会查询可以处理 https:// intent 的应用,但查询过滤器仅声明 android.support.customtabs.action.CustomTabsService 查询。是否应该声明对 https:// intent 的查询?

答:声明查询过滤条件时,它会过滤 PackageManager 查询的响应,而不是查询本身。由于支持自定义标签页的浏览器声明会处理 CustomTabsService,因此它们不会被过滤掉。不支持自定义标签页的浏览器将会被滤除。

总结

这些是调整现有自定义标签页集成以与 Android 11 配合使用所需的全部更改。如需详细了解如何将自定义标签页集成到 Android 应用中,请先查看实现指南,然后查看最佳实践,了解如何构建一流的集成。

如果您有任何疑问或反馈,请告诉我们!