广告收入归因
追踪来自聚合(mediation)平台的广告收入,并将其归因到获取用户的营销活动,从而在活动成本、应用内购买和广告变现方面提供完整的 ROI 可见性。
概述
什么是广告收入归因
广告收入归因将移动应用的广告收入与推动应用安装的用户获取活动相关联,使您能够衡量包含广告变现在内的真实活动盈利能力。
主要优势:
- 统一的 ROI 视图: 在单个仪表板中查看活动成本、应用内收入和广告收入
- 活动优化: 将广告收入数据回传到广告网络,以改进出价和定向
- LTV 衡量: 计算包含广告变现的完整用户生命周期价值
数据源: 广告收入数据通常来自您的聚合平台(例如 AdMob、AppLovin MAX、IronSource),粒度可为用户级或展示级。Singular 支持多种集成方式来接收这些数据。
了解更多: 有关设置、报告和故障排查的详细信息,请参阅 广告收入归因常见问题 。
实现要求
关键准则
SingularAdData 必填参数:
构造函数参数
adPlatform
、
currency
和
revenue
均为必填项。如果其中任何一个缺失、为空或无效,SDK 将在不抛出异常或返回错误的情况下静默丢弃对
Singular.adRevenue()
的调用。
Singular.adRevenue()
在 SDK 尚未初始化时也会静默不执行任何操作 — 调用前请始终确认
Singular.init()
已运行。
数据精度至关重要:
- 货币代码: 使用三位字母的 ISO 4217 货币代码(例如 USD、EUR、INR)。许多聚合平台以 USD 上报 — 在实施前请核实您平台的货币。
-
发送前验证:
在调用
Singular.adRevenue()之前,始终验证收入和货币数据。错误的数据在提交后无法更正。 - 平台差异: 部分 SDK 以 micros 单位上报收入(需除以 1,000,000),而其他 SDK 则以标准货币单位上报。请查阅您平台的文档。
设置步骤
按照以下步骤实现广告收入归因:
- 更新 SDK: 确保您使用的是最新版本的 Singular SDK。
- 选择集成方式: 在下方选择与您配置匹配的聚合平台集成。
- 实现回调: 添加平台特定的付费事件监听器以捕获收入数据。
- 验证数据: 测试收入上报,并确认数据出现在 Singular 仪表板中。
平台集成
AdMob 集成
使用付费事件监听器追踪来自 Google AdMob 的广告收入,实现展示级的收入上报。
要求:
按平台的收入上报:
AdMob 在不同平台上报告收入的方式不同。Android 以 micros 单位返回收入(例如 $0.005 = 5000)。在发送给 Singular 之前,请将
adValue.valueMicros
除以 1,000,000 以转换为标准货币单位。
实现
在加载广告时设置付费事件监听器以捕获收入数据并发送给 Singular。
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.rewarded.RewardedAd
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback
private const val AD_UNIT_ID = "YOUR_AD_UNIT_ID"
class AdManager(private val context: Context) {
private var rewardedAd: RewardedAd? = null
fun loadRewardedAd() {
val adRequest = AdRequest.Builder().build()
RewardedAd.load(context, AD_UNIT_ID, adRequest,
object : RewardedAdLoadCallback() {
override fun onAdLoaded(ad: RewardedAd) {
rewardedAd = ad
// Set full screen content callback
rewardedAd?.fullScreenContentCallback =
object : FullScreenContentCallback() {
override fun onAdShowedFullScreenContent() {
Log.d("AdManager", "Rewarded ad displayed")
}
override fun onAdDismissedFullScreenContent() {
Log.d("AdManager", "Rewarded ad dismissed")
}
}
// Set paid event listener for revenue tracking
rewardedAd?.setOnPaidEventListener { adValue ->
// Convert revenue from micros to standard units
val revenue = adValue.valueMicros / 1_000_000.0
val currency = adValue.currencyCode
// Validate revenue data before sending
if (revenue > 0 && !currency.isNullOrEmpty()) {
val data = SingularAdData(
"AdMob",
currency,
revenue
)
Singular.adRevenue(data)
Log.d("AdManager", "Ad revenue sent: $revenue $currency")
} else {
Log.w("AdManager", "Invalid revenue: $revenue $currency")
}
}
}
override fun onAdFailedToLoad(error: LoadAdError) {
Log.e("AdManager", "Ad failed to load: ${error.message}")
}
})
}
}
import com.singular.sdk.Singular;
import com.singular.sdk.SingularAdData;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.rewarded.RewardedAd;
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback;
private static final String AD_UNIT_ID = "YOUR_AD_UNIT_ID";
public class AdManager {
private RewardedAd rewardedAd;
private Context context;
public AdManager(Context context) {
this.context = context;
}
public void loadRewardedAd() {
AdRequest adRequest = new AdRequest.Builder().build();
RewardedAd.load(context, AD_UNIT_ID, adRequest,
new RewardedAdLoadCallback() {
@Override
public void onAdLoaded(RewardedAd ad) {
rewardedAd = ad;
// Set full screen content callback
rewardedAd.setFullScreenContentCallback(
new FullScreenContentCallback() {
@Override
public void onAdShowedFullScreenContent() {
Log.d("AdManager", "Rewarded ad displayed");
}
@Override
public void onAdDismissedFullScreenContent() {
Log.d("AdManager", "Rewarded ad dismissed");
}
});
// Set paid event listener for revenue tracking
rewardedAd.setOnPaidEventListener(new OnPaidEventListener() {
@Override
public void onPaidEvent(AdValue adValue) {
// Convert revenue from micros to standard units
double revenue = adValue.getValueMicros() / 1_000_000.0;
String currency = adValue.getCurrencyCode();
// Validate revenue data before sending
if (revenue > 0 && currency != null && !currency.isEmpty()) {
SingularAdData data = new SingularAdData(
"AdMob",
currency,
revenue
);
Singular.adRevenue(data);
Log.d("AdManager", "Ad revenue sent: " + revenue + " " + currency);
} else {
Log.w("AdManager", "Invalid revenue: " + revenue + " " + currency);
}
}
});
}
@Override
public void onAdFailedToLoad(LoadAdError error) {
Log.e("AdManager", "Ad failed to load: " + error.getMessage());
}
});
}
}
AppLovin MAX 集成
使用 AppLovin Impression-Level User Revenue API 共享展示级的广告收入。
要求:
- 实现 AppLovin MAX SDK(请参阅 Impression-Level Revenue API 指南 )
-
通过 AppLovin Communicator 订阅
max_revenue_events主题
实现
通过 AppLovin Communicator 回调处理收入消息。
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.applovin.communicator.AppLovinCommunicatorMessage
override fun onMessageReceived(message: AppLovinCommunicatorMessage) {
// Check for revenue events topic
if (message.topic == "max_revenue_events") {
val adData: Bundle? = message.messageData
// Extract and validate revenue value
val revenueValue = adData?.getDouble("revenue", 0.0) ?: 0.0
if (revenueValue > 0) {
val data = SingularAdData(
"AppLovin",
"USD", // AppLovin typically reports in USD
revenueValue
)
Singular.adRevenue(data)
Log.d("AppLovin", "Ad revenue sent: $revenueValue USD")
} else {
Log.w("AppLovin", "Invalid revenue value: $revenueValue")
}
}
}
import com.singular.sdk.Singular;
import com.singular.sdk.SingularAdData;
import com.applovin.communicator.AppLovinCommunicatorMessage;
@Override
public void onMessageReceived(AppLovinCommunicatorMessage message) {
// Check for revenue events topic
if ("max_revenue_events".equals(message.getTopic())) {
Bundle adData = message.getMessageData();
// Extract and validate revenue value
double revenueValue = (adData != null) ?
adData.getDouble("revenue", 0.0) : 0.0;
if (revenueValue > 0) {
SingularAdData data = new SingularAdData(
"AppLovin",
"USD", // AppLovin typically reports in USD
revenueValue
);
Singular.adRevenue(data);
Log.d("AppLovin", "Ad revenue sent: " + revenueValue + " USD");
} else {
Log.w("AppLovin", "Invalid revenue value: " + revenueValue);
}
}
}
Unity LevelPlay (IronSource) 集成
使用 IronSource SDK 追踪来自 ironSource 及聚合网络的展示级收入。
要求:
- 实现 ironSource SDK(请参阅 入门指南 )
- 在您的 IronSource 仪表板中启用 ARM SDK Postbacks 选项
- 设置展示数据监听器以接收收入回调
了解更多: 有关完整的设置详情,请参阅 IronSource 广告收入文档 。
实现
实现展示数据成功回调以捕获并发送收入。
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.ironsource.mediationsdk.impressionData.ImpressionData
fun onImpressionDataSuccess(impressionData: ImpressionData?) {
// Validate impression data
if (impressionData == null) {
Log.d("IronSource", "No impression data available")
return
}
// Extract and validate revenue
val revenue = impressionData.revenue?.toDouble() ?: 0.0
if (revenue <= 0) {
Log.w("IronSource", "Invalid revenue: $revenue")
return
}
// Create and send ad revenue data
val data = SingularAdData(
"IronSource",
"USD", // IronSource typically reports in USD
revenue
)
Singular.adRevenue(data)
Log.d("IronSource", "Ad revenue sent: $revenue USD")
}
import com.singular.sdk.Singular;
import com.singular.sdk.SingularAdData;
import com.ironsource.mediationsdk.impressionData.ImpressionData;
public void onImpressionDataSuccess(ImpressionData impressionData) {
// Validate impression data
if (impressionData == null) {
Log.d("IronSource", "No impression data available");
return;
}
// Extract and validate revenue
double revenue = (impressionData.getRevenue() != null) ?
impressionData.getRevenue().doubleValue() : 0.0;
if (revenue <= 0) {
Log.w("IronSource", "Invalid revenue: " + revenue);
return;
}
// Create and send ad revenue data
SingularAdData data = new SingularAdData(
"IronSource",
"USD", // IronSource typically reports in USD
revenue
);
Singular.adRevenue(data);
Log.d("IronSource", "Ad revenue sent: " + revenue + " USD");
}
TradPlus 集成
使用全局展示监听器捕获来自 TradPlus 聚合的广告收入。
要求:
-
通过
TradPlusSdk.setGlobalImpressionListener()设置全局展示监听器 -
处理
onImpressionSuccess回调以接收收入数据 - 将 eCPM 从毫单位转换为标准货币(除以 1000)
实现
注册全局展示监听器以追踪所有广告展示和收入。
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.tradplus.ads.mgr.GlobalImpressionManager
import com.tradplus.ads.base.bean.TPAdInfo
// Set global impression listener
TradPlusSdk.setGlobalImpressionListener(
object : GlobalImpressionManager.GlobalImpressionListener {
override fun onImpressionSuccess(tpAdInfo: TPAdInfo?) {
// Validate ad info
if (tpAdInfo == null) {
Log.w("TradPlus", "AdInfo is null")
return
}
// Convert eCPM from milli-units to dollars
val ecpm = tpAdInfo.ecpm ?: run {
Log.w("TradPlus", "eCPM is null")
return
}
val revenue = ecpm.toDouble() / 1000.0
// Validate revenue
if (revenue <= 0) {
Log.w("TradPlus", "Invalid revenue: $revenue")
return
}
// Create and send ad revenue data
val data = SingularAdData(
"TradPlus",
"USD",
revenue
)
Singular.adRevenue(data)
Log.d("TradPlus", "Ad revenue sent: $revenue USD")
}
}
)
import com.singular.sdk.Singular;
import com.singular.sdk.SingularAdData;
import com.tradplus.ads.mgr.GlobalImpressionManager;
import com.tradplus.ads.base.bean.TPAdInfo;
// Set global impression listener
TradPlusSdk.setGlobalImpressionListener(
new GlobalImpressionManager.GlobalImpressionListener() {
@Override
public void onImpressionSuccess(TPAdInfo tpAdInfo) {
// Validate ad info
if (tpAdInfo == null) {
Log.w("TradPlus", "AdInfo is null");
return;
}
// Validate eCPM is not null
if (tpAdInfo.getEcpm() == null) {
Log.w("TradPlus", "eCPM is null");
return;
}
// Convert eCPM from milli-units to dollars
double revenue = tpAdInfo.getEcpm().doubleValue() / 1000.0;
// Validate revenue
if (revenue <= 0) {
Log.w("TradPlus", "Invalid revenue: " + revenue);
return;
}
// Create and send ad revenue data
SingularAdData data = new SingularAdData(
"TradPlus",
"USD",
revenue
);
Singular.adRevenue(data);
Log.d("TradPlus", "Ad revenue sent: " + revenue + " USD");
}
}
);
通用集成 (其他平台)
使用通用
SingularAdData
接口集成任意聚合平台。
要求:
- 可以访问来自您聚合平台的展示级收入数据
- 收入金额采用标准货币单位(非 micros)
- ISO 4217 货币代码(例如 USD、EUR、INR)
数据精度: 在发送给 Singular 之前,请验证收入和货币数据。错误的数据在提交后无法更正。
实现
使用平台名称、货币和收入创建
SingularAdData
对象,然后调用
Singular.adRevenue()
。
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
fun reportAdRevenue(adPlatform: String, currency: String, revenue: Double) {
// Validate revenue
if (revenue <= 0) {
Log.w("AdRevenue", "Invalid revenue: $revenue")
return
}
// Validate currency
if (currency.isEmpty()) {
Log.w("AdRevenue", "Invalid currency: $currency")
return
}
// Create ad revenue data object
val data = SingularAdData(
adPlatform,
currency,
revenue
)
// Send to Singular
Singular.adRevenue(data)
Log.d("AdRevenue", "Revenue sent: $revenue $currency from $adPlatform")
}
// Example usage
reportAdRevenue("MyMediationPlatform", "USD", 0.05)
import com.singular.sdk.Singular;
import com.singular.sdk.SingularAdData;
public void reportAdRevenue(String adPlatform, String currency, double revenue) {
// Validate revenue
if (revenue <= 0) {
Log.w("AdRevenue", "Invalid revenue: " + revenue);
return;
}
// Validate currency
if (currency == null || currency.isEmpty()) {
Log.w("AdRevenue", "Invalid currency: " + currency);
return;
}
// Create ad revenue data object
SingularAdData data = new SingularAdData(
adPlatform,
currency,
revenue
);
// Send to Singular
Singular.adRevenue(data);
Log.d("AdRevenue", "Revenue sent: " + revenue + " " + currency +
" from " + adPlatform);
}
// Example usage
reportAdRevenue("MyMediationPlatform", "USD", 0.05);
使用附加归因丰富广告数据
SingularAdData 提供可链式调用的 setter,用于设置展示级Meta数据。当您的聚合平台提供这些信息时,使用这些方法用版位、广告单Meta、聚合分组和精度等细节丰富事件。通过这些方法设置的归因会出现在 Singular 报告以及下游回传中。
| 方法 | 描述 |
|---|---|
withNetworkName(String networkName)
|
覆盖网络名称(默认值为传递给构造函数的广告平台)。当聚合中底层网络与聚合平台不同时使用。 |
withAdType(String adType)
|
广告形式(例如,
"Rewarded"
、
"Interstitial"
、
"Banner"
)。
|
withAdGroupType(String adGroupType)
|
由您的聚合平台定义的广告组分类。 |
withImpressionId(String impressionId)
|
展示的唯一标识符,用于去重和对账。 |
withAdPlacementName(String adPlacementName)
|
人类可读的版位名称(例如,
"level_complete"
)。
|
withAdUnitId(String adUnitId)
|
平台特定的广告单Meta标识符(例如 AdMob 的
"ca-app-pub-..."
)。
|
withAdUnitName(String adUnitName)
|
人类可读的广告单Meta名称。 |
withAdGroupId(String adGroupId)
|
来自聚合平台的广告组标识符。 |
withAdGroupName(String adGroupName)
|
人类可读的广告组名称。 |
withAdGroupPriority(String adGroupPriority)
|
在聚合瀑布流中分配给广告组的优先级。 |
withPrecision(String precision)
|
当平台支持时的收入精度指标(例如,
"publisher_provided"
、
"estimated"
、
"exact"
)。
|
withPlacementId(String placementId)
|
平台特定的版位标识符。 |
withLimitDataSharing(boolean shouldLimitDataSharing)
|
为这一次广告事件覆盖全局数据共享限制,例如当用户已退出个性化广告时。 |
Kotlin 使用示例:
val data = SingularAdData("AdMob", "USD", 0.05)
.withAdUnitId("ca-app-pub-123456789/1234567890")
.withAdType("Rewarded")
.withAdPlacementName("level_complete")
.withImpressionId("imp_abc123")
.withPrecision("publisher_provided")
Singular.adRevenue(data)
Java 使用示例:
SingularAdData data = new SingularAdData("AdMob", "USD", 0.05)
.withAdUnitId("ca-app-pub-123456789/1234567890")
.withAdType("Rewarded")
.withAdPlacementName("level_complete")
.withImpressionId("imp_abc123")
.withPrecision("publisher_provided");
Singular.adRevenue(data);
测试与验证
验证收入上报
测试您的广告收入实现,以确保数据正确地流入 Singular。
- 检查日志: 验证收入回调日志显示正确的值和货币。
- 测试广告: 加载并展示测试广告以触发收入事件。
- 仪表板验证: 确认收入在 24 小时内出现在 Singular 仪表板中。
- 数据精度: 验证收入金额与您聚合平台的报告一致。
故障排查: 如果 Singular 中未显示收入,请检查:
- 您的 Singular 帐户中已启用广告收入归因
- 收入值大于 0
- 货币代码是有效的 ISO 4217 代码
- 平台名称与 Singular 期望的格式相匹配