others-Adjust第三方统计
others-Adjust第三方统计
前篇
接入
build.gradle 引入库
1
2
3
4
5dependencies {
implementation 'com.adjust.sdk:adjust-android:4.24.1'
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
19<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>
<!-- adjust -->
<receiver
android:name="com.adjust.sdk.AdjustReferrerReceiver"
android:permission="android.permission.INSTALL_PACKAGES"
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
5
6
7
8
9
10
11
12
13######### adjust #########
-keep class com.adjust.sdk.** { *; }
-keep class com.google.android.gms.common.ConnectionResult {
int SUCCESS;
}
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient {
com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context);
}
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info {
java.lang.String getId();
boolean isLimitAdTrackingEnabled();
}
-keep public class com.android.installreferrer.** { *; }java 代码
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248package com.ecological.auctioneer.other;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import com.adjust.sdk.Adjust;
import com.adjust.sdk.AdjustConfig;
import com.adjust.sdk.AdjustEvent;
import com.adjust.sdk.LogLevel;
import com.ecological.auctioneer.Define;
import com.ecological.auctioneer.JsonTool;
import com.ecological.auctioneer.LogUtil;
import com.ecological.auctioneer.MyCode.ECode;
import com.ecological.auctioneer.SystemInfoUtil;
import com.ecological.auctioneer.Tools;
import com.ecological.auctioneer.model.JsonBean.CEventInfo;
import com.ecological.auctioneer.model.JsonBean.CTransfer;
import java.util.ArrayList;
import java.util.List;
public class AdjustHelper {
static AdjustHelper instance = null;
public static AdjustHelper getIns() {
if (instance == null) {
instance = new AdjustHelper();
}
return instance;
}
private static final String TAG = "--- AdjustHelper";
private static final String ResKey_Token = "adjust_token";
private static final String ResKey_Secret = "adjust_secret";
private boolean mIsCall = false;
private CTransfer mTransfer = new CTransfer(); // 保存数据
// // ---------------- 测试字段
// private String mAttribution = "attr: ";
// private String mEventTrackingSucceeded = "ets: ";
// private String mEventTrackingFailed = "etf: ";
// private String mSessionTrackingSucceeded = "sts: ";
// private String mSessionTrackingFailed = "stf: ";
public void init(Activity activity, boolean isDebug, Define.TransferRunnable task) {
if (isDebug) {
Log.d(TAG, "AdjustHelper debug mode");
}
String token = Tools.GetStringVaule(activity, ResKey_Token);
if (!Tools.isEmpty(token)) {
// ad 回调
initAdjust(activity, token, isDebug, (code, adJson) -> {
if (code != ECode.Ok) {
return;
}
mTransfer.AdJson = adJson;
LogUtil.TD(TAG, "--- initAdjust ok, AdJson: %s", mTransfer.AdJson);
callTask(task);
});
// referer 回调
initReferer(activity, (code, msg) -> {
if (code != ECode.Ok) {
return;
}
mTransfer.GgJson = Tools.parseUrl2Json(msg);
LogUtil.TD(TAG, "--- initReferer ok 111, GgJson: %s", mTransfer.GgJson);
// callTask(task); 不能 callTask, 要等 onConversionDataSuccess 回调
});
} else {
initReferer(activity, (code, msg) -> {
if (code != ECode.Ok) {
return;
}
mTransfer.GgJson = Tools.parseUrl2Json(msg);
LogUtil.TD(TAG, "--- initReferer ok 222, GgJson: %s", mTransfer.GgJson);
callTask(task);
});
}
}
private void initAdjust(Activity activity, String token, boolean isDebug, Define.CodeRunnable task) {
String secret = Tools.GetStringVaule(activity, ResKey_Secret);
List<Long> infoLst = parseSecret(secret);
String environment = AdjustConfig.ENVIRONMENT_PRODUCTION;
if (isDebug) {
environment = AdjustConfig.ENVIRONMENT_SANDBOX;
}
AdjustConfig config = new AdjustConfig(activity, token, environment);
config.setAppSecret(infoLst.get(0), infoLst.get(1), infoLst.get(2), infoLst.get(3), infoLst.get(4));
config.setLogLevel(isDebug ? LogLevel.VERBOSE : LogLevel.ERROR); // disable warning logs
config.setSendInBackground(true);
// -------- 注册各种回调
// Set attribution delegate.
config.setOnAttributionChangedListener(attribution -> {
LogUtil.TD(TAG, "Attribution: " + attribution.toString());
task.run(ECode.Ok, JsonTool.toJson(attribution));
});
// Set event success tracking delegate.
config.setOnEventTrackingSucceededListener(eventSuccessResponseData -> {
LogUtil.TD(TAG, "Event success data: " + eventSuccessResponseData.toString());
});
// Set event failure tracking delegate.
config.setOnEventTrackingFailedListener(eventFailureResponseData -> {
LogUtil.TD(TAG, "Event failure data: " + eventFailureResponseData.toString());
});
// Set session success tracking delegate.
config.setOnSessionTrackingSucceededListener(sessionSuccessResponseData -> {
LogUtil.TD(TAG, "Session success data: " + sessionSuccessResponseData.toString());
});
// Set session failure tracking delegate.
config.setOnSessionTrackingFailedListener(sessionFailureResponseData -> {
LogUtil.TD(TAG, "Session failure data: " + sessionFailureResponseData.toString());
});
// Evaluate deferred deep link to be launched.
config.setOnDeeplinkResponseListener(deeplink -> {
LogUtil.TD(TAG, "Deep link URL: " + deeplink);
return true;
});
Adjust.onCreate(config);
activity.getApplication().registerActivityLifecycleCallbacks(new AdjustLifecycleCallbacks());
}
private void initReferer(Context context, Define.CodeRunnable task) {
GoogleReferrerHelper.getIns().start(context, task);
}
public static List<Long> parseSecret(String secret) {
List<Long> numLst = new ArrayList<>();
try {
String[] idArr = secret.split(",");
for (String idStr : idArr) {
long num = Long.parseLong(idStr.trim());
numLst.add((num));
}
} catch (Exception e) {
e.printStackTrace();
}
LogUtil.TA(TAG, numLst.size() == 5, "--- parseSecret error, size: %d", numLst.size());
return numLst;
}
private static final class AdjustLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
public void onActivityStarted(Activity activity) {
}
public void onActivityResumed(Activity activity) {
Adjust.onResume();
}
public void onActivityPaused(Activity activity) {
Adjust.onPause();
}
public void onActivityStopped(Activity activity) {
}
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
public void onActivityDestroyed(Activity activity) {
}
}
public void logEvent(String jsonMsg, Define.CodeRunnable task) {
if (SystemInfoUtil.ThirdSdk != Define.EThirdSdk.Adjust) {
return;
}
CEventInfo ei = JsonTool.toObject(jsonMsg, CEventInfo.class);
logEvent(ei, task);
}
public void logEvent(CEventInfo ei, Define.CodeRunnable task) {
if (ei == null || ei.name.length() == 0 || SystemInfoUtil.ThirdSdk != Define.EThirdSdk.Adjust) {
return;
}
LogUtil.TD(TAG, "--- event: %s", JsonTool.beauty(ei));
AdjustEvent adjustEvent = new AdjustEvent(ei.name);
if (ei.valueToSum > 0.001f) {
String curr = "INR";
if (ei.params.size() > 0 && ei.params.containsKey("currency")) {
curr = (String) ei.params.get("currency");
}
adjustEvent.setRevenue(0.01, curr);
}
Adjust.trackEvent(adjustEvent);
}
public String getInfo() {
return JsonTool.toJson(mTransfer);
}
// public void showMsg(Context ctx) {
// CTips tps = new CTips();
// tps.msg = String.format("%s\n%s\n%s\n%s\n%s"
// , mAttribution, mEventTrackingSucceeded, mEventTrackingFailed, mSessionTrackingSucceeded, mSessionTrackingFailed);
// Tools.tips02(ctx, tps, null);
// }
private void callTask(Define.TransferRunnable task) {
if (mIsCall || task == null) {
return;
}
mIsCall = true;
task.run(mTransfer);
}
}
自定义短链配置 - 跟踪链接
即使是点击链接先跳转到浏览器的 gp 商店, 在点击打开商店进入商店里面下载, 也不会丢失归因参数. 这个比 appsflyer 做的好很多, 靠谱.
Google Play 应用的短链推广
后台配置, 把需要透传的参数设置到 推广结构 中.
!!!不要去设置 重定向/fallback 的链接, 否者获取不到 推广结构 里面的值. !!!
adjust 回调中 可以获取到 归因数据 Addata
1
{\"Addata\":\"{\\\"adgroup\\\":\\\"bbb\\\",\\\"adid\\\":\\\"a5ad874ca32aa310c636adaf54396ab6\\\",\\\"campaign\\\":\\\"aaa\\\",\\\"creative\\\":\\\"ccc\\\",\\\"network\\\":\\\"ad3\\\",\\\"trackerName\\\":\\\"ad3::aaa::bbb::ccc\\\",\\\"trackerToken\\\":\\\"3lcxp3p\\\"}\",\"Appid\":3,\"Deviceid\":\"1183eca72cbe5513eaf1e4f1232a8ee8\",\"Ggdata\":\"{\\\"utm_content\\\":\\\"bbb\\\",\\\"adjust_reftag\\\":\\\"cvmLwqpUXZUfr\\\",\\\"utm_term\\\":\\\"ccc\\\",\\\"utm_source\\\":\\\"ad3\\\",\\\"pid\\\":\\\"1109\\\",\\\"utm_campaign\\\":\\\"aaa\\\"}\
- 如果获取的还是旧数据, 可以尝试一下 清除归因
Facebook Ads 投放
需要 Facebook 对应的 app id 接收 服务条款, 才能在 af 的 onConversionDataSuccess 回调中, 接收到 campaign 相关数据
链接: https://www.facebook.com/ads/manage/advanced_mobile_measurement/app_based_tos?appid=123123 (123123 是 app id)
合伙伙伴设置 中, 添加 facebook, 填入 fb app id.
- 然后就可以在 adjust 的回调用获取到 fb 广告系列的名字
1
2
3
4
5
6
7config.setOnAttributionChangedListener(attribution -> { // adjust 归因回调
LogUtil.TD(TAG, "Attribution: " + attribution.toString());
task.run(ECode.Ok, JsonTool.toJson(attribution));
});
// 获取到的数据
\"Addata\":\"{\\\"adgroup\\\":\\\"0918 (23845782953730115)\\\",\\\"adid\\\":\\\"eee\\\",\\\"campaign\\\":\\\"ln_1094_rum9.19\\\",\\\"creative\\\":\\\"1 (23845782953750115)\\\",\\\"network\\\":\\\"Facebook Installs\\\",\\\"trackerName\\\":\\\"Facebook Installs::ln_1094_rum9.18 (23845782953570115)::0918 (23845782953730115)::1 (23845782953750115)\\\",\\\"trackerToken\\\":\\\"ofkv4pp\\\"}\"campaign 对应的就是 fb 广告系列名字
- 然后就可以在 adjust 的回调用获取到 fb 广告系列的名字
Google Ads 投放
sd
1
{"T":"2021-04-19T17:20:23.973+0800","M":"REQ","Method":"POST","Path":"/launch","Body":"{\"Os\":2,\"Deviceid\":\"86fa7532582dcb409c6975dc6eaf30ec\",\"Appid\":3,\"Addata\":\"{\\\"trackerToken\\\":\\\"kp3v7lp\\\",\\\"trackerName\\\":\\\"Google Ads ACI::SS_1194_0419_P_P (12787822281)::119533730085::Display \\\",\\\"network\\\":\\\"Google Ads ACI\\\",\\\"campaign\\\":\\\"SS_1194_0419_P_P (12787822281)\\\",\\\"adgroup\\\":\\\"119533730085\\\",\\\"creative\\\":\\\"Display \\\",\\\"adid\\\":\\\"d4ab79b4eb9041cb24f2333d9990f8b3\\\"}\",\"Ggdata\":\"{\\\"gclid\\\":\\\"Cj0KCQjw1PSDBhDbARIsAPeTqre5pNeJUW5iTnj6Hm1dgLk8xtM-0EUZGLjhfntUekqiPUh-rSMQyLUaAgKoEALw_wcB\\\"}\",\"Plat\":1193}"}
campaign 对应的就是 广告系列名 + 广告系列 id
查看归因
所有设置 -> 测试控制台, 输入 谷歌广告 id (gaid), 可以查看到该设备的 归因数据.
清除归因
在 查看归因 后, 点击 清楚设备 即可.
api 上报事件
官方文档: https://help.adjust.com/en/article/server-to-server-events
先确定是否归因正确 查看归因 , 再查看事件的归因是否正确.
生成 s2s 秘钥 secret. 所有设置 -> S2S 安全 -> 创建识别码, 如:
b78a67xxxxxx
激活 s2s. 所有设置 -> S2S 安全 -> 激活 S2S 认证
上报参数中, 不用平台的使用 id 字段不同
- Android: gps_adid, Google 广告 id, 通过
Adjust.getGoogleAdId
回调中获取到 - ios: idfa
- Android: gps_adid, Google 广告 id, 通过
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
25
26
27
28
29
30
31
32
33# adjust
def test_adEvent(self):
from urllib.parse import urlencode, quote
appToken = "y0sxxx"
eventToken = "4vexxx"
secret = "b78a67xxxxxx"
aduid = "ed6ec341-277c-43ce-b48d-xxx"
params = {
"revenue": 100, # 收益
"currency": "INR", # 币种
"environment": "production", # 生成 or 测试环境
}
data = {
"s2s": 1,
"app_token": appToken,
"event_token": eventToken,
"gps_adid": aduid, # 不同环境配置的值不一样, Android: gps_adid, 谷歌广告id
"partner_params": quote(utils.beautyJson(params, indent=None)), # 要 encode
}
url = "https://s2s.adjust.com/event?{}".format(urlencode(data, doseq=False)) # 不要 encode
headers = {
"Authorization": "Bearer {}".format(secret),
"Content-Type": "application/json",
}
print("--- url: {}".format(url))
code, rspDct = utils.httpPost(url, headers=headers)
print("--- code: {}".format(code))
print("--- rsp: {}".format(utils.beautyJson(rspDct)))上报成功, 返回的 json 有
"status": "OK"
才代表成功了.如果是
code: 200
但是 json 是空的, 则是上报失败.1
2
3
4
5
6
7--- url: https://s2s.adjust.com/event?s2s=1&app_token=y0sxxx&event_token=4vexxx&gps_adid=ed6ec341-277c-43ce-b48d-xxx&partner_params=%257B%2522revenue%2522%253A%2520100%252C%2520%2522currency%2522%253A%2520%2522INR%2522%252C%2520%2522environment%2522%253A%2520%2522production%2522%257D
--- code: 200
--- rsp: {
"status": "OK"
}
复用 Facebook 的 app id
Google 账号被封, 新的 Google 包可以复用 Facebook 的 app id, 然后 fb 的广告账号就不用重新开户了, sdk 能正常获取到 fb 的广告数据, 也就可以正常解析为 动态渠道.
实时回传
- 占位符文档 - https://partners.adjust.com/placeholders/
- 案例文档 - https://help.adjust.com/zh/article/best-practices-callbacks
在 原始数据导出 -> 实时回传 中, 在需要 ad 通知的事件中配置需要的数据, ad 服务器会通知到这个地方
比如配置 回传 url, adjust 会用 GET 方式调用这个 url:
1
https://aaa.bbb.com/adjust?gps_adid={gps_adid}&campaign={campaign_name}
会回传:
1
[GIN] 2021/05/26 - 03:05:23 | 200 | 102.017µs | 23.19.51.100 | GET /adjust?gps_adid=d0034713-792f-4158-baf8-1ab0b960f89d&campaign=polycell_1230_3%E6%88%B7_AND_IN_AEO_YZL_0518_P02+%2823847682441000003%29
附:
比较有用的几个信息