跳到主要内容

Deep Link

步骤1:创建指向应用内容的 deep link

在的应用清单中, 为网站 URI 创建一个 intent 过滤器,并配置应用程序使用相关的故意数据,以便用户能够在应用程序中达到正确的内容。

当点击的链接或程序化请求调用网页 URL intent 时,Android 系统会按顺序尝试执行以下每项操作,直到请求成功为止:

  1. 如果用户指定了可以处理该 URL 的首选应用,就打开此应用。
  2. 打开唯一可以处理该 URL 的应用。
  3. 允许用户从对话框中选择应用。

步骤2:配置 Player Network SDK

说明

版本要求:Player Network SDK 版本 1.12.00 及以上

在应用清单中,为应用创建 intent 过滤器,并将应用配置为使用来自相关 intent 的数据,以便将用户引导至应用中的正确内容。

<action>

指定 ACTION_VIEW intent 操作,以便能够从 Google 搜索中访问此 intent 过滤器。

<data>

添加一个或多个 <data> 标记,每个标记都代表一个解析为 Activity 的 URL 格式。<data> 标记必须至少包含 android:scheme 属性。 添加更多属性,以进一步细化 Activity 接受的 URL 类型。例如,您可能拥有多个接受相似 URL 的 Activity,(这些 URL 只是路径名称有所不同)。在这种情况下,请使用 android:path 属性或其 pathPatternpathPrefix 变体区分系统应针对不同 URL 路径打开哪个 Activity。

<category>

包含 BROWSABLE 类别。如果要从网络浏览器中访问 intent 过滤器,就必须提供该类别。否则,在浏览器中点击链接便无法解析为您的应用。 此外,还要包含 DEFAULT 类别。这样您的应用才可以响应隐式 intent。否则,只有在 intent 指定您的应用组件名称时,Activity 才能启动。

以下 XML 代码段展示了如何在清单中为 deep link 指定 intent 过滤器。URL “example://gizmos” 和 “http://www.example.com/gizmos” 都会解析到此 Activity。

<activity
android:name="com.example.android.GizmosActivity"
android:label="@string/title_gizmos" >
<intent-filter android:label="@string/filter_view_http_gizmos">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URLs that begin with "http://www.example.com/gizmos” -->
<data android:scheme="http"
android:host="www.example.com"
android:pathPrefix="/gizmos" />
<!-- note that the leading "/" is required for pathPrefix-->
</intent-filter>
<intent-filter android:label="@string/filter_view_example_gizmos">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URLs that begin with "example://gizmos” -->
<data android:scheme="example"
android:host="gizmos" />
</intent-filter>
</activity>

请注意,<data> 元素是这两个 intent 过滤器的唯一区别。虽然同一过滤器可以包含多个 <data> 元素,但如果您想要声明唯一网址 (例如特定的 schemehost 组合),则创建单独的过滤器很重要,因为同一 intent 过滤器中的多个 <data> 元素实际上会合并在一起以涵盖合并后属性的所有变体。例如,请参见以下示例:

<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
<data android:scheme="app" android:host="open.my.app" />
</intent-filter>

看起来这似乎仅支持 https://www.example.comapp://open.my.app。但是,实际上除了这两种之外,它还支持 app://www.example.comhttps://open.my.app

当向应用清单添加包含 Activity 内容 URL 的 intent 过滤器后,Android 可以在运行时将所有包含匹配 URL 的 Intents 转发到您的应用。

步骤3: 验证 Android 应用链接

将应用配置为要求验证应用链接。然后,在您的网站上发布 Digital Asset Links JSON 文件,以通过 Google Search Console 验证所有权。

Android 应用链接是一种特殊类型的 deep link,可让网站网址直接在 Android 应用中打开相应内容,无需用户选择应用。

如需验证您对应用和网站的所有权,您需要执行以下步骤:

  • 在清单中请求自动验证应用链接。这样即可向 Android 系统说明其应该验证您的应用是否属于 intent 过滤器中使用的网址网域。
  • 通过在以下位置托管 Digital Asset Links JSON 文件,声明您的网站和 intent 过滤器之间的关系:
https://domain.name/.well-known/assetlinks.json

您必须在网站上发布 Digital Asset Links JSON 文件,以指示与网站相关联的 Android 应用并验证应用的网址 intent。JSON 文件使用下列字段标识关联的应用:

  • package_name: 在应用的 build.gradle 文件中声明的应用 ID。
  • sha256_cert_fingerprints: 应用的签名证书的 SHA256 指纹。您可以利用 Java 密钥工具,通过以下命令生成该指纹:
$ keytool -list -v -keystore my-release-key.keystore

此字段支持多个指纹,这些指纹可用于支持不同版本的应用,例如调试版 build 和正式版 build。

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

[{
"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"]
}
}]

步骤4:接收 Android intent 数据

调用正确的方法检索与传入 Intent

在主 Activity 的 onCreateonResume 中调用 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());
}
}

步骤5:测试配置

安装应用并在 Android 文本编辑器打开测试链接。

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

    图片:app_lin

  2. 使用 adb 测试 intent 过滤器 URL 的一般语法为:

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

    例如,以下命令试图查看与指定 URL 相关的目标应用 Activity。

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

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

多商店渠道包

图片: Multi-Channel Configuration

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

链接会拉起渠道 A。

[{
"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"]
}
}]

步骤1:配置 Web 服务器

配置 JSON 文件 apple-app-site-association 并将其添加在 Web 服务器的根目录。Universal Link 不需要部署在官网的域名下,只需部署在确保可以访问的高级目录中。若需部署在官网,请参见下文中的示例或 支持 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 键的值是一个字符串数组,用于指定应用程序支持的网站路径以及不与应用程序关联的网站路径。指定不作为通用链接处理的区域时,将 "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 能力根据 Apple 官方文档 支持 Associated Domains 联系证书管理相关负责人申请开通 AppleID Associated Domains 的能力。

    图片: Associated Domains

    AppleID 开启了 Associated Domains 的能力之后请重新生成签名描述文件。

  2. 配置 Xcode Capabilities。在 Xcode 工程中的 Signing & Capabilities 中开启 Associated Domains ,并将 Domains 设置为 applinks: + official website domain

注意

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

图片: Associated_Domains_Xcode

游戏也可以通过代码和配置自动添加开启 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 版本 1.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

注意
  1. 因为微信内置浏览器的限制,在微信中打开链接时不会跳转应用。
  2. 在首次安装应用或更新应用后,系统会从对应的域名请求 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 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

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"});
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 的游戏主 Activity 节点下添加在 步骤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>

更多信息,请参见 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
}
}
}

  1. 设置被分享链接的用户点击生成的 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
}
}
}

  1. 参考以下代码生成 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);

集成测试

  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 数据。