添加深度链接支持
深度链接可引导用户访问应用程序中的特定内容。当用户在已安装应用的设备上点击深层链接时,应用会直接打开到目标内容,如产品页面或特定体验。
奇异跟踪链接支持标准深度链接(适用于已安装的应用)和延迟深度链接(适用于新安装的应用)。有关详细信息,请参阅深度链接常见问题解答和奇异链接常见问题解答。
实现路径:本指南涵盖裸 React Native和Expo实现。之所以存在这种区别,是因为 Expo 抽象了原生代码管理以简化 React Native 开发,而裸 React Native 则提供了对原生 iOS 和 Android 文件夹的直接访问。请根据您的项目结构选择合适的实现路径。
要求
先决条件
完成Singular Links 先决条件,为您的应用程序启用深度链接。
注释:
- 本文假设您的组织正在使用 SingularLinks- Singular 的跟踪链接技术。老客户使用的可能是传统的跟踪链接。
- 您应用程序的深度链接目的地需要在 Singular 的应用程序页面上进行配置(请参阅配置您的应用程序进行归因跟踪)。
实施 Singular 链接处理程序
SingularLink 处理程序提供一种回调机制,用于在应用程序打开时从 Singular 跟踪链接检索深度链接、延迟深度链接、直通参数和 URL 参数。
可用参数:
- 深度链接 (_dl):点击链接的用户在应用程序中的目标 URL
- 延迟深度链接 (_ddl):点击链接后安装应用程序的用户的目标 URL
- 直通 (_p):通过跟踪链接传递的自定义数据,以获取更多上下文信息
- urlParameters:来自打开应用程序的奇异链接的查询参数对象。不适用于延迟深度链接方案。
裸 React 原生实现
为直接访问原生 iOS 和 Android 代码的裸 React Native 项目配置深度链接。
iOS 平台配置
更新 iOS AppDelegate
通过在AppDelegate 文件中向 Singular SDK 传递launchOptions 和userActivity 对象,使 Singular SDK 能够处理启动相关数据并处理深度链接。
import Singular
// Deferred Deep link and Short Link support
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
if let singularBridge = NSClassFromString("SingularBridge") {
let selector = NSSelectorFromString("startSessionWithLaunchOptions:")
if singularBridge.responds(to: selector) {
singularBridge.perform(selector, with: launchOptions, afterDelay: 1)
}
}
factory.startReactNative(
withModuleName: "AppName", // Update with your App Name
in: window,
launchOptions: launchOptions
)
return true
}
// Universal Links (NSUserActivity)
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let url = userActivity.webpageURL {
print("Singular: Universal link received:", url.absoluteString)
if let singularBridge = NSClassFromString("SingularBridge") {
let selector = NSSelectorFromString("startSessionWithUserActivity:")
if singularBridge.responds(to: selector) {
singularBridge.perform(selector, with: userActivity, afterDelay: 1)
}
}
}
return RCTLinkingManager.application(
application,
continue: userActivity,
restorationHandler: restorationHandler
)
}
// Top of the AppDelegate.mm
#import <React/RCTLinkingManager.h>
#import <Singular-React-Native/SingularBridge.h>
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Add inside didFinishLaunchingWithOptions
[SingularBridge startSessionWithLaunchOptions:launchOptions];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
[SingularBridge startSessionWithUserActivity:userActivity];
return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
}
这些方法可确保 Singular SDK 接收到有关应用程序启动方式和原因的重要信息,Singular 会将这些信息用于归因跟踪和深层链接导航。
安卓平台配置
更新安卓主活动
修改MainActivity 文件,将Intent 对象传递给 Singular SDK,使 Singular SDK 能够处理启动相关数据并处理深度链接。
// Add as part of the imports at the top of the class
import android.content.Intent
import net.singular.react_native.SingularBridgeModule
// Add to the MainActivity class
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.data != null) {
setIntent(intent)
SingularBridgeModule.onNewIntent(intent)
}
}
// Add as part of the imports at the top of the class
import android.content.Intent;
import net.singular.react_native.SingularBridgeModule;
// Add to the MainActivity class
@Override
public void onNewIntent(Intent intent) {
if(intent.getData() != null) {
setIntent(intent);
super.onNewIntent(intent);
SingularBridgeModule.onNewIntent(intent);
}
}
Intent 对象包含有关应用程序启动方式和原因的信息,Singular 将其用于归因跟踪和深度链接导航。
SDK 配置
添加 withSingularLink 回调
配置withSingularLink 回调,以便在 SDK 初始化过程中处理传入的深度链接和延迟的深度链接数据。
// TurboModule direct API (React Native 0.76+ New Architecture)
import React, { useEffect } from 'react';
import NativeSingular from 'singular-react-native/jsNativeSingular';
import { NativeEventEmitter } from 'react-native';
function App() {
useEffect(() => {
// Create config
const config: SingularConfig = {
apikey: 'YOUR_SDK_KEY',
secret: 'YOUR_SDK_SECRET'
// Note: Deep link handlers are NOT set in config for TurboModule
};
NativeSingular.init(config);
// Set up deep link handler using event emitter
const emitter = new NativeEventEmitter(NativeSingular);
const linkListener = emitter.addListener('SingularLinkHandler', (params) => {
console.log('Singular Link resolved:', params);
// Extract deep link parameters
console.log('Singular: Deep link received:', params.deeplink);
console.log('Singular: Passthrough data:', params.passthrough);
console.log('Singular: Is deferred:', params.isDeferred);
console.log('Singular: urlParameters:', params.urlParameters);
// Handle deep link navigation
});
// Clean up event listener on unmount
return () => {
linkListener.remove();
};
}, []);
return (
// Your app components
null
);
}
import React, { useEffect } from 'react';
import { Singular, SingularConfig } from 'singular-react-native';
function App() {
useEffect(() => {
const config = new SingularConfig(
'YOUR_SDK_KEY',
'YOUR_SDK_SECRET'
)
.withSingularLink((params) => {
console.log('Singular Link resolved:', params);
// Extract deep link parameters
console.log('Singular: Deep link received:', params.deeplink);
console.log('Singular: Passthrough data:', params.passthrough);
console.log('Singular: Is deferred:', params.isDeferred);
console.log('Singular: urlParameters:', params.urlParameters);
// Handle deep link navigation
});
// Initialize SDK
Singular.init(config);
}, []);
return (
// Your app components
);
}
注意:只有当应用程序通过奇异链接打开时,才会触发withSingularLink 回调。有关详细信息,请参阅奇异链接常见问题解答。
有关完整的方法文档,请参阅withSingularLink 参考资料。
Expo 实现
使用配置文件和自定义插件为 Expo 项目配置深度链接,以自动处理本地代码修改。
应用程序配置
更新 App.json 以实现深度链接
由于 Expo 从本地代码管理中抽象出来,请在app.json 文件中添加特定平台的深度链接配置。
iOS 配置
- 在 Singular 跟踪链接域中添加associatedDomains功能
- 添加支持自定义 URL方案的方案(回退)
安卓配置
-
更新主机值以匹配你的 Singular 链接域(例如,
example.sng.link) - 在最后一个意图过滤器中配置方案,以支持传统方案链接
{
"expo": {
"name": "MySimpleApp",
"slug": "mysimpleapp",
"scheme": "ios-app-scheme",
"ios": {
"associatedDomains": [
"applinks:example.sng.link"
]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/A"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/A"
},
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/B"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/B"
},
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/E"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/E"
},
{
"scheme": "http",
"host": "example.sng.link",
"pathPrefix": "/F"
},
{
"scheme": "https",
"host": "example.sng.link",
"pathPrefix": "/F"
}
],
"category": [
"BROWSABLE",
"DEFAULT"
]
},
{
"action": "VIEW",
"data": [
{
"scheme": "android-app-scheme"
}
],
"category": [
"BROWSABLE",
"DEFAULT"
]
}
]
}
}
}
自定义世博会插件
创建特定平台插件
这些插件文件可在世博会管理工作流程中自动修改 Singular SDK 集成所需的本地代码,无需在每次expo prebuild 后进行手动编辑。
- iOS 插件:检测 Swift 或 Objective-C AppDelegate,并注入导入语句、设备标识符日志和通用链接处理代码,将属性数据转发至 Singular React Native 桥接器。
-
安卓插件:检测 Java 或 Kotlin MainActivity 文件,并添加导入、意图记录和
onNewIntent方法,以捕获深度链接并将其传递给 Singular 的桥接模块
这两个插件都能确保当用户点击深层链接或通用链接时,原生代码能正确捕获并将数据转发给您的 React Native 挂钩,以便进行归因跟踪。
设置步骤
- 进入项目根目录
- 创建插件文件夹
- 添加以下 JavaScript 插件文件
iOS 插件实现
创建plugins/singular-ios-deeplink-plugin.js ,自动配置 iOS 深度链接。
// plugins/singular-ios-deeplink-plugin.js
const { withAppDelegate } = require('@expo/config-plugins');
const withUniversalSingular = (config) => {
return withAppDelegate(config, (config) => {
const { modResults } = config;
// Detect file type
const isSwift = modResults.contents.includes('import Expo') ||
modResults.contents.includes('class AppDelegate');
const isObjectiveC = modResults.contents.includes('#import') ||
modResults.contents.includes('@implementation');
console.log(`Detected AppDelegate type: ${isSwift ? 'Swift' : isObjectiveC ? 'Objective-C' : 'Unknown'}`);
if (isSwift) {
modResults.contents = applySwiftModifications(modResults.contents);
} else if (isObjectiveC) {
modResults.contents = applyObjectiveCModifications(modResults.contents);
} else {
console.warn('Unable to detect AppDelegate type - skipping Singular modifications');
}
return config;
});
};
function applySwiftModifications(contents) {
// Add Singular import
if (!contents.includes('import Singular')) {
contents = contents.replace(
/import ReactAppDependencyProvider/,
`import ReactAppDependencyProvider
import Singular`
);
}
// Add IDFV logging
const swiftLaunchPattern = /didFinishLaunchingWithOptions.*?\) -> Bool \{/s;
if (!contents.includes('UIDevice.current.identifierForVendor')) {
contents = contents.replace(
swiftLaunchPattern,
`didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
print("IDFV", UIDevice.current.identifierForVendor!.uuidString)`
);
}
// Add universal link handling
if (!contents.includes('Universal link received for Singular')) {
contents = contents.replace(
/continue userActivity: NSUserActivity,\s*restorationHandler: @escaping \(\[UIUserActivityRestoring\]\?\) -> Void\s*\) -> Bool \{/,
`continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
if let url = userActivity.webpageURL {
print("Universal link received for Singular:", url.absoluteString)
if let singularBridge = NSClassFromString("SingularBridge") {
let selector = NSSelectorFromString("startSessionWithUserActivity:")
if singularBridge.responds(to: selector) {
singularBridge.perform(selector, with: userActivity, afterDelay: 1)
}
}
}`
);
}
return contents;
}
function applyObjectiveCModifications(contents) {
// Add Singular import
if (!contents.includes('#import <Singular-React-Native/SingularBridge.h>')) {
contents = contents.replace(
/#import "AppDelegate.h"/,
`#import "AppDelegate.h"
#import <Singular-React-Native/SingularBridge.h>`
);
}
// Add IDFV logging to didFinishLaunchingWithOptions
const objcLaunchPattern = /- \(BOOL\)application:\(UIApplication \*\)application didFinishLaunchingWithOptions:\(NSDictionary \*\)launchOptions\s*{/;
if (!contents.includes('NSLog(@"IDFV %@"')) {
contents = contents.replace(
objcLaunchPattern,
`- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(@"IDFV %@", [UIDevice currentDevice].identifierForVendor.UUIDString);
[SingularBridge startSessionWithLaunchOptions:launchOptions];`
);
}
// Add continueUserActivity method if not present
if (!contents.includes('continueUserActivity')) {
const methodToAdd = `
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
NSLog(@"Universal link received for Singular: %@", userActivity.webpageURL.absoluteString);
[SingularBridge startSessionWithUserActivity:userActivity];
return YES;
}`;
contents = contents.replace(
/@end$/,
`${methodToAdd}
@end`
);
}
return contents;
}
module.exports = withUniversalSingular;
安卓插件实现
创建plugins/singular-android-deeplink-plugin.js 以自动配置 Android 深度链接。
// plugins/singular-android-deeplink-plugin.js
const { withMainActivity } = require('@expo/config-plugins');
const withSingularAndroid = (config) => {
// Add MainActivity modifications
config = withMainActivity(config, (config) => {
const { modResults } = config;
// Detect Java vs Kotlin
const isKotlin = modResults.contents.includes('class MainActivity') &&
modResults.contents.includes('override fun');
const isJava = modResults.contents.includes('public class MainActivity') &&
modResults.contents.includes('@Override');
if (isKotlin) {
modResults.contents = applyKotlinModifications(modResults.contents);
} else if (isJava) {
modResults.contents = applyJavaModifications(modResults.contents);
}
return config;
});
return config;
};
function applyKotlinModifications(contents) {
// Add imports
if (!contents.includes('android.content.Intent')) {
contents = contents.replace(
/package com\..*?\n/,
`$&
import android.content.Intent
import net.singular.react_native.SingularBridgeModule`
);
}
// Add onNewIntent method
if (!contents.includes('onNewIntent')) {
const methodToAdd = `
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.data != null) {
setIntent(intent)
SingularBridgeModule.onNewIntent(intent)
}
}`;
contents = contents.replace(
/}\s*$/,
`${methodToAdd}
}`
);
}
return contents;
}
function applyJavaModifications(contents) {
// Add imports
if (!contents.includes('android.content.Intent')) {
contents = contents.replace(
/package com\..*?;/,
`$&
import android.content.Intent;
import net.singular.react_native.SingularBridgeModule;`
);
}
// Add onNewIntent method
if (!contents.includes('onNewIntent')) {
const methodToAdd = `
@Override
public void onNewIntent(Intent intent) {
if(intent.getData() != null) {
setIntent(intent);
super.onNewIntent(intent);
SingularBridgeModule.onNewIntent(intent);
}
}`;
contents = contents.replace(
/}\s*$/,
`${methodToAdd}
}`
);
}
return contents;
}
module.exports = withSingularAndroid;
注册插件
更新app.json 文件,以包含新的自定义插件和 Singular React Native 插件。
{
"expo": {
"plugins": [
"singular-react-native",
"./plugins/singular-ios-deeplink-plugin.js",
"./plugins/singular-android-deeplink-plugin.js"
]
}
}
世博会 SDK 配置
使用与裸 React Native 相同的方法配置 Expo 应用程序中的withSingularLink 回调。有关完整的实现细节,请参阅上面的SDK 配置部分。
处理程序行为
了解链接解析
withSingularLink 处理程序会根据应用程序是刚安装还是已安装而有不同的行为。
全新安装(延迟深度链接)
刚安装时,应用程序启动时不存在打开 URL。Singular 会完成归因,以确定跟踪链接是否包含深度链接或延迟深度链接值。
延迟深度链接流程:
- 用户点击配置了深度链接值的 Singular 跟踪链接
- 用户安装并首次打开应用程序
- Singular SDK 向 Singular 服务器发送第一个会话
- 归因完成并从跟踪链接中识别深度链接
-
深度链接值返回到
withSingularLink处理程序的deeplink参数中的isDeferred = true
测试延迟深度链接:
- 从测试设备上卸载应用程序(如果当前已安装)
- iOS:重置您的 IDFA。Android:重置您的谷歌广告 ID (GAID)
- 点击设备上的 Singular 跟踪链接(确保配置了深度链接值
- 安装并打开应用程序
归因应成功完成,延迟的深度链接值将传递到withSingularLink 处理程序。
专业提示在使用不同软件包名称(例如com.example.dev而不是com.example.prod )的开发构建中测试深度链接时,请专门针对开发应用的软件包名称配置跟踪链接。点击测试链接后,直接在设备上安装开发构建(通过 Android Studio 或 Xcode),而不是从应用商店下载生产应用。
已安装(即时深度链接)
当应用程序已安装时,点击奇异链接可使用通用链接(iOS)或安卓应用程序链接技术立即打开应用程序。
即时深度链接流程:
- 用户点击奇异跟踪链接
- 操作系统提供包含整个 Singular 跟踪链接的开放 URL
- 在 SDK 初始化过程中,Singular 对 URL 进行解析
-
Singular 提取
deeplink和passthrough值 -
通过
withSingularLink处理程序返回isDeferred = false的值
高级功能
直通参数
使用直通参数从跟踪链接点击中获取其他数据。
如果跟踪链接中包含passthrough (_p) 参数,withSingularLink 处理程序的passthrough参数就会包含相应数据。使用该参数可捕获营销活动元数据、用户细分数据或应用程序中需要的任何自定义信息。
传递值示例:
{
"utm_source": "Facebook",
"utm_campaign": "Web-to-App US RON",
"utm_creative": "Blue CTA for Promo",
"promo_code": "45k435hg"
}
在 (_p) 参数中带有 URL 编码直通值的跟踪链接示例:
https://yourapp.sng.link/A1b2c/abc123?_dl=myapp://product/123&_p=%7B%22utm_source%22%3A%20%22Facebook%22%2C%22utm_campaign%22%3A%20%22Web-to-App%20US%20RON%22%2C%22utm_creative%22%3A%20%22Blue%20CTA%20for%20Promo%22%2C%22promo_code%22%3A%20%2245k435hg%22%7D
withSingularLink 处理程序将收到:
urlParameters = {"utm_source": "Facebook","utm_campaign": "Web-to-App US RON","utm_creative": "Blue CTA for Promo","promo_code": "45k435hg"}
转发所有查询参数
通过在跟踪链接中添加_forward_params=2 参数,从跟踪链接 URL 获取所有查询参数。
当_forward_params=2 添加到跟踪链接时,所有查询参数都将包含在withSingularLink 处理程序的deeplink 参数中,从而使您可以访问包含所有参数的完整 URL。
跟踪链接示例:
https://yourapp.sng.link/A1b2c/abc123?_dl=myapp://product/123&_forward_params=2&utm_source=facebook&promo=SALE2024
withSingularLink 处理程序将收到
deeplink = "myapp://product/123?utm_source=facebook&promo=SALE2024"