> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cloudx.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Trusted Arbiter

> 在 Android 应用中比较 CloudX 出价与受支持的第三方出价

Trusted Arbiter 会比较已加载的 CloudX 出价与受支持的第三方出价，并返回选中的平台。CloudX SDK 4.1.0 及更高版本支持 CloudX、Unity LevelPlay 和 PubMatic 出价输入。CloudX SDK 4.2.0 及更高版本还支持发布商传入的自定义出价输入。

<Info>
  请在 `CloudX.initialize()` 完成后再调用 `CloudX.arbiter()`。在仲裁服务可用之前，SDK 会在传入的出价中回退选择可比较美元出价最高的平台。
</Info>

## 基础 API

从已加载的广告创建出价候选项，然后传给 `CloudX.arbiter()`。

```kotlin theme={null}
// cloudXAd 是 CloudX onAdLoaded 回调中的 CloudXAd 对象。
// levelPlayAdInfo 是 Unity LevelPlay 广告信息对象。
// pobBid 是 PubMatic/OpenWrap 出价对象。
val bids = listOf(
    CloudXArbiterBid.cloudX(cloudXAd),
    CloudXArbiterBid.levelPlay(
        networkName = levelPlayAdInfo.adNetwork,
        revenue = levelPlayAdInfo.revenue,
        precision = levelPlayAdInfo.precision,
    ),
    CloudXArbiterBid.pubmatic(
        price = pobBid.price,
        partnerName = pobBid.partnerName,
    )
)

val configuration = CloudXArbiterConfiguration.builder(bids).build()

CloudX.arbiter(configuration, object : CloudXArbiterListener {
    override fun onCompleted(result: CloudXArbiterResult) {
        Log.d("CloudX", "选中的平台: ${result.platform.name}")
    }
})
```

`CloudXArbiterBid.cloudX()` 接收 CloudX 加载回调中的 `CloudXAd` 对象。`levelPlay()` 接收 Unity LevelPlay 广告信息值。`pubmatic()` 接收 PubMatic OpenWrap 出价价格和可选的合作伙伴名称。出价工厂方法中的 `partnerName` 和 `extras` 映射均为可选参数。`onCompleted()` 在主线程上回调，因此你可以直接在其中展示广告或更新 UI。

`result.platform` 在选中平台时为 `CloudXArbiterPlatform.CLOUDX`、`LEVELPLAY` 或 `PUBMATIC`；当无法选出获胜平台时（例如未传入任何出价），则为 `CloudXArbiterPlatform.NONE`。

<Tip>
  Kotlin 调用方可以使用挂起函数重载来代替监听器，它会返回相同的 `CloudXArbiterResult`：

  ```kotlin theme={null}
  val result = CloudX.arbiter(configuration)
  Log.d("CloudX", "选中的平台: ${result.platform.name}")
  ```
</Tip>

## 自定义出价输入

当需要让 Trusted Arbiter 比较 CloudX 与没有专用出价辅助方法的第三方平台时，可以使用 `CloudXArbiterBid.custom()`。

```kotlin theme={null}
val customBid = CloudXArbiterBid.custom(
    platformName = "my_mediation_platform",
    networkName = "winning_demand_source",
    revenuePerImpressionUSD = 0.00125,
    precision = CloudXArbiterPrecision.EXACT,
    extras = mapOf("ad_unit" to "third-party-ad-unit-id"),
)

val configuration = CloudXArbiterConfiguration.builder(
    listOf(CloudXArbiterBid.cloudX(cloudXAd), customBid)
).build()
```

当自定义出价胜出时，`result.platform` 为 `CloudXArbiterPlatform.CUSTOM`，`result.platformName` 为出价中传入的 `platformName`。`revenuePerImpressionUSD` 应传入单次展示的美元收益，而不是 CPM。请使用 `CloudXArbiterPrecision.EXACT`、`ESTIMATED`、`PUBLISHER_DEFINED` 或 `UNDEFINED` 描述该收益值的精度。

## 分步示例：在 CloudX 与 LevelPlay 之间仲裁

本演练展示了应读取哪个 Unity LevelPlay 回调，以及应将哪些值传入 `CloudX.arbiter()`。示例使用插屏广告，但相同的字段映射适用于任何广告格式。

<Steps>
  <Step title="加载两个候选项">
    创建 CloudX 和 LevelPlay 插屏广告，绑定监听器，并为每个平台启动加载。

    ```kotlin theme={null}
    val cloudXInterstitial = CloudX.createInterstitial(context, "YOUR_CLOUDX_AD_UNIT_ID")
    cloudXInterstitial.listener = cloudXListener
    cloudXInterstitial.load()

    val levelPlayInterstitial = LevelPlayInterstitialAd("YOUR_LEVELPLAY_AD_UNIT_ID")
    levelPlayInterstitial.setListener(levelPlayListener)
    levelPlayInterstitial.loadAd()
    ```
  </Step>

  <Step title="保存每个平台已加载的广告">
    LevelPlay 在其 `onAdLoaded` 回调中提供 `LevelPlayAdInfo`；CloudX 在 `onAdLoaded` 中提供 `CloudXAd`。请保存两者——下一步会从中读取仲裁输入。

    ```kotlin theme={null}
    private var cloudXAd: CloudXAd? = null
    private var levelPlayInfo: LevelPlayAdInfo? = null

    // CloudXInterstitialListener
    override fun onAdLoaded(cloudXAd: CloudXAd) {
        this.cloudXAd = cloudXAd
    }

    // LevelPlayInterstitialAdListener
    override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
        levelPlayInfo = levelPlayAdInfo
    }
    ```
  </Step>

  <Step title="将值映射为出价">
    从 `LevelPlayAdInfo` 读取 LevelPlay 字段，并传给 `CloudXArbiterBid.levelPlay()`。CloudX 出价直接接收 `CloudXAd`。`listOfNotNull` 只会提交实际加载成功的平台。

    | `LevelPlayAdInfo` 字段 | 类型       | `CloudXArbiterBid.levelPlay` 参数 |
    | -------------------- | -------- | ------------------------------- |
    | `adNetwork`          | `String` | `networkName`                   |
    | `revenue`            | `Double` | `revenue`                       |
    | `precision`          | `String` | `precision`                     |

    ```kotlin theme={null}
    val bids = listOfNotNull(
        cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
        levelPlayInfo?.let { info ->
            CloudXArbiterBid.levelPlay(
                networkName = info.adNetwork,   // LevelPlayAdInfo.adNetwork
                revenue = info.revenue,         // LevelPlayAdInfo.revenue
                precision = info.precision,     // LevelPlayAdInfo.precision
            )
        },
    )
    ```
  </Step>

  <Step title="运行仲裁器">
    将出价封装进 `CloudXArbiterConfiguration`，连同监听器传给 `CloudX.arbiter()`。`onCompleted()` 在主线程上回调。

    ```kotlin theme={null}
    val configuration = CloudXArbiterConfiguration.builder(bids).build()
    CloudX.arbiter(configuration, object : CloudXArbiterListener {
        override fun onCompleted(result: CloudXArbiterResult) {
            showWinner(result)
        }
    })
    ```
  </Step>

  <Step title="展示获胜平台">
    根据 `result.platform` 进行分支，展示获胜平台的广告。`CloudXArbiterPlatform.NONE` 表示未选出获胜平台——此时不展示广告，继续应用流程。

    ```kotlin theme={null}
    private fun showWinner(result: CloudXArbiterResult) {
        when (result.platform) {
            CloudXArbiterPlatform.CLOUDX -> cloudXInterstitial.show(activity)
            CloudXArbiterPlatform.LEVELPLAY -> levelPlayInterstitial.showAd(activity)
            else -> { } // CloudXArbiterPlatform.NONE——无获胜平台；不展示广告
        }
    }
    ```
  </Step>
</Steps>

下方的 `ArbiterInterstitialController` 将上述步骤封装为一个可复用的组件，会在到达广告位之前提前准备好获胜平台。

## 插屏广告示例

这个插屏广告示例会在两个平台之间仲裁：CloudX 和 Unity LevelPlay。在到达广告位之前先准备好获胜平台：

1. 并行加载 CloudX 和 LevelPlay。
2. 等待两个平台都加载完成或加载失败。
3. 只将已加载的候选项提交给 Trusted Arbiter。
4. 缓存选中的平台。
5. 到达广告位时，立即展示缓存的获胜广告。

如果两个平台都加载失败，开始新的加载周期。如果到达广告位时还没有准备好获胜平台，则继续应用流程，不展示广告。

```kotlin ArbiterInterstitialController.kt theme={null}
/**
 * 提前准备好 Trusted Arbiter 的获胜平台，以便在到达广告位时立即展示插屏广告。
 *
 * 并行加载 [cloudXInterstitial] 和 [levelPlayInterstitial]，等待两者都加载完成或加载失败后，
 * 将已加载的候选项提交给 [CloudX.arbiter]，并将选中的 [CloudXArbiterPlatform] 缓存到
 * [nextWinner] 中。
 */
class ArbiterInterstitialController(
    private val cloudXInterstitial: CloudXInterstitialAd,
    private val levelPlayInterstitial: LevelPlayInterstitialAd,
) {
    /** 在获胜平台准备就绪后接收仲裁结果。 */
    interface Listener {
        /** 当仲裁器为下一次展示选定 [platform] 时调用。 */
        fun onWinnerPrepared(platform: CloudXArbiterPlatform)
    }

    /** 设置该属性以监听 [Listener.onWinnerPrepared] 回调。 */
    var listener: Listener? = null
    private var cloudXAd: CloudXAd? = null
    private var cloudXLoadDone = false
    private var levelPlayAdInfo: LevelPlayAdInfo? = null
    private var levelPlayLoadDone = false
    private var nextWinner: CloudXArbiterPlatform? = null

    init {
        cloudXInterstitial.listener = createCloudXListener()
        levelPlayInterstitial.setListener(createLevelPlayListener())
    }

    /** 为当前没有缓存广告的每个平台启动加载。 */
    fun loadMissingAds() {
        if (cloudXAd == null) cloudXInterstitial.load()
        if (levelPlayAdInfo == null) levelPlayInterstitial.loadAd()
    }

    /**
     * 在 [activity] 上展示已准备好的获胜广告，仅在实际调用展示方法时返回 true。
     *
     * 当没有就绪的获胜平台，或缓存的广告已不再可用时返回 false，此时会启动新的加载周期。
     */
    fun showAtPlacement(activity: Activity): Boolean {
        return when (nextWinner) {
            CloudXArbiterPlatform.CLOUDX -> showCloudX(activity)
            CloudXArbiterPlatform.LEVELPLAY -> showLevelPlay(activity)
            else -> false
        }
    }

    /**
     * 在两个平台都完成加载后运行仲裁器，然后缓存获胜平台。
     *
     * 在两个加载都完成之前会提前返回。如果两个平台都未加载成功，则重新启动加载周期；
     * 否则将已加载的候选项提交给 [CloudX.arbiter]。
     */
    private fun maybePrepareWinner() {
        if (!cloudXLoadDone || !levelPlayLoadDone) return

        if (cloudXAd == null && levelPlayAdInfo == null) {
            cloudXLoadDone = false
            levelPlayLoadDone = false
            loadMissingAds()
            return
        }

        val bids = listOfNotNull(
            cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
            levelPlayAdInfo?.let { adInfo ->
                CloudXArbiterBid.levelPlay(
                    networkName = adInfo.adNetwork,
                    revenue = adInfo.revenue,
                    precision = adInfo.precision,
                )
            }
        )

        CloudX.arbiter(
            configuration = CloudXArbiterConfiguration.builder(bids).build(),
            listener = object : CloudXArbiterListener {
                override fun onCompleted(result: CloudXArbiterResult) {
                    nextWinner = result.platform
                    listener?.onWinnerPrepared(result.platform)
                }
            }
        )
    }

    private fun showCloudX(activity: Activity): Boolean {
        if (cloudXInterstitial.isAdReady) {
            cloudXInterstitial.show(activity)
            return true
        }

        clearCloudXAndLoadMissingAds()
        return false
    }

    private fun showLevelPlay(activity: Activity): Boolean {
        if (levelPlayInterstitial.isAdReady) {
            levelPlayInterstitial.showAd(activity)
            return true
        }

        clearLevelPlayAndLoadMissingAds()
        return false
    }

    private fun clearCloudXAndLoadMissingAds() {
        cloudXAd = null
        cloudXLoadDone = false
        nextWinner = null
        loadMissingAds()
    }

    private fun clearLevelPlayAndLoadMissingAds() {
        levelPlayAdInfo = null
        levelPlayLoadDone = false
        nextWinner = null
        loadMissingAds()
    }

    private fun createCloudXListener() = object : CloudXInterstitialListener {
        override fun onAdLoaded(cloudXAd: CloudXAd) {
            this@ArbiterInterstitialController.cloudXAd = cloudXAd
            cloudXLoadDone = true
            maybePrepareWinner()
        }

        override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
            cloudXAd = null
            cloudXLoadDone = true
            maybePrepareWinner()
        }

        override fun onAdDisplayed(cloudXAd: CloudXAd) = Unit

        override fun onAdDisplayFailed(cloudXAd: CloudXAd, cloudXError: CloudXError) {
            clearCloudXAndLoadMissingAds()
        }

        override fun onAdHidden(cloudXAd: CloudXAd) {
            clearCloudXAndLoadMissingAds()
        }

        override fun onAdClicked(cloudXAd: CloudXAd) = Unit
    }

    private fun createLevelPlayListener() = object : LevelPlayInterstitialAdListener {
        override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
            this@ArbiterInterstitialController.levelPlayAdInfo = levelPlayAdInfo
            levelPlayLoadDone = true
            maybePrepareWinner()
        }

        override fun onAdLoadFailed(levelPlayAdError: LevelPlayAdError) {
            levelPlayAdInfo = null
            levelPlayLoadDone = true
            maybePrepareWinner()
        }

        override fun onAdInfoChanged(levelPlayAdInfo: LevelPlayAdInfo) {
            this@ArbiterInterstitialController.levelPlayAdInfo = levelPlayAdInfo
        }

        override fun onAdDisplayed(levelPlayAdInfo: LevelPlayAdInfo) = Unit

        override fun onAdDisplayFailed(
            levelPlayAdError: LevelPlayAdError,
            levelPlayAdInfo: LevelPlayAdInfo
        ) {
            clearLevelPlayAndLoadMissingAds()
        }

        override fun onAdClosed(levelPlayAdInfo: LevelPlayAdInfo) {
            clearLevelPlayAndLoadMissingAds()
        }

        override fun onAdClicked(levelPlayAdInfo: LevelPlayAdInfo) = Unit
    }
}
```

`showAtPlacement()` 只有在实际调用广告展示方法时才会返回 `true`。`onAdInfoChanged()` 会在 LevelPlay 广告仍然处于已加载状态时，保持缓存的 LevelPlay 候选项为最新。

对于 PubMatic OpenWrap，使用 `CloudXArbiterBid.pubmatic(price, partnerName)` 创建第三方出价。如果仲裁服务不可用，SDK 会在传入的受支持出价中回退选择可比较美元出价最高的平台。
