Android 应用链接

通常需要连接网站和移动应用,以便网站上的链接启动移动应用并在移动应用中显示内容。 应用链接(也称为 深层链接)是一种技术,使移动设备能够响应 URI 并在由 URI 表示的移动应用中启动内容。

Android 通过意向系统处理应用链接。 当你在移动浏览器中点击链接时,浏览器会发送一个意图,Android 会将该意图委托给已注册的应用程序。 这些链接可以基于自定义方案,例如 myappname://,也可以使用 HTTP 或 HTTPS 方案。 例如,单击食谱网站上的链接将打开与该网站关联的移动应用,然后向用户显示特定的食谱。 如果注册了多个应用来处理意向,Android 将显示一个消除歧义对话框,要求用户选择哪个应用来处理意向。 未安装您应用的用户将被引导至您网站上的内容。

Android 将应用链接分为三个类别:

  • 深层链接 是任何将用户带到应用中特定内容的方案的 URI。 单击深层链接时,可能会显示一个消除歧义对话框,要求用户选择一个应用来处理深层链接。
  • Web 链接 是使用 HTTP 或 HTTPS 方案的深层链接。 在 Android 12 及更高版本上,Web 链接始终在 Web 浏览器中显示内容。 在早期版本的 Android 上,如果应用可以处理 Web 链接,则会显示一个消除歧义对话框,要求用户选择一个应用来处理 Web 链接。
  • 可在 API 23+ 上使用的 Android 应用链接是使用 HTTP 或 HTTPS 方案的 Web 链接,其中包含该autoVerify属性。 此属性使应用成为应用链接的默认处理程序。 因此,当单击应用链接时,应用将打开而不显示消除歧义对话框。

.NET MAUI Android 应用可以支持所有三类应用链接。 但是,本文重点介绍 Android 应用链接。 这需要证明域的所有权,以及在域上托管数字资产链接文件 JSON 文件,该文件描述与应用的关系。 这使 Android 能够验证尝试处理 URI 的应用是否拥有 URI 域的所有权,以防止恶意应用截获应用链接。

在 .NET MAUI Android 应用中处理 Android 应用链接的过程如下所示:

  1. 验证域所有权。 有关详细信息,请参阅 “验证域所有权”。
  2. 在网站上创建和托管数字资产链接文件。 有关详细信息,请参阅 创建和托管数字资产链接文件
  3. 在应用中为网站 URI 配置意向筛选器。 有关详细信息,请参阅 “配置意向筛选器”。
  4. 从传入意向中读取数据。 有关详细信息,请参阅 从传入意向中读取数据

重要

若要使用 Android 应用链接,请执行以下步骤。

  • 你的应用版本必须位于 Google Play 上。
  • 必须在 Google 开发人员控制台中针对应用注册配套网站。 应用与网站关联后,可以同时为网站和应用编制 URI 的索引,然后可在搜索结果中提供这些 URI。 有关详细信息,请参阅 support.google.com 上的 Google 搜索上的应用索引

有关 Android 应用链接的详细信息,请参阅 处理 Android 应用链接

验证域所有权

需要验证你在 Google 搜索控制台中提供应用链接的域的所有权。 所有权验证意味着证明你拥有特定网站。 Google 搜索控制台支持多种验证方法。 有关详细信息,请参阅在 support.google.com 上验证网站所有权

Android 应用链接要求 Android 在将应用设置为 URI 的默认处理程序之前验证应用与网站之间的关联。 首次安装应用时,将进行此验证。 数字资产链接文件是一个 JSON 文件,必须由位于以下位置的相关 Web 域托管: https://___domain.name/.well-known/assetlinks.json

数字资产文件包含 Android 验证关联所需的元数据。 该文件需要以下键值对:

  • namespace - Android 应用的命名空间。
  • package_name - Android 应用的包名称。
  • sha256_cert_fingerprints - 从 .keystore 文件获取的已签名应用的 SHA256 指纹。 有关查找密钥存储的签名的信息,请参阅 查找密钥存储的签名

以下示例 assetlinks.json 文件向 Android 应用授予链接打开权限 com.companyname.myrecipeapp

[
   {
      "relation": [
         "delegate_permission/common.handle_all_urls"
      ],
      "target": {
         "namespace": "android_app",
         "package_name": "com.companyname.myrecipeapp",
         "sha256_cert_fingerprints": [
            "14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
         ]
      }
   }
]

可以注册多个 SHA256 指纹以支持应用的不同版本或构建。 以下 assetlinks.json 文件授予链接打开权限给 com.companyname.myrecipeappcom.companyname.mycookingapp 两个 Android 应用:

[
   {
      "relation": [
         "delegate_permission/common.handle_all_urls"
      ],
      "target": {
         "namespace": "android_app",
         "package_name": "com.companyname.myrecipeapp",
         "sha256_cert_fingerprints": [
            "14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
         ]
      }
   },
   {
      "relation": [
         "delegate_permission/common.handle_all_urls"
      ],
      "target": {
         "namespace": "android_app",
         "package_name": "com.companyname.mycookingapp",
         "sha256_cert_fingerprints": [
            "14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
         ]
      }
   }
]

小窍门

使用 语句列表生成器和测试器 工具来帮助生成正确的 JSON 并对其进行验证。

将您的 JSON 验证文件发布到 https://___domain.name/.well-known/assetlinks.json 时,您必须确保:

  • 该文件随内容类型 application/json一起提供。
  • 无论应用是否使用 HTTPS 作为方案,都必须通过 HTTPS 访问该文件。
  • 文件必须可访问,而无需重定向。
  • 如果应用链接支持多个域,则必须在每个域上发布 assetlinks.json 文件。

您可以使用 Google 的数字资产链接 API 来确认数字资产文件是否格式正确并且已托管。

https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=
  https://<WEB SITE ADDRESS>:&relation=delegate_permission/common.handle_all_urls

有关详细信息,请在 developer.android.com 上参阅声明网站关联

配置意向筛选器

必须配置意向筛选器,以便将 URI 或 URI 集从网站映射到 Android 应用中的活动。 在 .NET MAUI 中,可以通过向活动添加 IntentFilterAttribute 来实现此目的。 意向筛选器必须声明以下信息:

  • ActionView - 这会注册意向筛选器以响应查看信息的请求。
  • Categories - 意图过滤器应该注册 CategoryDefaultCategoryBrowsable 才能正确处理 Web URI。
  • DataScheme - 意向筛选器必须声明自定义方案,以及/或 HTTPS 和/或 HTTPS。
  • DataHost - 这是 URI 源自的域。
  • DataPathPrefix - 这是网站上的资源的可选路径,必须以 a /开头。
  • AutoVerify - 这告知 Android 验证应用与网站之间的关系。 它必须设置为 true 否则 Android 不会验证应用与网站之间的关联,因此不会将应用设置为 URI 的默认处理程序。

以下示例演示如何使用 IntentFilterAttribute 来处理来自 https://www.recipe-app.com/recipes 的链接:

using Android.App;
using Android.Content;
using Android.Content.PM;

namespace MyNamespace;

[Activity(
    Theme = "@style/Maui.SplashTheme",
    MainLauncher = true,
    ConfigurationChanges = ConfigChanges.ScreenSize |
        ConfigChanges.Orientation |
        ConfigChanges.UiMode |
        ConfigChanges.ScreenLayout |
        ConfigChanges.SmallestScreenSize |
        ConfigChanges.KeyboardHidden |
        ConfigChanges.Density)]
[IntentFilter(
    new string[] { Intent.ActionView },
    Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
    DataScheme = "https",
    DataHost = "recipe-app.com",
    DataPath = "/recipe",
    AutoVerify = true,)]    
public class MainActivity : MauiAppCompatActivity
{
}

注释

可以在意向筛选器中指定多个方案和主机。 有关详细信息,请参阅在 developer.android.com 上创建指向应用内容的深层链接

在将应用注册为 URI 的默认处理程序之前,Android 会将意图筛选器中标识的每个主机与网站上的数字资产文件进行核对。 所有意向筛选器都必须通过验证,然后 Android 才能将应用建立为默认处理程序。 添加包含活动内容 URI 的意图过滤器后,Android 能够在运行时将具有匹配 URI 的任何意图路由到应用。

可能还需要将活动标记为可导出,以便其他应用可以启动活动。 可以通过将 Exported = true 添加到现有的 ActivityAttribute 来实现此目的。 有关详细信息,请参阅 developer.android.com 上的 Activity 元素

调用 Web URI 意向时,Android 会尝试以下作,直到请求成功:

  1. 打开用于处理 URI 的首选应用。
  2. 打开唯一可用的应用来处理 URI。
  3. 允许用户选择应用来处理 URI。

有关意图和意图过滤器的详细信息,请参阅 developer.android.com 上的意图和意图过滤器

从传入意图中读取数据

当 Android 通过意图过滤器启动您的活动时,可以使用意图提供的数据来确定要执行的操作。 这应在早期生命周期委托中执行,理想的委托是 OnCreate。 当创建活动时,将调用OnCreate委托。 有关生命周期委托的详细信息,请参阅 平台生命周期事件

要响应触发的 Android 生命周期委托,请在 MauiProgram 类的 CreateMauiapp 方法中对 MauiAppBuilder 对象调用 ConfigureLifecycleEvents 方法。 然后,在 ILifecycleBuilder 对象上,调用方法 AddAndroid 并指定 Action 来为所需的委托注册一个处理程序。

using Microsoft.Maui.LifecycleEvents;
using Microsoft.Extensions.Logging;

namespace MyNamespace;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            })
            .ConfigureLifecycleEvents(lifecycle =>
            {
#if ANDROID
                lifecycle.AddAndroid(android =>
                {
                    android.OnCreate((activity, bundle) =>
                    {
                        var action = activity.Intent?.Action;
                        var data = activity.Intent?.Data?.ToString();

                        if (action == Android.Content.Intent.ActionView && data is not null)
                        {
                            Task.Run(() => HandleAppLink(data));
                        }
                    });
                });
#endif
            });

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }

    static void HandleAppLink(string url)
    {
        if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri))
            App.Current?.SendOnAppLinkRequestReceived(uri);
    }
}

Intent.Action 属性检索与传入意向关联的作,该 Intent.Data 属性检索与传入意向关联的数据。 如果将意图操作设置为 ActionView,那么可以使用 SendOnAppLinkRequestReceived 方法将意图数据传递到 App 类。

警告

应用链接为应用提供潜在的攻击途径,因此请确保验证所有 URI 参数并丢弃任何格式不正确的 URI。

在你的App类中,重写OnAppLinkRequestReceived方法以接收和处理意图数据:

namespace MyNamespace;

public partial class App : Application
{
    ...

    protected override async void OnAppLinkRequestReceived(Uri uri)
    {
        base.OnAppLinkRequestReceived(uri);

        // Show an alert to test that the app link was received.
        await Dispatcher.DispatchAsync(async () =>
        {
            await Windows[0].Page!.DisplayAlert("App link received", uri.ToString(), "OK");
        });

        Console.WriteLine("App link: " + uri.ToString());
    }
}
namespace MyNamespace;

public partial class App : Application
{
    ...

    protected override async void OnAppLinkRequestReceived(Uri uri)
    {
        base.OnAppLinkRequestReceived(uri);

        // Show an alert to test that the app link was received.
        await Dispatcher.DispatchAsync(async () =>
        {
            await Windows[0].Page!.DisplayAlertAsync("App link received", uri.ToString(), "OK");
        });

        Console.WriteLine("App link: " + uri.ToString());
    }
}

在上面的示例中, OnAppLinkRequestReceived 覆盖显示应用链接 URI。 实际上,应用链接应将用户直接转到 URI 表示的内容,而无需任何提示、登录或其他中断。 因此,OnAppLinkRequestReceived 覆盖是调用导航到 URI 所表示内容的位置。

如果数字资产文件已正确托管,则可以使用 Android 调试桥( adb使用活动管理器工具 am)模拟打开 URI 以确保应用链接正常工作。 例如,以下命令尝试查看与 URI 关联的目标应用活动:

adb shell am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d YOUR_URI_HERE

此命令将发送一个意图,Android 会将其定向到您的移动应用,该应用应启动并显示为该 URI 注册的活动。

注释

可以针对模拟器或设备运行 adb

此外,还可以显示设备上安装的应用的现有链接处理策略:

adb shell dumpsys package ___domain-preferred-apps

此命令将显示以下信息:

  • 包 - 应用的包名称。
  • 域 - 由空格分隔的域,其 Web 链接将由应用处理。
  • 状态 - 应用的当前链接处理状态。 一个值always,表示应用已设置为AutoVerifytrue并已通过系统验证。 紧随其后的是一个表示首选项记录的十六进制数。

有关命令的详细信息 adb ,请参阅 Android 调试桥

此外,还可以通过 Play Console 管理和验证 Android 应用链接。 有关详细信息,请参阅 developer.android.com 上的 “管理和验证 Android 应用链接 ”。

有关故障排除建议,请参阅 developer.android.com 上的 修复常见实现错误