# 演练模式

> 来源：https://help.gravity-engine.com/docs/client-sdk/android/advanced/dry-run-mode
> 说明 Android SDK 如何通过 dryRunEventWithCallback 接入引力演练模式，包含 trace_id 获取、回调处理与付费/关键行为向媒体回传的示例代码，以及 Android 演练模式快捷接入方式。



> **注意**
> 此文档主要说明引力 Android SDK 演练模式相关的代码接入部分。运营和投放的后台配置请参考[引力 & 回传演练模式](https://gravityengine.feishu.cn/wiki/G6lwwJn8NiDr9wkYUZFcp5wonme?fromScene=spaceOverview)。
>
> 目前安卓支持演练的媒体平台如下：
>
> * 安卓：巨量 & 腾讯 & 百度 & 快手 & TapTap

为了该模式更灵活的使用，建议将演练模式在代码中配置为一个开关，方便联调和测试。

| 更新日期       | 更新内容                                                                                                        |
| ---------- | ----------------------------------------------------------------------------------------------------------- |
| 2026-09-24 | 新增 Android 演练模式快捷接入：[引力：Android 演练模式快捷接入](https://gravityengine.feishu.cn/wiki/IIF2wokPLigOWdkWYodcPOycnPf) |

## 接入事项 [#接入事项]

因演练模式主要是为了付费的自定义回传，为了减少投放每次调整映射策略，导致的客户端频繁改动，引力提供了演练模式，支持客户端技术从引力获取具体订单对应的回传结果，再执行给媒体的回传。

请客户端技术务必确认下面的流程图步骤明确后开始接入。

### Android SDK 演练 [#android-sdk-演练]

#### 快捷接入 [#快捷接入]

参考文档：[引力：Android 演练模式快捷接入](https://gravityengine.feishu.cn/wiki/IIF2wokPLigOWdkWYodcPOycnPf)

#### 手动接入 [#手动接入]

以下是 Android SDK，技术需要处理的 5 大步骤：

1. 升级引力 SDK 到 [5.0.20](https://github.com/GravityInfinite/GravityEngine-Android-Demo/releases) 及以上版本，建议使用最新版本；
2. 正常按照[引力接入文档](/docs/client-sdk/android/quickstart)接入，建议您一定上报用户初始化渠道，对应字段 `channel` 值，不上报无法选择[渠道包相关归因](https://gravityengine.feishu.cn/wiki/M9ZxwS12dixXvfkjwGPcRed8nic?from=space_search)；
3. 在用户发生付费行为时，客户端需要获取到 `trace_id`，获取方式：
   1. **如果历史是通过客户端上报付费事件给引力**：调用引力 SDK 付费事件上报接口 `trackPayEvent`，该接口会返回一个 `trace_id`，请使用变量保存好；
   2. **如果历史是通过服务端 API 上报付费事件给引力**：API 上报之后，您的服务端需要返回本次事件上报的 `$order_id` 给您的客户端，此时 `$order_id` 即为 `trace_id`
4. 然后调用 `dryRunEventWithCallback` 接口，传入上一步获取的 `trace_id`；
5. 客户在 `dryRunEventWithCallback` 方法的回调函数中获取当前事件所映射的媒体回传事件，并根据返回的信息来执行向媒体 SDK 上报事件的逻辑；

核心代码实现逻辑如下，请注意看注释！（包含了关键行为和付费相关代码，根据实际取用）

```java
DryRunEventCallback callback = new DryRunEventCallback() {
    @Override
    public void onFailed(String errorMsg) {
        Log.d(TAG, "errorMsg " + errorMsg);
    }

    @Override
    public void onEmpty(GravityEngineSDK.GEDryRunEventEmptyType type) {
        // type枚举：
        // 当前用户归因媒体未开启演练模式：EMPTY_TYPE_NOT_ENABLE
        // 不在回传窗口期：EMPTY_TYPE_NO_IN_POSTBACK_WINDOW
        // 扣量不回传：EMPTY_TYPE_SKIP
        Log.d(TAG, "not need to postback anyone");
    }

    @Override
    public void onTrackPay(String eventMediaType, int payAmount, String traceId, JSONObject otherParams) {
        // payAmount参数单位是分，给媒体回传的时候，请留意是否需要转化成元
        Log.d(TAG, "p " + eventMediaType + " " + payAmount + " " + traceId);
        if (eventMediaType.equals("bytedance")) {
            // 调用巨量回传付费事件的方法，主要参数是payAmount
            // 其他参数可以从otherParams中解析出来
            String productType = otherParams.optString("type", "");
            String productName = otherParams.optString("name", "");
            String productId = otherParams.optString("id", "");
            int productNum = otherParams.optInt("num", 1);
            String productPayChannel = otherParams.optString("channel", "");
            String currencyType = otherParams.optString("currency", "$");
            GameReportHelper.onEventPurchase(productType, productName, productId, productNum, productPayChannel, currencyType, true, payAmount);
        } else if (eventMediaType.equals("tencent")) {
            // 调用腾讯回传付费事件的方法，主要参数是payAmount
        }
    }

    @Override
    public void onTrackKeyActive(String eventMediaType, String traceId, JSONObject otherParams) {
        Log.d(TAG, "p " + eventMediaType + " " + traceId);
        if (eventMediaType.equals("bytedance")) {
            // 调用巨量回传关键行为事件的方法
            JSONObject paramsObj = new JSONObject();
            String origin_event = otherParams.optString("origin_event", "游戏过一关");
            try {
                paramsObj.put("origin_event", origin_event); // 添加你的原始事件名称参数
            } catch (JSONException e) {
                e.printStackTrace();
            }
            AppLog.onEventV3("game_addiction", paramsObj);
        } else if (eventMediaType.equals("tencent")) {
            // 调用腾讯回传关键行为事件的方法
        }
    }
};
// 演练模式-付费案例
final String traceId = GravityEngineHelper.getInstance().trackPayEvent(200, "CNY", "order_id" + System.currentTimeMillis(), "月卡", "支付宝");
JSONObject payOtherParams = new JSONObject();
try {
    payOtherParams.put("type", "gift");
    payOtherParams.put("name", "flower");
    payOtherParams.put("id", "008");
    payOtherParams.put("num", 1);
    payOtherParams.put("channel", "wechat");
    payOtherParams.put("currency", "￥");

} catch (JSONException e) {
    throw new RuntimeException(e);
}
GravityEngineHelper.getInstance().dryRunEventWithCallback(traceId, payOtherParams, callback);

// 演练模式-关键行为案例
final String keyActiveTraceId = GravityEngineHelper.getInstance().track("在引力定义的关键行为事件");
JSONObject keyActiveOtherParams = new JSONObject();
try {
    keyActiveOtherParams.put("origin_event", "游戏过关事件");

} catch (JSONException e) {
    throw new RuntimeException(e);
}
GravityEngineHelper.getInstance().dryRunEventWithCallback(keyActiveTraceId, keyActiveOtherParams, callback);
```

上述实例代码中 `GravityEngineHelper.getInstance()` 为 `GravityEngineSDK` 对象的实例，您可以通过调用 `GravityEngineSDK.setupAndStart` 获取\~

`onTrackPay` 回调中的 `payAmount` 参数单位是分，给具体媒体回传的时候，请留意是否需要转化成元\~

## 常见问题 [#常见问题]

1. **如果代码执行了演练模式的部分，但是应用没有配置，演练模式是否生效？**

   1. 不开启演练模式，调用演练模式的接口会返回 `onEmpty`（当前用户归因媒体未开启演练模式）；
   2. 需同时开启，否则演练模式不生效。

2. **是否有相关接口能获取到应用管理的演练模式是否开启以及选择的类型？**

   1. 暂时无法获取。

3. **如何验证演练模式接入是否成功？**

   1. 在引力后台开启演练模式，调用演练模式之后验证返回的数据是否符合配置的映射。

4. **当前用户归因媒体未开启演练模式怎么理解？**

   1. 当用户来自**暂不支持演练的渠道或未开启演练模式**时会显示此提示。目前仅**腾讯、巨量、百度、快手、TapTap**的归因用户可开启演练，其他渠道（如 B 站）及自然量用户均会返回此状态。

5. **订单细查 SDK 回传状态分别是什么含义？**

   1. 等待判断：条件映射状态为未通过的；
   2. 无需 SDK 回传：满足 SDK 映射的条件及回传比例，执行扣传；
   3. SDK 待回传：等待 SDK 返回回传结果，一般是没有调用演练模式接口；
   4. SDK 已回传：已通过 SDK 回传。
