Deferred Deep Linking(DDL)
Deferred Deep Linking 介绍
Deferred Deep Linking 介绍,延迟深度链接 (解决未安装 APP)
相比 DeepLink,它增加了判断 APP 是否被安装,用户匹配的 2 个功能;
- 当用户点击链接的时候判断 APP 是否安装,如果用户没有安装时,引导用户跳转到应用商店下载应用。
- 用户匹配功能,当用户点击链接时和用户启动 APP 时,分别将这两次用户 Device Fingerprint(设备指纹信息)传到服务器进行模糊匹配,使用户下载且启动 APP 时,直接打开相应的指定页面。
通过上面的 2 个技术方案,不仅:
- 可以让被分享者更快更便捷的回到 APP,且回到指定的活动页面,而且:
- 可以引导未安装 APP 的用户下载 APP、
- 分享者和被分享者的关系链会通过设备指纹信息记录下来,在业务场景中给出相应的奖励。
Deferred Deeplink 可以先判断用户是否已经安装了 App 应用,如果没有则先引导至 App 应用商店中下载 App,在用户安装 App 后跳转到指定 App 页面 Deeplink 中。
Deferred Deeplink 在未安装 App 应用人群定向推广中效果更佳突出。另外国外的 App 运营在社交推广中广泛使用 Deferred Deeplink 技术,比如一个购物 App 中用户分享了一个自己喜欢的产品到社交账户中,如果没有使用 Deferred Deeplink。其好友看到分享,点击下载安装打开 App 应用后,很可能找不到其好友分享的产品,导致较高的用户流失率。
三方 Deep Link/App Link/DDL 技术
Onelink
自研 ddl 技术
Google Play Install Referrer
[[Google Play Install Referrer]]
Facebook AD Deeplink
Facebook Deferred Deep Linking(FB DDL)
facebook 通过 deferred deeplink 实现获取投放时候 campaign,adset,ad 信息。
https://developers.facebook.com/docs/android/deep-linking
什么是 Deferred Deep Linking
Deferred deep linking allows you to send people to a custom view after they install your app through the app store.
DDL 只用于未安装 App 的情况,如果已经安装了,就不需要用 DDL 了
Facebook DDL 接入
1
AppLinkData.fetchDeferredAppLinkData(context, appLinkCallBack)
官方工具测试 Facebook DDL
- 打开这个应用广告帮手页面
- 点击选择应用,选中你的应用,然后确认,再往下滑动网页会看到下面界面:
- 点击测试深度链接按钮,在弹出的窗口中填写相应参数信息,发送测试链接:
- 从手机端点击进入测试效果:
- 为安装后接收深度链接,你的应用需要在启动时调用 Facebook SDK 方式中的
1
2
3
4
5
// 模拟Facebook DDL https://developers.facebook.com/tools/app-ads-helper/?id=145288758558252
AppLinkData.fetchDeferredAppLinkData(this) {
// Process app link data
Log.d(TAG, "onDeferredAppLinkDataFetched ${it.string()}")
}
测试阶段测试 Facebook
测试方法
- 在 facebook app 中分享链接并打开https://fb.me/2Oz4dhMesRwhy6R,然后会提示开启动态广告,开启后,打开 facebook app,跳转到个人中的动态,等待广告数据加载出来
- 预览到广告之后,先不点击链接下载,先卸载本机已有 xxx app
- 然后点击链接下载,跳转到 Google paly 商店
- 再安装调试的 APP,就能获取到 facebook ddl 信息了
- 启动 app 时,抓包看接口
graph.facebook.com/xxx/appid/activities
:
测试的链接
- xxx 新客 onelink
- xxx 老客 deeplink
- yyy deeplink
- yyy onelink
Facebook applinks
Facebook App links 原理
实现了 App Links protocol 的库
- Bolts for Android - Open-source reference implementation for Android apps
Google AD DDL (Deferred Deep Link)
Google 广告的延迟 deep link
Google DDL Enable deferred deep linking in your measurement SDK
Google DDL 的接入和测试官方文档
1
2
3
4
5
dependencies {
// ...
implementation 'com.google.firebase:firebase-analytics:21.0.0'
// ...
}
- 开启 google ddl 支持
1
2
<!-- Value to be added to enable deferred deep links -->
<meta-data android:name="google_analytics_deferred_deep_link_enabled" android:value="true"/>
- 配置好了,GA4F 会在 App 启动的时候拉取配置
在 APP 首次启动的时候会请求接口:https://www.googleadservices.com/pagead/conversion/app/deeplink?id_type=adid&sdk_version=v79009.232216&rdid=d6f32803-fecd-4226-b7a7-18fffbcfd52a&bundleid=com.zzz&retry=0&ddl_test=1
Google DDL 获取配置代码
官方监听 SP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* The main launch activity of the app.
*/
public class MainActivity extends AppCompatActivity {
private SharedPreferences preferences;
private SharedPreferences.OnSharedPreferenceChangeListener deepLinkListener;
@Override
protected void onStart() {
super.onStart();
preferences.registerOnSharedPreferenceChangeListener(deepLinkListener);
}
@Override
protected void onStop() {
super.onStop();
preferences.unregisterOnSharedPreferenceChangeListener(deepLinkListener);
deepLinkListener = null;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
preferences =
getSharedPreferences("google.analytics.deferred.deeplink.prefs", MODE_PRIVATE);
deepLinkListener = (sharedPreferences, key) -> {
Log.d("DEEPLINK_LISTENER", "Deep link changed");
if ("deeplink".equals(key)) {
String deeplink = sharedPreferences.getString(key, null);
Double cTime = Double.longBitsToDouble(sharedPreferences.getLong("timestamp", 0));
Log.d("DEEPLINK_LISTENER", "Deep link retrieved: " + deeplink);
showDeepLinkResult(deeplink);
}
};
}
public void showDeepLinkResult(String result) {
String toastText = result;
if (toastText == null) {
toastText = "The deep link retrieval failed";
} else if (toastText.isEmpty()) {
toastText = "Deep link empty";
}
Toast.makeText(MainActivity.this, toastText, Toast.LENGTH_LONG).show();
Log.d("DEEPLINK", toastText);
}
}
注意:获取到的时间是微秒,需要用 Double.longBitsToDouble(…) 来转换
官方的是监听 SP 的变化,我们也可以用 RX 每 100ml 轮询 sp 的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
fun requestGoogleDDLObservable(
timeout: Long = Long.MAX_VALUE
): Observable<DDLInfo> {
val s1 = SystemClock.elapsedRealtime()
val sp = AppContext.application.getSharedPreferences(
"google.analytics.deferred.deeplink.prefs",
Context.MODE_PRIVATE
)
return Observable
.interval(100L, TimeUnit.MILLISECONDS, Schedulers.io())
// .doOnNext { Logger.d(TAG, "AppOneLinker->requestGoogleDDLObservable doOnNext $it.${Thread.currentThread().name}") }
.map {
val deeplink: String = sp.getString("deeplink", "") ?: ""
val timestamp =
java.lang.Double.longBitsToDouble(sp.getLong("timestamp", 0L))
.toSafeLong()
DDLInfo(deeplink, timestamp, cost = SystemClock.elapsedRealtime() - s1)
}
.takeUntil { ddlInfo ->
// Logger.d(TAG, "AppOneLinker->requestGoogleDDLObservable takeUntil ddlInfo=$ddlInfo.${Thread.currentThread().name}")
ddlInfo.isValid()
}
.filter { ddlInfo ->
// Logger.d(TAG, "AppOneLinker->requestGoogleDDLObservable filter ddlInfo=$ddlInfo.(${Thread.currentThread().name})")
ddlInfo.isValid()
}
.timeout(timeout, TimeUnit.MILLISECONDS)
.onErrorResumeNext { throwable: Throwable ->
if (throwable is TimeoutException) {
Observable.just(DDLInfo.timeoutDDLInfo())
} else {
Observable.just(
DDLInfo.errorDDLInfo(
throwable.message ?: ""
)
)
}
}
}
fun Any?.toSafeLong(): Long {
var value = 0L
try {
value = when (this) {
is Int -> this.toLong()
is String -> {
if (isEmpty()) {
0L
} else {
java.lang.Long.valueOf(this)
}
}
is Double -> this.toLong()
is Long -> this
else -> 0L
}
} catch (e: Exception) {
Logger.printException(e)
} finally {
return value
}
}
Google DDL 模拟测试
xxx Google DDL 测试
- 设置你的设备应该接收到的 DDL,24h 有效(修改 rdid 为 google ad id,bundleid 为 applicationId,deeplink 为要带的数据)gaid(google ad id) 在不同 APP 的值是一样的
1
curl "www.googleadservices.com/pagead/conversion/app/deeplink?&rdid=<<your device adid>>&id_type=adid&bundleid=<<your application package>>&deeplink=<<deeplink you want to receive>>&ddl_test=1"
- 验证 deeplink 设置的对不对,你从 Google 获取的 deeplink 对不对
- 为你的应用开启 ddl 的 test mode
adb shell setprop debug.deferred.deeplink com.zzz
- 你的设备开启 test mode: 在 Android 设备上启用 Analytics 调试模式
adb shell setprop debug.firebase.analytics.app com.zzz // 调试模式将保持启用状态,直至您通过执行以下命令明确将其停用 adb shell setprop debug.firebase.analytics.app .none.
yyy Google DDL 模拟测试
- 生成可测试的 ddl
- 测试生成的 ddl
- 为你的应用开启 ddl 的 test mode
adb shell setprop debug.deferred.deeplink com.yyy
Google DDL 测试完整的 shell 脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
## 模拟测试google ddl流程:xxx
function adb:gooddl:xxx() {
rdid=c6f320e6-4aec-4245-8a69-52296591f9b8
applicationId=$MAIN_APP_ID
deeplink=xxxlink://applink/cart
find_apk_regex=$1
APK_BUILD_PATH=$WORKSPACE_MAIN/xxx_android/xxx/build/outputs/apk
adb:gooddl $rdid $applicationId $deeplink $APK_BUILD_PATH $find_apk_regex
}
function adb:gooddl:yyy() {
# rdid为google ad id
rdid=c6f320e6-4aec-4245-8a69-52296591f9b8
applicationId=$SECOND_APP_ID
deeplink=xxxlink://applink/cart
find_apk_regex=$1
APK_BUILD_PATH=$WORKSPACE_MAIN/xxx_android/xxx/build/outputs/apk
adb:gooddl $rdid $applicationId $deeplink $APK_BUILD_PATH $find_apk_regex
}
function adb:gooddl() {
echo 'Google DDL模拟测试, 参数:' $@
rdid=$1
if [[ -z "${rdid}" ]] ; then
echo -e "\033[31mFATAL: 请输入合法的rdid(Google Ad Id): \033[0m"
return 0
fi
applicationId=$2
if [[ -z "${applicationId}" ]] ; then
echo -e "\033[31mFATAL: 请输入合法的applicationId: \033[0m"
return 0
fi
deeplink=$3
if [[ -z "${deeplink}" ]] ; then
echo -e "\033[31mFATAL: 请输入合法的deeplink: \033[0m"
return 0
fi
apk_build_path=$4
if [[ -z "${apk_build_path}" ]] ; then
echo -e "\033[31mFATAL: 请输入合法的apk_build_path: \033[0m"
return 0
else
# 这里的-d参数判断目录是否存在
if [ ! -d "${apk_build_path}" ]; then
echo -e "\033[31mFATAL: $apk_build_path 目录不存在 \033[0m"
return 0
fi
fi
find_regex=$5
# 去掉代理
echo '0. 清除手机Charles等抓包代理'
proxy:off
echo "1. 设置你的设备应该接收到的Google DDL,24h有效"
curl "www.googleadservices.com/pagead/conversion/app/deeplink?&rdid=$rdid&id_type=adid&bundleid=$applicationId&deeplink=$deeplink&ddl_test=1"
echo "2. 验证deeplink设置的对不对,获取你将从Google DDL获取的deeplink"
curl "www.googleadservices.com/pagead/conversion/app/deeplink?&rdid=$rdid&id_type=adid&bundleid=$applicationId&ddl_test=1"
echo "3-1. 为你的应用开启Google DDL的test mode"
adb shell setprop debug.deferred.deeplink $applicationId
echo "3-2. 在 Android 设备上启用 Analytics 调试模式"
adb shell setprop debug.firebase.analytics.app $applicationId
echo '4. cd ' $apk_build_path
cd $apk_build_path > /dev/null
if [[ -z "${find_regex}" ]] ; then
final_apk_path=$(find $apk_build_path -name "*.apk")
else
final_apk_path=$(find $apk_build_path -name "*${find_regex}*.apk")
fi
echo "4. find apk: [$final_apk_path]"
# 卸载App
echo "5. 卸载$applicationId"
adb uninstall $applicationId > /dev/null
echo "6. 安装测试包:$final_apk_path"
# 安装要测试的App
adb install -r -t "$final_apk_path"
echo "7. 开启手机Charles抓包代理"
# 开启代理
proxy:on
echo "8. 重新打开 $applicationId,开始测试Google DDL吧"
echo " |-- 即将启动xxx"
if [ "$applicationId" = "$MAIN_APP_ID" ]; then
adb shell am start -n $MAIN_APP_SPLASH
elif [ "$applicationId" = "$SECOND_APP_ID" ]; then
adb shell am start -n $SECOND_APP_SPLASH
fi
echo "9. 在APP首次启动的时候会请求接口:https://www.googleadservices.com/pagead/conversion/app/deeplink?id_type=adid&sdk_version=v79009.232216&rdid=$rdid&bundleid=$applicationId&retry=0&ddl_test=1"
sleep 5
curl "https://www.googleadservices.com/pagead/conversion/app/deeplink?id_type=adid&sdk_version=v79009.232216&rdid=$rdid&bundleid=$applicationId&retry=0&ddl_test=1"
}
Tiktok Deferred Deeplinks
其他
Appsflyer-onelink
Branch
Google Play Installer Referer
见上面的 Google Play Installer Referer
openinstall
- app 跳转 deeplink
- app 免邀请码安装(Deferred deeplink)