Adjust Deeplink + react-native-i
Adjust Deeplink
Adjust 它的核心功能是 跟踪和分析、归因分析、深度链接、广告花费追踪、防欺诈工具、实时数据等等,它是非常强大也是市面上比较热门营销工具,本次主要介绍深度链接。Adjust 的 SDK 包里已经包含了深度链接的功能,所以不需要再额外下载依赖,只需要在 Adjust 的后台增加一些设定和微改原生代码即可。
React-Native 配置深度链接
深度链接的配置,需要更改「IOS」和「Android」的原生代码,以及使用 Adjust 提供的「深度链接生成器」生成出深度链接。下面会具体讲述更改原生代码和使用深度链接生成器,生出深度链接。也可以查看官网 React-Native 配置。
IOS 通用链接设定
IOS 的通用链接:
- 打开 adjust 登入后台地址
- 找到你需要设定的应用,然后点击应用下的 灰色三角形(^) --> 「所有设置」
- 完成上面的点击流程,右边会出现侧边栏菜单,请点击「平台」--> 「应用类型请选择 IOS」--> 「默认设备选择通用」--> 「点击通用链接(Universal Linking)」
- 应用前缀(APP PREFIX),需要前往 Apple Developer 账户找到
- 应用方案(APP SCHEME), 这个可以自定义即可,但必须是唯一性的
- 点击保存之后,就可以得到一个原始通用链接,类似 xxxx.adj.st 这样的
如果遇到不明白的地方,可以查看官网的 IOS 通用链接配置 截图流程,会比较详细
启用 Associated Domain(比较重要,能否开启 app,就看它了)
- 打开 Xcode 打开项目
- 点击左上角的文件夹 icon --> 点击项目名称 --> 进入到「Signing & Capabilities」--> 点击下方的 「all」--> 找到「Associated Domain」--> 点击 「+」然后输入 applinks:你的通用链接,例如 applinks:xxxx.adj.st。(前缀必须是 applinks:)
- 输入好之后,按下键盘回车键(Enter)即可
增加 IOS 原始代码
路径是 ios/you_project/AppDelegate.m
增加代码如下
#import <React/RCTLinkingManager.h>
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
return [RCTLinkingManager application:application openURL:url options:options];
}
<!-- 这段很重要,如果不加它的话,用户通过点击深度链接,把app从背景呼叫到前景时,拿不到深度链接到参数 -->
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
注意:保存代码之后,请 cd /ios 执行 pod install,然后再重新 build 到真机上测试,请不要使用模拟器
具体详细说明可以查看React-native 的 Linking 文档
Android 深度链接
需要更改的原生代码如下
路径:android/app/src/main/AndroidManifest.xml
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask" // 请选择 singleTask 模式开启app
android:screenOrientation="portrait"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- 主要是以下这段代码,如果深度链接无法开启app,请检查深度链接配置的 scheme 是否和下面代码中的 scheme 一致-->
<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="myapp"/>
</intent-filter>
</activity>
注意:scheme 的内容,可以根据自己的需求设定,但必须是唯一性的。请于上面配置 ios 的通用链接中的 「APP SCHEME」保持一致
具体详细说明可以查看 深度链接 的文档
Adjust 深度链接生成器
- 打开adjust 登入后台地址
- 登入之后点击左侧菜单选择「深度链接生成器」,如果你的后台没有出现的话,可能是权限不够,需要增加权限
- PLATFORM 请选择 【Multiplatform】(多平台)
- AD ENVIRONMENT 请选择 【Other】
- FORMAT 请选择 【ulink】(如果不选 ulink 的话,下面选项中就不会出现 RAW UNIVERSAL LINK)
- APP 请选择 你的项目
- TRACKER 请选择 【Dynamic Link】(这些都可以自定义的,建议选择相关的跟踪途径。另外提醒一下在设定链接的时候,请注意「用户目的位置」这一项的子项中深度链接配置,"已安装应用的用户跳转到哪里?",请一定选择「应用内界面」,否则用户打开 app 之后,会跳转到商店,这个很关键。如果遇到打开 app 之后会跳转到商店时,也请检查这一项设定)
- RAW UNIVERSAL LINK 请填写 通用链接例如 xxx.adj.st(还可以设定自定义品牌链接,这个会在最后详细说明)
- ANDROID APP SCHEME 请填写 你项目中的 scheme,例如 myapp://
- IOS UNIVERSAL LINK PATH 和 ANDROID DEEPLINK PATH 请填写一样的路径参数 例如 navigate?pageName=Dame
- FALLBACK URL(optional) 这一项非必填,目前使用的地方是,在非移动端的设备点击深度链接时,跳转到指定的网页,例如:https://www.testdemo.com/
- REDIRECT MACOS URL(optional) 对 ios 和 macos 有效,这一项非必填,目前使用的地方是,在非移动端的设备点击深度链接时,跳转到指定的网页,例如:https://www.testdemo.com/
- 点击底部的 Generate Deeplink,等待完成之后,就会创建出一个深度链接,出现在右侧,这个时候可以复制它去尝试 app 的设定是否有效了
当把 IOS 和 Android 的需要调整的地方都调整时,在 build 到真机后,点击深度链接不出意外的话,就可以顺利开启 app 了,但想要获取参数,还需要进一步更改 js 代码
注意:在填写 FALLBACK URL(optional) 和 REDIRECT MACOS URL(optional) 请一定要把跳转网站后增加 “/”,不然 IOS 收不到任何参数,范例格式:https://www.testdemo.com/
JS 代码调整
以下代码功能如下
- 接收深度链接的参数进行格式化处理
- 把处理好的参数和事件存入 state 中
- 处理导页的函数监听 state 改变,并符合导页的条件后进行导页
代码仅供参考,可以根据自己的业务需求修改
import { useState, useEffect } from "react";
import { Linking } from "react-native";
import { useSelector } from "react-redux";
import { NavigationContainerRefWithCurrent } from "@react-navigation/native";
import { RootState, DynamicLinkEventEnum, RedirectUrlType } from "@tcg/xoso_web_core";
import useRedirect from "~/hooks/useRedirect";
import useDynamicLinkStore, { pushTask, shiftTask } from "~/store/useDynamicLinkStore";
/** method: 將url params轉為key value物件 */
const urlQueryParser = (url: string): { [key: string]: string } => {
const regex = /[?&]([^=#]+)=([^&#]*)/g;
const params: any = {};
let match;
while ((match = regex.exec(url))) {
params[match[1]] = match[2];
}
return params;
};
/** method: 動態連結導頁 */
const navigateByDynamicLink = ({ navigationRef }: { navigationRef: NavigationContainerRefWithCurrent<ReactNavigation.RootParamList> }) => {
const systemInitialized = useSelector((state: RootState) => state.system.initialized);
const hasProfile = useSelector((state: RootState) => !!state.auth.userProfile);
const dynamicLinkTasks = useDynamicLinkStore((state) => state.dynamicLinkTasks);
const [navigateItem, setNavigateItem] = useState<RedirectUrlType | undefined>(undefined);
// 當navigateItem改變時觸發導頁的hook
useRedirect.redirectByUrlObj({
navigationRef,
urlObj: navigateItem,
onFinish: () => {
shiftTask();
},
});
useEffect(() => {
if (!systemInitialized || !dynamicLinkTasks || !navigationRef || !hasProfile) return; // 若尚未開啟完app或無推播資料就不繼續
// 當推播資料有夾帶導頁資訊時進行處理
const oauthBindTask = Object.assign({}, dynamicLinkTasks[0]);
if (oauthBindTask.event === DynamicLinkEventEnum.navigate && oauthBindTask.querys.pageName) {
// 動態連結會將導頁目標及參數全放在querys,因此轉換過程需將
const cloneQuery = Object.assign({}, oauthBindTask.querys);
delete cloneQuery.pageName;
const urlObj = {
page: oauthBindTask.querys.pageName,
params: cloneQuery,
};
setNavigateItem(urlObj);
}
}, [navigationRef, systemInitialized, dynamicLinkTasks, hasProfile]);
};
/** 启动监听深度链接 */
const startListenerDeepLinking = () => {
const handleLink = (link: any, isInit: boolean) => {
try {
if (!link.url) return;
const url = link.url;
// DynamicLinks挾帶的params參數物件
const querys = urlQueryParser(url);
// 定義應處理的event範圍
const eventRange = [DynamicLinkEventEnum.navigate];
if (isInit) {
eventRange.push(DynamicLinkEventEnum.init_app);
} else {
eventRange.push(DynamicLinkEventEnum.foreground);
eventRange.push(DynamicLinkEventEnum.oauth_bind);
}
// 屬於DynamicLinkEventEnum定義範圍者才可放入task
eventRange.forEach((event) => {
if (url.includes(`${event}`)) {
pushTask({ event, querys });
}
});
} catch (error) {
console.log("handleLink error", error);
}
};
// 获取初始化深度链接
const handleInitDynamicLinks = async () => {
const url = await Linking.getInitialURL();
handleLink({ url }, true);
};
useEffect(() => {
handleInitDynamicLinks();
// 添加深度链接监听器
const subscription = Linking.addEventListener("url", (event: any) => {
handleLink(event, false);
});
// 清除监听器,避免内存泄漏
return () => {
subscription.remove();
};
}, []);
};
export default {
startListenerDeepLinking,
navigateByDynamicLink,
};
自定义品牌域名
Adjust 提供的域名都是不够人性化的,很多时候想要使用自己的域名来做深度链接,Adjust 也考虑到了这一点,所以它提供了自定义域名功能,操作如下;
- 打开adjust 登入后台地址
- 选择你需要配置的应用 app,并点击 app 应用模块最下面的(^)
- 点击所有设置 --> 自定义链接 例如域名为 testdemo
- 输入唯一的子域以注册点击 URL 和深层链接中使用的子域
- 选择保存
- 选择 ✓(勾号图标)以确认您已输入正确的子域
- 把得到的自定义域名 例如:testdemo.go.link,去更改 Associated Domains
- 打开 Xcode 打开项目
- 点击左上角的文件夹 icon --> 点击项目名称 --> 进入到「Signing & Capabilities」--> 点击下方的 「all」--> 找到「Associated Domain」--> 双击之前配置的通用链接,然后替换成自定义域名,例如:testdemo.go.link
- 输入好之后,按下键盘回车键(Enter)即可
- 回到 adjust 的 「深度链接生成器」,按照「Adjust 深度链接生成器」的设定,重新生成一遍,自定义域名会自动生成到 RAW UNIVERSAL LINK。当填写完之后,点击最下面的 Generate Deeplink 就可以使用自定义域名的深度链接,进行开启 app 了;
具体详细说明可以查看 深度链接的文档
解决 InAppBrowser 打开深度链接会直接开启商店
使用了 react-native-inappbrowser-reborn 在 app 内部开启浏览器进行第三方绑定认证,等待完成之后的 redirect,开启深度链接时,会自动打开商店,而不是开启重新返回 app;
这个时候需要做 2 个步骤:
- react-native-inappbrowser-reborn 改用 openAuth,它分三个参数,第一个参数是原本的 url,第二个参数是 scheme 例如 my-app://,第三个参数就是关于浏览器的对象配置
- 进入 adjust 后台,打开「深度链接生成器」, 然后重新按照之前的配置重新配置一条深度链接,但在选择 FORMAT 但时候,请选择 jsr,简单描述一下 jsr 的类型,它通用链接会在这种情况下中断并将所有用户发送到商店,即使他们安装了该应用程序也是如此。关于jsr可以看这里
- 如果原本使用了自定义域名 xxx.go.link,这个时候会消失,打开链接会是 404,这个时候把它换成原本的「通用链接」xxx.adj.st 就可以了,当然也可以自己进行组合,格式如下
`https://app.adjust.com/jsr?url=${encodeURIComponent(https://xxx.adj.st?adj_t=xxxx&adjust_deeplink_js=1&${'其他的参数'}`)}`
- 当再次使用 InAppBrowser.openAuth 的时候,就可以在.then 的回调函数中,拿到{type: 'success', url: 'my-app://?xxxx'}的内容,然后就可以根据自己的业务需求进行编码了
注意:adjust_deeplink_js=1 是必须要加的参数,不然还是会一样开启商店 详情可以查看 我的应用已经安装,但为何还是会被转到应用商店?