others-AppsFlyer第三方统计
others-AppsFlyer第三方统计
前篇
- 官网 - https://www.appsflyer.com/
- Android 集成文档 - https://support.appsflyer.com/hc/en-us/articles/207032126-Android-SDK-integration-for-developers#introduction
- 事件 - https://support.appsflyer.com/hc/en-us/articles/115005544169-Rich-in-app-events-for-Android-and-iOS
- Get Facebook raw data using Google Install Referrer - https://support.appsflyer.com/hc/en-us/articles/4409322107537
SDK 接入
build.gradle 引入库
1
2
3
4
5dependencies {
implementation 'com.appsflyer:af-android-sdk:5.4.0'
implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0'
implementation 'com.android.installreferrer:installreferrer:2.1'
}权限 及 广播 的 清单配置 AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<manifest>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- Optional : -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application>
<!-- appsflyer -->
<receiver
android:name="com.appsflyer.SingleInstallBroadcastReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.android.vending.INSTALL_REFERRER" />
</intent-filter>
</receiver>
</application>
</manifest>混淆配置 proguard-user.txt
1
2
3
4######### appsflyer #########
-keep public class com.android.installreferrer.** { *; }
-keep class com.appsflyer.** { *; }
-dontwarn com.android.installreferrerjava 代码
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
102
103
104
105
106package com.yang.androidaar.other;
import android.content.Context;
import android.util.Log;
import com.appsflyer.AppsFlyerConversionListener;
import com.appsflyer.AppsFlyerLib;
import com.yang.androidaar.Define;
import com.yang.androidaar.JsonTool;
import com.yang.androidaar.MyCode.ECode;
import com.yang.androidaar.Tools;
import com.yang.androidaar.model.JsonBean.CEventInfo;
import java.util.HashMap;
import java.util.Map;
public class AppsflyerHelper {
static AppsflyerHelper instance = null;
public static AppsflyerHelper getIns() {
if (instance == null) {
instance = new AppsflyerHelper();
}
return instance;
}
private static final String TAG = "--- AppsflyerHelper";
private String mAfJson = null;
private boolean mIsCall = false;
public void init(Context context, boolean isDebug, Define.JsonRunnable task) {
try {
// af 回调
innerInit(context, isDebug, (code, afJson) -> {
if (code != ECode.Ok) {
return;
}
mAfJson = afJson;
});
} catch (Exception e) {
e.printStackTrace();
callTask(task);
}
}
private void innerInit(Context context, boolean isDebug, Define.CodeRunnable task) {
String devKey = Tools.GetStringVaule(context, "af_dev_key"); // 获取 af 后台的秘钥
AppsFlyerConversionListener conversionListener = new AppsFlyerConversionListener() {
public void onConversionDataSuccess(Map<String, Object> conversionData) {
// 转化回调
task.run(ECode.Ok, JsonTool.toJson(conversionData));
}
public void onConversionDataFail(String errorMessage) {
task.run(ECode.LoginError, errorMessage);
}
public void onAppOpenAttribution(Map<String, String> attributionData) {
// 深度连接的回调: https://support.appsflyer.com/hc/zh-cn/articles/213766183-%E5%BC%80%E5%8F%91%E8%80%85Unity-Plugin-V4%E5%AF%B9%E6%8E%A5%E6%8C%87%E5%8D%97#%E6%A0%B8%E5%BF%83api
Log.d(TAG, "onAppOpenAttribution: ");
}
public void onAttributionFailure(String errorMessage) {
Log.d(TAG, "onAttributionFailure: ");
}
};
// af 初始化, 在 onCreate 的时候就调用
AppsFlyerLib.getInstance().init(devKey, conversionListener, context);
AppsFlyerLib.getInstance().startTracking(context);
AppsFlyerLib.getInstance().setDebugLog(isDebug);
}
public void logEvent(Context context, String jsonMsg, Define.CodeRunnable task) {
CEventInfo ei = JsonTool.toObject(jsonMsg, CEventInfo.class);
logEvent(context, ei, task);
}
public void logEvent(Context context, CEventInfo ei, Define.CodeRunnable task) {
if (ei == null || ei.name.length() == 0) {
return;
}
AppsFlyerLib.getInstance().trackEvent(context, ei.name, ei.params);
}
public String getInfo() {
Map<String, Object> args = new HashMap<>();
args.put("AfJson", mAfJson);
return JsonTool.toJson(args);
}
private void callTask(Define.JsonRunnable task) {
if (mIsCall || task == null) {
return;
}
mIsCall = true;
task.run(mAfJson);
}
}
非自然量测试
在 sdk 对接测试 中, 点击 非自然量测试
选择 加白 设备 (没有加白的设备要先加白), 然后选择 other
二维码生成后, 扫描二维码跳转到目的连接即可
卸载再安装 apk, 直接启动, 就能获取到 非自然流量 的归因数据
然后 af 面板上就会自动显示 非自然量 测试成功
自定义短链配置
这个的短板是: 如果点击链接先跳转到浏览器的 gp 商店, 在点击打开商店进入商店里面下载, 就会丢失归因数据. 这个 adjust 做的就比较好, 不会丢失归因参数.
Google Play 应用的短链推广
后台配置
onConversionDataSuccess
中可以获取到对应的参数
Facebook Ads 投放
官方说明获取归因数据必须 接收 服务条款 - https://support.appsflyer.com/hc/zh-cn/articles/207032096-Deferred-Deeplinking-Getting-the-Conversion-Data#%E7%AE%80%E4%BB%8B
需要 Facebook 对应的 app id 接收 服务条款, 才能在 af 的 onConversionDataSuccess 回调中, 接收到 campaign 相关数据
链接: https://www.facebook.com/ads/manage/advanced_mobile_measurement/app_based_tos?appid=123123 (123123 是 app id)
参考: 设备层级数据
api 上报事件
官方文档: 适用于移动设备的服务器到服务器事件API
py 测试代码
测试用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24# appsflyer
def test_afEvent(self):
pkgName = "com.aaa.bbb" # 安卓包名
secret = "axYvBxxxxxxxxxxxxx" # dev key
afuid = "1602570356839-6524xxxxxxxxxxxxxx" # af uid
url = "https://api2.appsflyer.com/inappevent/{}".format(pkgName)
headers = {
"authentication": secret,
"Content-Type": "application/json",
}
data = {
"appsflyer_id": afuid, # af uid
"eventName": "test_yx01_af_purchase", # 事件
"eventCurrency": "INR", # 币种
"eventValue": utils.beautyJson({
"af_revenue": "100", # 收益
}, indent=None),
}
code, rspDct = utils.httpPost(url, dct=data, headers=headers)
print("--- code: {}".format(code))
print("--- rsp: {}".format(utils.beautyJson(rspDct)))
第三方数据服务条款
实时回传
在 af 后台配置回传 url 及 需要回传的字段, 在 Integration -> API Access
回传的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19{
"af_c_id": "127021111111",
"appsflyer_id": "1622000030319-803339034811111111",
"campaign": "Daji1_1250_040801",
"event_time": "2021-05-26 03:33:53.998",
"af_channel": "ACI_Search",
"app_id": "com.aaa.bbb",
"event_name": "install",
"advertising_id": "1eac9954-cfa4-4a45-a4c0-aaaaaa"
}
附:
- 比较有用的几个信息, 勾选 *激活* 行了
```json
"{\"af_c_id\":\"13536068592\",\"app_name\":\"Rummy Winner Online - 13 Cards\",\"appsflyer_id\":\"1624014963149-589936205098832861\",\"campaign\":\"Daji_1254_0617\",\"event_time\":\"2021-06-18 11:16:10.676\",\"af_channel\":\"ACI_Display\",\"app_id\":\"com.quencheddwfkcv.politicianslijohqe\",\"event_name\":\"install\",\"advertising_id\":\"79bd45f4-66c1-45e6-a617-a030dfb5e46a\"}"
iOS 接入
生成 api/s2s token
右上角账号下拉 -> 安全中心
Pull Api
Organic Installs
https://dev.appsflyer.com/hc/reference/get_app-id-organic-installs-report-v5
示例:
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
30curl --request GET \
--url 'https://hq1.appsflyer.com/api/raw-data/export/app/com.aaa.bbb/organic_installs_report/v5?from=2024-05-08&to=2024-05-10&additional_fields=blocked_reason_rule,store_reinstall,impressions,contributor3_match_type,custom_dimension,conversion_type,gp_click_time,match_type,mediation_network,oaid,deeplink_url,blocked_reason,blocked_sub_reason,gp_broadcast_referrer,gp_install_begin,campaign_type,custom_data,rejected_reason,device_download_time,keyword_match_type,contributor1_match_type,contributor2_match_type,device_model,monetization_network,segment,is_lat,gp_referrer,blocked_reason_value,device_category,app_type,rejected_reason_value,ad_unit,keyword_id,placement,network_account_id,install_app_store,amazon_aid,att,engagement_type,contributor1_engagement_type,contributor2_engagement_type,contributor3_engagement_type' \
--header 'accept: text/csv' \
--header 'authorization: Bearer aaa'
---
### 踩坑
#### 链路验证失败
- 报错: `Chain validation failed`
- 原因是因为手机系统时间不正确, 比如把系统时间调到了未来 几天/几个月 之后, 就会出现这样的问题, 解决办法就是把系统时间调回正确的时间
- 参考: https://blog.csdn.net/m0_37609239/article/details/123893566
---
#### 置到后台返回来回重复调用
- 原因是 AppsFlyerConversionListener 里的回调会回调回来
- 解决办法, 回调后取消回调即可
```json
AppsFlyerLib.getInstance().unregisterConversionListener();