跳到主要内容

Deep Link

Deep link 和 universal link 分别是在 Android 和 iOS 设备上打开您的应用中特定页面的方法。

Deep link 为一种重定向 URI,可用于将访问者转向应用中的内容,而 web link 则是使用 HTTP 或 HTTPS 协议的 deep link。要在 Android 创建 deep link,可通过在应用的 activity 中添加 intent-filter 来实现。由于玩家设备上的其他应用(例如系统浏览器)可能也有办法处理相同类型的 intent,玩家可选择使用其他应用来打开您的 deep link。

Android 6.0(API 级别 23)及更高版本支持使用 Android 应用链接(Android App Links),通过添加到 web link 中的 autoVerify 属性,将您的应用设置为某种链接类型的默认处理应用。Android 系统将自动校验您的应用是否符合 intent-filter 中所指定的 host 网域。然而,需注意玩家仍可通过手动修改系统设置来选择该链接类型的默认处理应用。

更多信息,请参见 处理 Android 应用链接

步骤1:配置 Player Network SDK

版本要求

Player Network SDK V1.12.00 及以上

  1. AndroidManifest.xml 中添加 URI 相关的 intent-filter,并配置您的 URI 数据。

    自定义协议链接

    配置 deep link:

    • scheme - 自定义协议
    • host - 自定义 host 网域
    • pathPrefix(可选)- 自定义页面路径前缀,详见 多商店渠道包

    Deep link lipass://app/xxxxxx 代码示例:

    <activity
    android:name=".ExampleActivity"
    android:exported="true"
    ...>
    <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="lipass"
    android:host="app" />
    </intent-filter>
    </activity>
    HTTP/HTTPS 协议链接

    选择配置 web link(HTTP/HTTPS 协议的 deep link)或 Android App Link(用于设置默认处理程序,含自动校验的 web link):

    • scheme - http 或 https
    • host - 自定义 host 网域
    • pathPrefix(可选)- 自定义页面路径前缀,详见 多商店渠道包

    Web link https://www.levelinfinite.com/app/xxxxxx 代码示例:

    <activity
    android:name=".ExampleActivity"
    android:exported="true"
    ...>
    <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="http"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    <data android:scheme="https"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    </intent-filter>
    </activity>

    Android App Link https://www.levelinfinite.com/app/xxxxxx 代码示例(Android 6.0 及更高版本支持):

    <activity
    android:name=".ExampleActivity"
    android:exported="true"
    ...>
    <intent-filter android:autoVerify="true"><!-- 开启自动校验 -->
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="http"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    <data android:scheme="https"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    </intent-filter>
    </activity>

    代码示例:

    <activity
    android:name=".ExampleActivity"
    android:exported="true"
    ...>
    <intent-filter>
    <!-- 该 intent-filter 支持 deep link `lipass://app/xxxxxx` -->
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="lipass"
    android:host="app" />
    </intent-filter>
    <intent-filter android:autoVerify="true">
    <!-- 该 intent-filter 支持 Android App Link `https://www.levelinfinite.com/app/xxxxxx` -->
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="http"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    <data android:scheme="https"
    android:host="www.levelinfinite.com"
    android:pathPrefix="/app" />
    </intent-filter>
    </activity>
  2. 处理 intent 数据。

    MainActivityonCreateonNewIntent 中调用 getData()getAction() 方法,检索与传入 Intent 相关联的数据和操作。

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Intent intent = getIntent();
    String action = intent.getAction();
    Uri data = intent.getData();

    if (data != null) {
    INTLSDK.Deeplink.receive(data.toString());
    }
    }

    public void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    INTLSDK.onNewIntent(intent);

    Uri appLinkData = intent.getData();
    if (appLinkData != null) {
    INTLSDK.Deeplink.receive(appLinkData.toString());
    }
    }

步骤2:配置官网

注意

此步骤仅适用于已配置 Android App Link 场景,如果您的应用未使用 Android App Link 则为可选配置。

通过 Google Search Console 验证您对应用和网域的所有权,在以下位置部署 Digital Asset Links JSON 文件:

https://domain.name/.well-known/assetlinks.json

Digital Asset Links JSON 文件使用下列字段标识关联的应用:

  • package_name: 在应用的 build.gradle 文件中声明的应用 ID。

  • sha256_cert_fingerprints: 应用的签名证书的 SHA256 指纹。此字段支持多个指纹,这些指纹可用于支持不同版本的应用,例如调试版 build 和正式版 build。

    您可以利用 Java 密钥工具,通过以下命令生成该指纹:

    $ keytool -list -v -keystore my-release-key.keystore

以下 assetlinks.json 示例文件可为 android_app 授予打开 com.example 链接的权限:

[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example",
"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"]
}
}]

[可选] 多商店渠道包

图片: Multi-Channel Configuration

对于不同包名的渠道包,可使用 android:pathPrefix 属性进行区分,例如 渠道 A 添加了 android:pathPrefix="/sample" 则如果同时安装了渠道A和渠道B包,使用 http://sdk-deeplink-confs.intlgame.com/sample 链接会拉起渠道A。

多商店渠道包 JSON 文件配置示例:

[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.puppies.app",
"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.example.monkeys.app",
"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"]
}
}]
说明

因为微信内置浏览器的限制,在微信中打开链接时不会跳转应用。

安装应用后,在 Android 文本编辑器打开测试链接:

  1. 在 Android 文本应用 (例如便签) 中点击链接跳转应用后,在日志中包含 [ (intl_deeplink_manager.cpp:37) Receive] 字样。

    图片:app_link

  2. 使用 adb 测试 intent-filter URL 的一般语法为:

    $ adb shell am start
    -W -a android.intent.action.VIEW
    -d "URL" PACKAGE

    例如,以下命令试图查看与 lipass://app/xxxxxx 相关的目标应用 activity。

    $ adb shell am start
    -W -a android.intent.action.VIEW
    -d "lipass://app/xxxxxx" com.example.android_app
注意

调用网页 URI 时,Android 系统会依序尝试执行下列操作,直到请求成功为止:

  1. 如果已指定该链接类型的默认处理应用,则打开此应用。
  2. 打开唯一可以处理该链接类型的应用。
  3. 允许玩家从对话框中选择应用打开。

步骤1:配置 Web 服务器

配置 JSON 文件 apple-app-site-association 并将其添加在 Web 服务器的根目录。Universal link 不需要部署在官网的域名下,只需部署在确保可以访问的高级目录中。若需部署在官网,请参见下文中的示例或 Supporting associated domains

将 universal link 部署在官网域名的配置:

{
"applinks": {
"apps": [],
"details": [
{
"appID": "9JA89QQLNQ.com.apple.wwdc",
"paths": [ "/wwdc/news/", "/videos/wwdc/2015/*"]
},
{
"appID": "ABCD1234.com.apple.wwdc",
"paths": [ "*" ]
}
]
}
}
  1. apps 键必须存在,其值必须是空数组
  2. details 键对应一个字典数组,网站支持每个应用程序一部字典,数组中字典的顺序决定了系统在查找匹配项时遵循的顺序,因此可以指定一个应用程序来处理网站的特定部分。
  3. 每个特定应用程序的字典都包含一个 appID 键和一个 paths 链:
    • appID 键的值是团队 ID 或应用 ID 前缀,后跟Bundle ID。
    • paths 键的值是一个字符串数组,用于指定应用程序支持的网站路径以及不与应用程序关联的网站路径。指定不作为 universal link 处理的区域时,将 "NOT" (包括空格)添加到路径字符串的开头,例如 "paths": [ "/wwdc/news/", "NOT/videos/wwdc/2010/*", "/videos/wwdc/201?/*"]。由于系统 paths 会按照指定的顺序评估数组中的每个路径,并在找到正匹配或负匹配时停止评估,因此需在低优先级路径之前指定高优先级路径。
说明

对于重签名的 App,必须将重签名证书的 appID 以及 paths 键也配置在这里。

apple-app-site-association 文件中指定网站路径 paths 的方法:

  • * 指定整个网站。
  • 用一个特定的 URL 指定一个特定的链接,例如 /wwdc/news/
  • * 匹配任何字符串,并将 * 附加到特定的 URL 以指定网站的一部分,例如 /videos/wwdc/2015/*
  • ? 匹配任何单个字符,并将两个通配符(*?) 组合在一个路径中,例如 /foo/*/bar/201?/mypage
说明

paths 数组中指定网站路径的字符串必须区分大小写。

步骤2:配置 associated domains

  1. 开启 AppleID Associated Domains 能力需联系证书管理相关负责人申请开通。更多详情,请参见 Supporting associated domains

    图片: Associated Domains

  2. 开启 Associated Domains 能力后,请重新生成签名描述文件。

  3. 配置 Xcode 能力。在 Xcode 工程中的 Signing & Capabilities 中开启 Associated Domains,并在 Domains 添加您的域名,格式为 applinks:your_link

    图片: Associated_Domains_Xcode

    注意

    配置 Domains 时无需添加 https://

游戏也可以通过代码和配置自动添加开启 Associated Domains

可以在 PostProcess 中添加以下代码:

#if UNITY_2019_3_OR_NEWER
var capManager = new UnityEditor.iOS.Xcode.ProjectCapabilityManager(projPath, entitlementsFilePath, targetGuid: targetProjectName);
#else
var capManager = new UnityEditor.iOS.Xcode.ProjectCapabilityManager(projPath, entitlementsFilePath, targetProjectName);
#endif

capManager.AddAssociatedDomains(new string[] { "your_link1", "your_link2", "your_link3" });

步骤3:配置 Player Network SDK

版本要求

Player Network SDK V1.12.00 及以上

由于当前 INTLCore 对该入口方法 hook 不生效,所以在 Unity 的 UnityAppController.mm 文件的 UnityAppController 类中添加 iOS 的生命周期入口函数。

目前 XCodePostProcess.cs 代码已经自动在编译后的处理流程中添加了 application:continueUserActivity:restorationHandler 方法 (函数中不需要额外添加代码)。

注意

不要在 UnityAppController 的子类中添加生命周期入口函数。确保入口函数是在跟 application:openURL:options: 等系统生命周期函数在同一个类中。

- (BOOL)application:(UIApplication *)application continueUserActivity:
(NSUserActivity *)userActivity restorationHandler:(void(^)
(NSArray<id<UIUserActivityRestoring>> *
__nullablerestorableObjects))restorationHandler {
return YES;
}

步骤4:测试 universal link

说明

因为微信内置浏览器的限制,在微信中打开链接时不会跳转应用。

在 iOS 备忘录中点击应用的链接后,应跳转至应用并在日志中记录:

INTLApplicationDelegate:continueUserActivity:restorationHandler

示例:
图片: INTL_Universal_Link_Log

注意

在安装或更新后首次打开应用时,系统会从对应的域名请求 apple-app-site-association 文件。当配置正确但 universal link 不可访问时,请尝试重启手机或重新安装应用。

进行 apple-app-site-association 以及应用的 Xcode 工程配置之后,整个 universal link 运作流程完全由系统控制。据推断,在安装或更新后,首次打开应用时会发生以下情况:

  1. App 向工程里配置的域名发起 Get 请求拉取 apple-app-site-association 文件。
  2. App 将 apple-app-site-association 注册到系统。
  3. 在 WebView 中打开 URL 时根据注册的 apple-app-site-association URL 检查打开的 URL。
  4. 如果命中了注册过的 universal link,则打开应用触发 universal link delegate。
  5. 如果没命中,则 WebView 继续跳转 URL。

AppsFlyer 支持 AppsFlyer SDK 提供的以下深度链接功能:

  • 深度链接(Deep linking):深度链接发生在目标应用程序已经安装的情况下。配置的深度链接会将用户发送到特定的应用内位置或完成特定的应用内活动。

  • 延迟深度链接(Deferred deep linking):延迟深度链接发生在目标应用程序未安装时。配置的深度链接会将用户发送到特定应用商店中目标应用的下载页面。当用户安装并打开应用时,延迟深度链接会将用户发送到目标应用内位置或完成目标应用内活动。

AppsFlyer OneLink 是 AppsFlyer 独有的功能,指的是跨平台功能的归属链接,支持将用户从各种源平台(如电子邮件、社交媒体、网络、二维码等)转发到目标应用程序,在此过程中还会转发用户的 ID。运营团队需要确认返回的 ID 是 OpenID 还是设备 ID,然后在最终数据分析中使用得到的用户 ID 将相关渠道归属于受邀用户。

步骤1:在 AppsFlyer 控制台上创建一个应用

根据 AppsFlyer 文档 添加应用程序 来创建您的应用程序。

步骤2:配置 OneLink

注意

如果是 OneLink 管理的新手,请根据新客户使用指引配置 OneLink 模板。更多信息,请参见 OneLink 模板

  1. 进入 AppsFlyer OneLink 管理

  2. 在右上角,点击菜单图标后选择添加 OneLink 模板,并记录模板 ID。

    图片:OneLink配置

  3. 在 OneLink 设置页面,输入模板名称,子域名,并选择之前创建的应用程序。
    记录输入的子域名。

    图片:OneLink 配置 2

  4. 对于 Android 应用程序,在 When app is installed 部分,点击 Use App Links to launch the app 添加 keystoreSHA-256。更多信息,请参见 Android initial setup

    图片:OneLink 配置 3

  5. 复制并保存 AppsFlyer 后台生成的 intent-filter 代码。

    图片:OneLink 配置 3

  6. 保存 OneLink 模板。

步骤3:配置 Player Network SDK

Android
  1. INTLConfig.ini[AppsFlyer] 节点下添加以下代码,并将 {OneLink_Template_ID} 替换为 步骤2 记录的模板 ID。

    APPSFLYER_APP_INVITE_ONELINK_ID_ANDROID = {OneLink_Template_ID}
  2. INTLConfig.ini 中的 [Android LifeCycle] 不包含 AppsFlyer,则添加 AppsFlyer。

    LIFECYCLE = WeChat,QQ,Twitter,Adjust,Facebook,Google,Line,VK,Garena,Discord,Dmm,Update,Firebase,KaKao,WhatsApp,Permission,GooglePGS,AppsFlyer
  3. AndroidManifest.xml 的游戏 MainActivity 节点下添加在 步骤2 中保存的 Android intent-filter 代码。
    此代码用于打开游戏,请根据实际情况替换 {Subdomain}{OneLink_Template_ID}

    <activity
    android:name="com.intlgame.unity.MainActivity"
    android:configChanges="fontScale|keyboard|keyboardHidden|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"
    android:exported="true"
    android:label="@string/app_name"
    android:launchMode="singleTask">
    <intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data
    android:host="{Subdomain}.onelink.me"
    android:pathPrefix="/{OneLink_Template_ID}"
    android:scheme="https" />
    </intent-filter>
    </activity>
iOS
  1. INTLConfig.ini[AppsFlyer] 节点下添加以下代码,并将 {OneLink_Template_ID} 替换为 步骤2 记录的模板 ID。

    APPSFLYER_APP_INVITE_ONELINK_ID_IOS = {OneLink_Template_ID}
  2. Assets\INTLSDK\Editor\XUPorter\XCodePostProcess.cs 中,将从步骤2中得到的子域名 (例如 appslinks:subdomain.onelink.me) 添加到该行。

    capManager.AddAssociatedDomains(new string[] { "appslinks:subdomain.onelink.me"});

更多信息,请参见 OneLink 链接与用户体验

  1. 设置生成 OneLink 的回调。

    INTLAPI.AddExtendResultObserver(OnExtendEvent);//设置回调
    INTLExtend.Invoke("AppsFlyer", "generateInviteLinkUrl", paramsJsonString);// paramsJsonString是需要传送给OneLink的信息
    public void OnExtendEvent(INTLExtendResult extendResult)
    {
    if ("generateInviteLinkUrl".Equals(extendResult.ExtendMethodName))//生成 OneLink 的方法
    {
    if (extendResult.RetCode == 0)//OneLink 生成成功
    {
    string inviteUrl = extendResult.RetMsg;//生成的 OneLink,可以分享给其他玩家
    //TODO:share link to other players
    }
    else//OneLink 生成失败
    {
    //TODO: handle OneLink Failed result
    }
    }
    }

  2. 设置被分享链接的用户点击生成的 OneLink 后的结果回调。

    生成 OneLink 和点击 OneLink 共用一个回调,通过回调中的 INTLExtendResult 的参数区分。

        INTLAPI.AddExtendResultObserver(OnExtendEvent);//设置回调
    public void OnExtendEvent(INTLExtendResult extendResult)
    {
    if ("OnOneLinkResult".Equals(extendResult.ExtendMethodName))//OneLink 点击回调
    {
    if (extendResult.RetCode == 0)//OneLink 点击回调成功
    {
    string jsonParameter = extendResult.ExtraJson;//点击 OneLink 后 AF 返回的参数
    //TODO:Handle OneLink Parameter
    }
    else//OneLink 点击回调失败
    {
    //TODO: handle OneLink Failed result
    }
    }
    }

  3. 参考以下代码生成 OneLink,并根据实际情况替换代码中的参数。

    StringBuilder sb = new StringBuilder();
    sb.Append("{");
    sb.Append("\"deep_link_value\":\"abc\"").Append(","); //<TARGET_VIEW>

    sb.Append("\"deep_link_sub1\":\"1234\"").Append(",");//<PROMO_CODE>
    sb.Append("\"deep_link_sub2\":\"1234\"").Append(",");//<REFERRER_ID(openid)>
    sb.Append("\"channel\":\"mobile_share\"").Append(",");//Channel
    sb.Append("\"campaign\":\"summer_sale\"");//Campaign
    //其他参数,可选
    sb.Append("\"deep_link_sub3\":\"1234\"").Append(",");
    sb.Append("\"deep_link_sub4\":\"1234\"").Append(",");
    sb.Append("\"deep_link_sub5\":\"1234\"").Append(",");
    sb.Append("\"deep_link_sub6\":\"1234\"").Append(",");
    sb.Append("\"deep_link_sub7\":\"1234\"").Append(",");
    sb.Append("\"deep_link_sub8\":\"1234\"").Append(",");

    sb.Append("\"af_sub4\":\"12324\"").Append(",");
    sb.Append("\"af_sub5\":\"dfasdf\"").Append(",");

    sb.Append("}");
    String paramsJsonString = sb.ToString();
    INTLExtend.Invoke("AppsFlyer", "generateInviteLinkUrl", paramsJsonString);

步骤4:测试 OneLink 集成

  1. 进入 AppsFlyer 控制台。

  2. 在左侧边栏中,单击 SDK Integration Tests

  3. 选择要测试的应用程序,点击 Run test

    图片:集成测试 1

  4. Run non-organic install test 页面,选择测试设备并选择 Other

  5. 扫描二维码并安装应用。

    图片:集成测试 2

注意

若要再次测试激活,请删除该应用程序并重新启动测试。

参考文档

AppsFlyer 深度链接和 OneLink:Deep linking

iOSAndroid
初始设置初始设置
统一 deep linking统一 deep linking
旧版本 API旧版本 API
使用 iOS 私人中继的延迟 deep link使用 Android 私人中继的延迟 deep link
用户邀请属性用户邀请属性

客户端接口

Deep link 回调处理

Deep link observer 处理 deep link 数据相关的回调函数。更多信息,请参见:

API函数定义
SetDeepLinkObserver设置 deep link 回调,通知游戏引擎层是否有数据到达,收到回调后,需要手动调 Fetch 函数获取数据。
RemoveDeepLinkObserver移除 deep link 的回调

Fetch

Fetch 接口获取 SDK 缓存的 deep link 数据。更多信息,请参见:

API函数定义
Fetch获取 SDK 缓存的 deep link 数据。