Trusted Arbiter
在 Android 应用中比较 CloudX 出价与受支持的第三方出价
Trusted Arbiter 会比较已加载的 CloudX 出价与受支持的第三方出价,并返回选中的平台。CloudX SDK 4.1.0 及更高版本支持 CloudX、Unity LevelPlay 和 PubMatic 出价输入。CloudX SDK 4.2.0 及更高版本还支持发布商传入的自定义出价输入。
支持的广告格式
Trusted Arbiter 与广告格式无关:无论广告格式如何,它都会接收任意已加载的 CloudX 广告,并与传入的第三方出价进行比较。横幅广告、MREC、插屏广告和激励视频广告均受支持。
- 插屏广告和激励视频广告(全屏格式)遵循本页后文展示的分步演练和控制器模式。
- 横幅广告和 MREC(视图格式)需要横幅与 MREC 仲裁一节中介绍的额外处理,因为落选出价对应的视图绝不能被添加到视图层级中。
基础 API
从已加载的广告创建出价候选项,然后传给 CloudX.arbiter()。
// 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。
AdMob 与 Google Ad Manager
CloudX 会将已加载的 CloudX 广告与已加载的 AdMob 或 Google Ad Manager 广告进行比较。AdMob 和 Ad Manager 是两个独立的需求来源,因此二者可以同时参与同一次仲裁。此方式适用于发布商自行管理的聚合配置,包括商业上称为 AdMob Pro 的账户;SDK 不提供单独的 AdMob Pro API。
Google 需求方通常不会在展示前透露已加载广告的价格,因此仲裁器无法获得可与 CloudX 出价比较的 pre-bid 价格。CloudX 会根据同类广告单元过往的表现估算出价,因此你无需自行提供价格。不需要任何 pre-bid 定价 API。
如果你的 AdMob 账户能够在 pre-bid 阶段提供展示级收益数据,也可以自行提供该精确价格来代替使用估算值——参见下方使用 pre-bid ILRD 手动输入价格。
将 Google 付费事件回传给 CloudX(必需)
转发 Google 的付费事件是 Trusted Arbiter 的 AdMob 与 Ad Manager 集成中的必需环节,而不是可选的分析增强项。在展示通过仲裁胜出的 AdMob 或 Ad Manager 广告之后,必须把 Google 为该广告上报的展示级收入交给 CloudX SDK。如果缺少这一步,CloudX 就无法得知 Google 需求方实际支付的价格,后续仲裁的估算质量也会随之下降。
Google 只通过每个广告的 OnPaidEventListener 下发这类收入数据——不存在全局展示事件总线——因此请为每个参与仲裁的 Google 广告设置该监听器,并通过 CloudX.reportRevenueData() 转发每个事件。AdValue.valueMicros 以所报告货币的微单位表示,需要除以 1_000_000.0 才能得到单次展示的收益。AdMob 广告请传入 CloudXRevenuePlatform.ADMOB,Ad Manager 广告请传入 CloudXRevenuePlatform.GAM。
private fun Int.toCloudXRevenuePrecision(): CloudXRevenuePrecision = when (this) {
AdValue.PrecisionType.PRECISE -> CloudXRevenuePrecision.EXACT
AdValue.PrecisionType.ESTIMATED -> CloudXRevenuePrecision.ESTIMATED
AdValue.PrecisionType.PUBLISHER_PROVIDED -> CloudXRevenuePrecision.PUBLISHER_DEFINED
else -> CloudXRevenuePrecision.UNDEFINED
}
private fun reportGooglePaidEvent(
platform: CloudXRevenuePlatform,
adValue: AdValue,
adFormat: String,
adUnitId: String,
responseInfo: ResponseInfo?,
): Boolean {
val servedBy = responseInfo?.loadedAdapterResponseInfo
return CloudX.reportRevenueData(
CloudXRevenueData.builder(
platform = platform,
revenue = adValue.valueMicros / 1_000_000.0,
adFormat = adFormat,
)
.currencyCode(adValue.currencyCode)
.precision(adValue.precisionType.toCloudXRevenuePrecision())
.networkName(servedBy?.adSourceName)
.adUnitId(adUnitId)
.thirdPartyAdPlacementId(servedBy?.adSourceInstanceName)
.build(),
)
}
// 在展示参与仲裁的 Google 广告之前,为它设置监听器。
adMobInterstitial.setOnPaidEventListener { adValue ->
reportGooglePaidEvent(
platform = CloudXRevenuePlatform.ADMOB,
adValue = adValue,
adFormat = "interstitial",
adUnitId = adMobAdUnitId,
responseInfo = adMobInterstitial.responseInfo,
)
}
// Ad Manager 广告的上报方式相同,只需改用 GAM 平台。
adManagerInterstitial.setOnPaidEventListener { adValue ->
reportGooglePaidEvent(
platform = CloudXRevenuePlatform.GAM,
adValue = adValue,
adFormat = "interstitial",
adUnitId = adManagerAdUnitId,
responseInfo = adManagerInterstitial.responseInfo,
)
}当事件被接受进入 CloudX 收入管道时,reportRevenueData() 返回 true;如果 SDK 尚未初始化或服务端收入追踪已关闭,则返回 false。对于横幅广告和 MREC,只需为 AdView 设置一次监听器——Google 会在同一个视图上重复触发付费事件——并将格式传为 "banner" 或 "mrec"。完整的字段说明以及其他受支持的平台参见发布商上报收入数据。
用你已加载广告的广告单元 ID 创建出价:
// networkName 为可选参数;如果你知道获胜的广告来源,请传入它,
// 例如 responseInfo?.loadedAdapterResponseInfo?.adSourceName。
val adMobBid = CloudXArbiterBid.adMob(
adUnitId = adMobAdUnitId,
networkName = adMobNetworkName ?: "admob",
manualRevenuePerImpressionUSD = null,
extras = emptyMap(),
)
// Ad Manager 广告单元 ID 的格式为 /NNNNNNN/placement/name。
val adManagerBid = CloudXArbiterBid.gam(
adUnitId = "/21775744923/example/interstitial",
networkName = "gam",
manualRevenuePerImpressionUSD = null,
extras = emptyMap(),
)
val configuration = CloudXArbiterConfiguration.builder(
listOf(CloudXArbiterBid.cloudX(cloudXAd), adMobBid, adManagerBid)
).build()
CloudX.arbiter(configuration, object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
when (result.platform) {
CloudXArbiterPlatform.CLOUDX -> cloudXInterstitial.show(activity)
CloudXArbiterPlatform.ADMOB -> adMobInterstitial.show(activity)
CloudXArbiterPlatform.GAM -> adManagerInterstitial.show(activity)
else -> {}
}
}
})获胜的 Google 出价会报告自己的平台——CloudXArbiterPlatform.ADMOB 或 CloudXArbiterPlatform.GAM。你不再需要像处理自定义出价那样,通过 result.platformName 来区分这两个来源。
传入空白的广告单元 ID,出价对象仍会正常构建,不会导致应用崩溃,但它不具备可用的身份信息:既不会被定价,也会被服务器拒绝。
使用 pre-bid ILRD 手动输入价格
部分 AdMob 账户可以在 pre-bid 阶段提供展示级收益数据(ILRD):已加载广告的广告价值在加载时即可获取,早于广告展示。这是一项受账户控制的历史能力,请与你的 Google 客户团队确认你的账户是否已启用。在展示之前就已知的精确单次展示价格,是自行提供价格优于 CloudX 估算值的唯一场景。
将 pre-bid 广告价值作为 manualRevenuePerImpressionUSD 传入,它会覆盖估算值:
val adMobBid = CloudXArbiterBid.adMob(
adUnitId = adMobAdUnitId,
networkName = adMobNetworkName ?: "admob",
// valueMicros 以微单位(micros)表示:1,000,000 微单位等于 1 个货币单位。
// 请勿再额外除以 1,000——该数值本身已是单次展示的收益,而不是 CPM。
manualRevenuePerImpressionUSD = preBidAdValue.valueMicros / 1_000_000.0,
extras = emptyMap(),
)该数值的处理方式:
0.0是一个真实的价格。它表示该出价价值为零——而不是价格缺失。- 负值和非有限值不是有效价格,因此会被视为缺失并记录日志。
- 空白的广告单元 ID 会导致手动价格被完全丢弃,因为没有身份信息的出价无法通过校验。
manualRevenuePerImpressionUSD 是单次展示的美元收益,而不是 CPM。请换算你的价格来源报告的数值:
- 以微单位(micros)表示的数值 必须除以 1,000,000。AdMob 在 Android 上以微单位报告广告价值,1,000,000 微单位等于 1 个货币单位,因此
AdValue.getValueMicros()为5000时对应单次展示0.005。请勿把微单位数值误当作 eCPM 而再额外除以 1,000。 - 非美元金额 必须先换算为美元。
分步示例:在 CloudX 与 LevelPlay 之间仲裁
本演练展示了应读取哪个 Unity LevelPlay 回调,以及应将哪些值传入 CloudX.arbiter()。示例使用插屏广告,但相同的字段映射适用于任何广告格式——横幅广告和 MREC 有何不同请参见支持的广告格式。
加载两个候选项
创建 CloudX 和 LevelPlay 插屏广告,绑定监听器,并为每个平台启动加载。
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()保存每个平台已加载的广告
LevelPlay 在其 onAdLoaded 回调中提供 LevelPlayAdInfo;CloudX 在 onAdLoaded 中提供 CloudXAd。请保存两者——下一步会从中读取仲裁输入。
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
}将值映射为出价
从 LevelPlayAdInfo 读取 LevelPlay 字段,并传给 CloudXArbiterBid.levelPlay()。CloudX 出价直接接收 CloudXAd。listOfNotNull 只会提交实际加载成功的平台。
LevelPlayAdInfo 字段 | 类型 | CloudXArbiterBid.levelPlay 参数 |
|---|---|---|
adNetwork | String | networkName |
revenue | Double | revenue |
precision | String | precision |
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
)
},
)运行仲裁器
将出价封装进 CloudXArbiterConfiguration,连同监听器传给 CloudX.arbiter()。onCompleted() 在主线程上回调。
val configuration = CloudXArbiterConfiguration.builder(bids).build()
CloudX.arbiter(configuration, object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
showWinner(result)
}
})展示获胜平台
根据 result.platform 进行分支,展示获胜平台的广告。CloudXArbiterPlatform.NONE 表示未选出获胜平台——此时不展示广告,继续应用流程。
private fun showWinner(result: CloudXArbiterResult) {
when (result.platform) {
CloudXArbiterPlatform.CLOUDX -> cloudXInterstitial.show(activity)
CloudXArbiterPlatform.LEVELPLAY -> levelPlayInterstitial.showAd(activity)
else -> { } // CloudXArbiterPlatform.NONE——无获胜平台;不展示广告
}
}下方的 ArbiterInterstitialController 将上述步骤封装为一个可复用的组件,会在到达广告位之前提前准备好获胜平台。
插屏广告示例
这个插屏广告示例会在两个平台之间仲裁:CloudX 和 Unity LevelPlay。在到达广告位之前先准备好获胜平台:
- 并行加载 CloudX 和 LevelPlay。
- 等待两个平台都加载完成或加载失败。
- 只将已加载的候选项提交给 Trusted Arbiter。
- 缓存选中的平台。
- 到达广告位时,立即展示缓存的获胜广告。
如果两个平台都加载失败,开始新的加载周期。如果到达广告位时还没有准备好获胜平台,则继续应用流程,不展示广告。
/**
* 提前准备好 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 会在传入的受支持出价中回退选择可比较美元出价最高的平台。
横幅与 MREC 仲裁
横幅广告和 MREC 属于视图格式:多个网络可以同时持有已加载的广告,但屏幕上只能展示一个视图。相较于全屏格式,横幅和 MREC 的仲裁多出三项要求。
禁用自动刷新
必须在各处都关闭自动刷新,让仲裁器(而不是各网络内部的计时器)来控制何时展示新广告:
- 在 CloudX 控制台中为该广告单元关闭自动刷新。
- 创建
CloudXAdView后立即调用stopAutoRefresh()。 - 同时在其他参与仲裁的网络的横幅广告 API 上禁用自动刷新。
视图附加
所有候选广告都应在屏幕外加载。只有获胜出价对应的横幅视图可以被添加到视图层级或展示。落选的视图应保持未附加状态——不要将其添加到任何父视图,哪怕只是临时添加——因为添加视图会使其渲染,并可能为仲裁器拒绝的出价触发展示。与标准横幅集成不同(后者在创建时就将视图添加到布局中,参见 横幅与 MREC),使用 Trusted Arbiter 时视图不得在创建时附加——只有在仲裁选中其为获胜方后才能附加。
刷新周期
禁用自动刷新后,仲裁器必须驱动自己的刷新循环:
运行第一轮
并行加载所有网络的候选广告,运行仲裁器,并附加获胜视图。
为获胜方启动下一次加载
获胜出价的展示一旦触发,立即为获胜网络启动新一轮加载,以便为下一轮做好准备。
保留未获胜的已填充广告
保留未获胜网络已经填充的广告,留待下一轮仲裁使用。只对上一轮未填充的网络重新发起加载请求。
重新仲裁
等待未完成的加载响应全部返回后,针对完整的候选集合再次运行仲裁器。
按周期切换
每隔 20-30 秒刷新一次展示中的广告,将新的获胜视图替换掉旧视图。间隔短于 20 秒会降低 CPM 表现。
横幅广告示例
以下示例在 CloudX 横幅广告与 LevelPlay 横幅广告之间进行仲裁,并驱动上述刷新周期。MREC 的结构相同——将 CloudX.createBanner() 替换为 CloudX.createMREC(),并将 LevelPlay 横幅类型替换为 LevelPlay MREC 类型即可。
/**
* 为横幅广告位驱动 Trusted Arbiter:并行加载 [cloudXBanner] 和 [levelPlayBanner],
* 在已填充的候选项之间进行仲裁,将获胜视图添加到 [container],并按
* [refreshIntervalMs] 周期性刷新。
*
* 必须在两个网络上都禁用自动刷新(CloudX 通过控制台设置,此处通过
* `stopAutoRefresh()`,LevelPlay 通过其对应设置)——刷新周期改由本控制器接管。
*/
class ArbiterBannerController(
private val cloudXBanner: CloudXAdView,
private val levelPlayBanner: LevelPlayBannerAdView,
private val container: ViewGroup,
private val refreshIntervalMs: Long = 25_000L,
) {
private val handler = Handler(Looper.getMainLooper())
private var cloudXAd: CloudXAd? = null
private var cloudXFilled = false
private var cloudXLoadDone = false
private var levelPlayAdInfo: LevelPlayAdInfo? = null
private var levelPlayFilled = false
private var levelPlayLoadDone = false
private var currentWinner: CloudXArbiterPlatform? = null
private val refreshRunnable = Runnable { runArbitrationRound() }
init {
cloudXBanner.stopAutoRefresh()
cloudXBanner.listener = createCloudXListener()
cloudXBanner.revenueListener = createCloudXRevenueListener()
levelPlayBanner.bannerListener = createLevelPlayListener()
}
/** 启动第一轮:为尚未持有填充广告的每个网络发起加载。 */
fun start() {
loadMissingAds()
}
fun stop() {
handler.removeCallbacks(refreshRunnable)
}
private fun loadMissingAds() {
if (!cloudXFilled) cloudXBanner.load()
if (!levelPlayFilled) levelPlayBanner.loadAd()
}
/** 仅在两个网络都已结束加载(无论填充还是失败)时运行;只针对待处理的候选项重新仲裁。 */
private fun maybeArbitrate() {
if (!cloudXLoadDone || !levelPlayLoadDone) return
val bids = listOfNotNull(
cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
levelPlayAdInfo?.let { info ->
CloudXArbiterBid.levelPlay(
networkName = info.adNetwork,
revenue = info.revenue,
precision = info.precision,
)
},
)
if (bids.isEmpty()) {
loadMissingAds()
return
}
CloudX.arbiter(
configuration = CloudXArbiterConfiguration.builder(bids).build(),
listener = object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
showWinner(result.platform)
}
}
)
}
private fun runArbitrationRound() {
maybeArbitrate()
}
/** 只附加获胜视图;落选视图永远不会被添加到 [container] 中。 */
private fun showWinner(platform: CloudXArbiterPlatform) {
currentWinner = platform
container.removeAllViews()
when (platform) {
CloudXArbiterPlatform.CLOUDX -> container.addView(cloudXBanner)
CloudXArbiterPlatform.LEVELPLAY -> container.addView(levelPlayBanner)
else -> return // CloudXArbiterPlatform.NONE——无获胜平台;container 保持为空
}
handler.removeCallbacks(refreshRunnable)
handler.postDelayed(refreshRunnable, refreshIntervalMs)
}
/** 获胜网络的展示一旦触发,立即启动其下一次加载。 */
private fun onWinnerImpression(platform: CloudXArbiterPlatform) {
when (platform) {
CloudXArbiterPlatform.CLOUDX -> {
cloudXFilled = false
cloudXLoadDone = false
cloudXBanner.load()
}
CloudXArbiterPlatform.LEVELPLAY -> {
levelPlayFilled = false
levelPlayLoadDone = false
levelPlayBanner.loadAd()
}
else -> Unit
}
}
private fun createCloudXListener() = object : CloudXAdViewListener {
override fun onAdLoaded(cloudXAd: CloudXAd) {
this@ArbiterBannerController.cloudXAd = cloudXAd
cloudXFilled = true
cloudXLoadDone = true
maybeArbitrate()
}
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
cloudXAd = null
cloudXFilled = false
cloudXLoadDone = true
maybeArbitrate()
}
override fun onAdClicked(cloudXAd: CloudXAd) = Unit
override fun onAdExpanded(cloudXAd: CloudXAd) = Unit
override fun onAdCollapsed(cloudXAd: CloudXAd) = Unit
}
private fun createCloudXRevenueListener() = object : CloudXAdRevenueListener {
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
// onAdRevenuePaid 在 CloudX 展示发生时触发;仅当 CloudX 正在展示时才响应。
if (currentWinner == CloudXArbiterPlatform.CLOUDX) {
onWinnerImpression(CloudXArbiterPlatform.CLOUDX)
}
}
}
private fun createLevelPlayListener() = object : LevelPlayBannerAdViewListener {
override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
this@ArbiterBannerController.levelPlayAdInfo = levelPlayAdInfo
levelPlayFilled = true
levelPlayLoadDone = true
maybeArbitrate()
}
override fun onAdLoadFailed(levelPlayAdError: LevelPlayAdError) {
levelPlayAdInfo = null
levelPlayFilled = false
levelPlayLoadDone = true
maybeArbitrate()
}
override fun onAdDisplayed(levelPlayAdInfo: LevelPlayAdInfo) {
if (currentWinner == CloudXArbiterPlatform.LEVELPLAY) {
onWinnerImpression(CloudXArbiterPlatform.LEVELPLAY)
}
}
override fun onAdClicked(levelPlayAdInfo: LevelPlayAdInfo) = Unit
}
}onAdRevenuePaid()(CloudX)和 onAdDisplayed()(LevelPlay)分别是各自网络展示发生的位置;获胜网络的下一次加载正是在这里启动,从而在不影响当前展示的情况下为下一轮准备好新的填充广告。已经持有已填充、未附加广告的网络会直接跳过 loadMissingAds(),直到该填充被使用或过期。
自定义出价输入
当需要让 Trusted Arbiter 比较 CloudX 与没有专用出价辅助方法的第三方平台时,可以使用 CloudXArbiterBid.custom()。
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 描述该收益值的精度。