Adjust Deeplink
Adjust 它的核心功能是 跟蹤和分析、歸因分析、深度鏈接、廣告花費(fèi)追蹤、防欺詐工具、實(shí)時(shí)數(shù)據(jù)等等,它是非常強(qiáng)大也是市面上比較熱門(mén)營(yíng)銷(xiāo)工具,本次主要介紹深度鏈接。Adjust 的 SDK 包里已經(jīng)包含了深度鏈接的功能,所以不需要再額外下載依賴(lài),只需要在 Adjust 的后臺(tái)增加一些設(shè)定和微改原生代碼即可。
React-Native 配置深度鏈接
深度鏈接的配置,需要更改「IOS」和「Android」的原生代碼,以及使用 Adjust 提供的「深度鏈接生成器」生成出深度鏈接。下面會(huì)具體講述更改原生代碼和使用深度鏈接生成器,生出深度鏈接。也可以查看官網(wǎng) React-Native 配置。
IOS 通用鏈接設(shè)定
IOS 的通用鏈接:
- 打開(kāi) adjust 登入后臺(tái)地址
- 找到你需要設(shè)定的應(yīng)用,然后點(diǎn)擊應(yīng)用下的 灰色三角形(^) --> 「所有設(shè)置」
- 完成上面的點(diǎn)擊流程,右邊會(huì)出現(xiàn)側(cè)邊欄菜單,請(qǐng)點(diǎn)擊「平臺(tái)」--> 「應(yīng)用類(lèi)型請(qǐng)選擇 IOS」--> 「默認(rèn)設(shè)備選擇通用」--> 「點(diǎn)擊通用鏈接(Universal Linking)」
- 應(yīng)用前綴(APP PREFIX),需要前往 Apple Developer 賬戶找到
- 應(yīng)用方案(APP SCHEME), 這個(gè)可以自定義即可,但必須是唯一性的
- 點(diǎn)擊保存之后,就可以得到一個(gè)原始通用鏈接,類(lèi)似 xxxx.adj.st 這樣的
如果遇到不明白的地方,可以查看官網(wǎng)的 IOS 通用鏈接配置 截圖流程,會(huì)比較詳細(xì)
啟用 Associated Domain(比較重要,能否開(kāi)啟 app,就看它了)
- 打開(kāi) Xcode 打開(kāi)項(xiàng)目
- 點(diǎn)擊左上角的文件夾 icon --> 點(diǎn)擊項(xiàng)目名稱(chēng) --> 進(jìn)入到「Signing & Capabilities」--> 點(diǎn)擊下方的 「all」--> 找到「Associated Domain」--> 點(diǎn)擊 「+」然后輸入 applinks:你的通用鏈接,例如 applinks:xxxx.adj.st。(前綴必須是 applinks:)
- 輸入好之后,按下鍵盤(pán)回車(chē)鍵(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];
}
<!-- 這段很重要,如果不加它的話,用戶通過(guò)點(diǎn)擊深度鏈接,把a(bǔ)pp從背景呼叫到前景時(shí),拿不到深度鏈接到參數(shù) -->
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
return [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
注意:保存代碼之后,請(qǐng) cd /ios 執(zhí)行 pod install,然后再重新 build 到真機(jī)上測(cè)試,請(qǐng)不要使用模擬器
具體詳細(xì)說(shuō)明可以查看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" // 請(qǐng)選擇 singleTask 模式開(kāi)啟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>
<!-- 主要是以下這段代碼,如果深度鏈接無(wú)法開(kāi)啟app,請(qǐng)檢查深度鏈接配置的 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 的內(nèi)容,可以根據(jù)自己的需求設(shè)定,但必須是唯一性的。請(qǐng)于上面配置 ios 的通用鏈接中的 「APP SCHEME」保持一致
具體詳細(xì)說(shuō)明可以查看 深度鏈接 的文檔
Adjust 深度鏈接生成器
- 打開(kāi)adjust 登入后臺(tái)地址
- 登入之后點(diǎn)擊左側(cè)菜單選擇「深度鏈接生成器」,如果你的后臺(tái)沒(méi)有出現(xiàn)的話,可能是權(quán)限不夠,需要增加權(quán)限
- PLATFORM 請(qǐng)選擇 【Multiplatform】(多平臺(tái))
- AD ENVIRONMENT 請(qǐng)選擇 【Other】
- FORMAT 請(qǐng)選擇 【ulink】(如果不選 ulink 的話,下面選項(xiàng)中就不會(huì)出現(xiàn) RAW UNIVERSAL LINK)
- APP 請(qǐng)選擇 你的項(xiàng)目
- TRACKER 請(qǐng)選擇 【Dynamic Link】(這些都可以自定義的,建議選擇相關(guān)的跟蹤途徑。另外提醒一下在設(shè)定鏈接的時(shí)候,請(qǐng)注意「用戶目的位置」這一項(xiàng)的子項(xiàng)中深度鏈接配置,"已安裝應(yīng)用的用戶跳轉(zhuǎn)到哪里?",請(qǐng)一定選擇「應(yīng)用內(nèi)界面」,否則用戶打開(kāi) app 之后,會(huì)跳轉(zhuǎn)到商店,這個(gè)很關(guān)鍵。如果遇到打開(kāi) app 之后會(huì)跳轉(zhuǎn)到商店時(shí),也請(qǐng)檢查這一項(xiàng)設(shè)定)
- RAW UNIVERSAL LINK 請(qǐng)?zhí)顚?xiě) 通用鏈接例如 xxx.adj.st(還可以設(shè)定自定義品牌鏈接,這個(gè)會(huì)在最后詳細(xì)說(shuō)明)
- ANDROID APP SCHEME 請(qǐng)?zhí)顚?xiě) 你項(xiàng)目中的 scheme,例如 myapp://
- IOS UNIVERSAL LINK PATH 和 ANDROID DEEPLINK PATH 請(qǐng)?zhí)顚?xiě)一樣的路徑參數(shù) 例如 navigate?pageName=Dame
- FALLBACK URL(optional) 這一項(xiàng)非必填,目前使用的地方是,在非移動(dòng)端的設(shè)備點(diǎn)擊深度鏈接時(shí),跳轉(zhuǎn)到指定的網(wǎng)頁(yè),例如:https://www.testdemo.com/
- REDIRECT MACOS URL(optional) 對(duì) ios 和 macos 有效,這一項(xiàng)非必填,目前使用的地方是,在非移動(dòng)端的設(shè)備點(diǎn)擊深度鏈接時(shí),跳轉(zhuǎn)到指定的網(wǎng)頁(yè),例如:https://www.testdemo.com/
- 點(diǎn)擊底部的 Generate Deeplink,等待完成之后,就會(huì)創(chuàng)建出一個(gè)深度鏈接,出現(xiàn)在右側(cè),這個(gè)時(shí)候可以復(fù)制它去嘗試 app 的設(shè)定是否有效了
當(dāng)把 IOS 和 Android 的需要調(diào)整的地方都調(diào)整時(shí),在 build 到真機(jī)后,點(diǎn)擊深度鏈接不出意外的話,就可以順利開(kāi)啟 app 了,但想要獲取參數(shù),還需要進(jìn)一步更改 js 代碼
注意:在填寫(xiě) FALLBACK URL(optional) 和 REDIRECT MACOS URL(optional) 請(qǐng)一定要把跳轉(zhuǎn)網(wǎng)站后增加 “/”,不然 IOS 收不到任何參數(shù),范例格式:https://www.testdemo.com/
JS 代碼調(diào)整
以下代碼功能如下
- 接收深度鏈接的參數(shù)進(jìn)行格式化處理
- 把處理好的參數(shù)和事件存入 state 中
- 處理導(dǎo)頁(yè)的函數(shù)監(jiān)聽(tīng) state 改變,并符合導(dǎo)頁(yè)的條件后進(jìn)行導(dǎo)頁(yè)
代碼僅供參考,可以根據(jù)自己的業(yè)務(wù)需求修改
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轉(zhuǎn)為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: 動(dòng)態(tài)連結(jié)導(dǎo)頁(yè) */
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);
// 當(dāng)navigateItem改變時(shí)觸發(fā)導(dǎo)頁(yè)的hook
useRedirect.redirectByUrlObj({
navigationRef,
urlObj: navigateItem,
onFinish: () => {
shiftTask();
},
});
useEffect(() => {
if (!systemInitialized || !dynamicLinkTasks || !navigationRef || !hasProfile) return; // 若尚未開(kāi)啟完app或無(wú)推播資料就不繼續(xù)
// 當(dāng)推播資料有夾帶導(dǎo)頁(yè)資訊時(shí)進(jìn)行處理
const oauthBindTask = Object.assign({}, dynamicLinkTasks[0]);
if (oauthBindTask.event === DynamicLinkEventEnum.navigate && oauthBindTask.querys.pageName) {
// 動(dòng)態(tài)連結(jié)會(huì)將導(dǎo)頁(yè)目標(biāo)及參數(shù)全放在querys,因此轉(zhuǎn)換過(guò)程需將
const cloneQuery = Object.assign({}, oauthBindTask.querys);
delete cloneQuery.pageName;
const urlObj = {
page: oauthBindTask.querys.pageName,
params: cloneQuery,
};
setNavigateItem(urlObj);
}
}, [navigationRef, systemInitialized, dynamicLinkTasks, hasProfile]);
};
/** 啟動(dòng)監(jiān)聽(tīng)深度鏈接 */
const startListenerDeepLinking = () => {
const handleLink = (link: any, isInit: boolean) => {
try {
if (!link.url) return;
const url = link.url;
// DynamicLinks挾帶的params參數(shù)物件
const querys = urlQueryParser(url);
// 定義應(yīng)處理的event範(fàn)圍
const eventRange = [DynamicLinkEventEnum.navigate];
if (isInit) {
eventRange.push(DynamicLinkEventEnum.init_app);
} else {
eventRange.push(DynamicLinkEventEnum.foreground);
eventRange.push(DynamicLinkEventEnum.oauth_bind);
}
// 屬於DynamicLinkEventEnum定義範(fàn)圍者才可放入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();
// 添加深度鏈接監(jiān)聽(tīng)器
const subscription = Linking.addEventListener("url", (event: any) => {
handleLink(event, false);
});
// 清除監(jiān)聽(tīng)器,避免內(nèi)存泄漏
return () => {
subscription.remove();
};
}, []);
};
export default {
startListenerDeepLinking,
navigateByDynamicLink,
};
自定義品牌域名
Adjust 提供的域名都是不夠人性化的,很多時(shí)候想要使用自己的域名來(lái)做深度鏈接,Adjust 也考慮到了這一點(diǎn),所以它提供了自定義域名功能,操作如下;
- 打開(kāi)adjust 登入后臺(tái)地址
- 選擇你需要配置的應(yīng)用 app,并點(diǎn)擊 app 應(yīng)用模塊最下面的(^)
- 點(diǎn)擊所有設(shè)置 --> 自定義鏈接 例如域名為 testdemo
- 輸入唯一的子域以注冊(cè)點(diǎn)擊 URL 和深層鏈接中使用的子域
- 選擇保存
- 選擇 ?(勾號(hào)圖標(biāo))以確認(rèn)您已輸入正確的子域
- 把得到的自定義域名 例如:testdemo.go.link,去更改 Associated Domains
- 打開(kāi) Xcode 打開(kāi)項(xiàng)目
- 點(diǎn)擊左上角的文件夾 icon --> 點(diǎn)擊項(xiàng)目名稱(chēng) --> 進(jìn)入到「Signing & Capabilities」--> 點(diǎn)擊下方的 「all」--> 找到「Associated Domain」--> 雙擊之前配置的通用鏈接,然后替換成自定義域名,例如:testdemo.go.link
- 輸入好之后,按下鍵盤(pán)回車(chē)鍵(Enter)即可
- 回到 adjust 的 「深度鏈接生成器」,按照「Adjust 深度鏈接生成器」的設(shè)定,重新生成一遍,自定義域名會(huì)自動(dòng)生成到 RAW UNIVERSAL LINK。當(dāng)填寫(xiě)完之后,點(diǎn)擊最下面的 Generate Deeplink 就可以使用自定義域名的深度鏈接,進(jìn)行開(kāi)啟 app 了;
具體詳細(xì)說(shuō)明可以查看 深度鏈接的文檔
解決 InAppBrowser 打開(kāi)深度鏈接會(huì)直接開(kāi)啟商店
使用了 react-native-inappbrowser-reborn 在 app 內(nèi)部開(kāi)啟瀏覽器進(jìn)行第三方綁定認(rèn)證,等待完成之后的 redirect,開(kāi)啟深度鏈接時(shí),會(huì)自動(dòng)打開(kāi)商店,而不是開(kāi)啟重新返回 app;
這個(gè)時(shí)候需要做 2 個(gè)步驟:
- react-native-inappbrowser-reborn 改用 openAuth,它分三個(gè)參數(shù),第一個(gè)參數(shù)是原本的 url,第二個(gè)參數(shù)是 scheme 例如 my-app://,第三個(gè)參數(shù)就是關(guān)于瀏覽器的對(duì)象配置
- 進(jìn)入 adjust 后臺(tái),打開(kāi)「深度鏈接生成器」, 然后重新按照之前的配置重新配置一條深度鏈接,但在選擇 FORMAT 但時(shí)候,請(qǐng)選擇 jsr,簡(jiǎn)單描述一下 jsr 的類(lèi)型,它通用鏈接會(huì)在這種情況下中斷并將所有用戶發(fā)送到商店,即使他們安裝了該應(yīng)用程序也是如此。關(guān)于jsr可以看這里
- 如果原本使用了自定義域名 xxx.go.link,這個(gè)時(shí)候會(huì)消失,打開(kāi)鏈接會(huì)是 404,這個(gè)時(shí)候把它換成原本的「通用鏈接」xxx.adj.st 就可以了,當(dāng)然也可以自己進(jìn)行組合,格式如下
`https://app.adjust.com/jsr?url=${encodeURIComponent(https://xxx.adj.st?adj_t=xxxx&adjust_deeplink_js=1&${'其他的參數(shù)'}`)}`
- 當(dāng)再次使用 InAppBrowser.openAuth 的時(shí)候,就可以在.then 的回調(diào)函數(shù)中,拿到{type: 'success', url: 'my-app://?xxxx'}的內(nèi)容,然后就可以根據(jù)自己的業(yè)務(wù)需求進(jìn)行編碼了
注意:adjust_deeplink_js=1 是必須要加的參數(shù),不然還是會(huì)一樣開(kāi)啟商店 詳情可以查看 我的應(yīng)用已經(jīng)安裝,但為何還是會(huì)被轉(zhuǎn)到應(yīng)用商店?