others-Adjust第三方统计

others-Adjust第三方统计


前篇


接入

  1. build.gradle 引入库

    1
    2
    3
    4
    5
    dependencies {
    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'
    }
  2. 权限 及 广播 的 清单配置 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>
  3. 混淆配置 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.** { *; }
  4. 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
    248
    package 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 {
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

    }

    @Override
    public void onActivityStarted(Activity activity) {

    }

    @Override
    public void onActivityResumed(Activity activity) {
    Adjust.onResume();
    }

    @Override
    public void onActivityPaused(Activity activity) {
    Adjust.onPause();
    }

    @Override
    public void onActivityStopped(Activity activity) {

    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

    }

    @Override
    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 应用的短链推广

  1. 后台配置, 把需要透传的参数设置到 推广结构 中.

    !!!不要去设置 重定向/fallback 的链接, 否者获取不到 推广结构 里面的值. !!!

  2. 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
      7
      config.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 广告系列名字


  • 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

先确定是否归因正确 查看归因 , 再查看事件的归因是否正确.

  1. 生成 s2s 秘钥 secret. 所有设置 -> S2S 安全 -> 创建识别码, 如: b78a67xxxxxx

  2. 激活 s2s. 所有设置 -> S2S 安全 -> 激活 S2S 认证

  3. 上报参数中, 不用平台的使用 id 字段不同

    • Android: gps_adid, Google 广告 id, 通过 Adjust.getGoogleAdId 回调中获取到
    • ios: idfa

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 的广告数据, 也就可以正常解析为 动态渠道.


实时回传

原始数据导出 -> 实时回传 中, 在需要 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

附: