广告收入归属
跟踪来自中介平台的广告收入,并将其归因于为您的应用程序带来用户的特定营销活动,从而实现全面的投资回报率分析。
广告收入归属将移动应用广告收入与产生用户的营销活动联系起来,在统一的报告中将营销活动成本、应用内收入和广告收入结合起来。这些数据还可回流至广告网络,以优化营销活动的效果。
了解更多信息:有关归因方法和支持的中介平台的详细信息,请参阅Singular Ad Revenue Attribution FAQ。
广告收入归因如何工作
归因流程
您的中介平台通过回调报告印象级或用户级收入数据,您对这些数据进行验证并转发给 Singular 进行归因分析。
- 活动归因:将广告收入与获取每个用户的特定营销活动联系起来,显示每个营销活动的真实投资回报率。
- 数据来源:收入数据来自您的调解平台,粒度为用户级或印象级
- 网络优化:Singular 将收入数据反馈给广告网络,以改进定位和竞价策略
实施要求
在实施广告收入跟踪之前确保数据的准确性,因为错误的收入数据在传输后无法更正。
关键要求:
- 货币代码:使用三个字母的 ISO 4217 代码(美元、欧元、印度卢比)。大多数调解平台默认使用美元
- 数据验证:在发送到 Singular 之前,始终验证收入值为正数,货币代码为非空。
- 单位转换:有些平台以微米(1,000,000 = 1.00 美元)为单位报告收入。在发送到 Singular 之前,请转换为美元。
设置步骤
按照以下步骤在您的中介平台上实施广告收入归属。
- 更新 SDK:确保您运行的是最新版本的 Singular Flutter SDK。
- 配置平台:在您的中介平台仪表板(AdMob、AppLovin 等)上启用广告收入报告。
- 实施回调:从您的中介 SDK 添加收入事件监听器,以捕获印象数据
- 验证数据:在转发给 Singular 之前,检查收入是否大于 0,货币是否有效
- 测试集成:验证收入数据在 24 小时内出现在 Singular 报告中
AdMob 集成
前提条件
在与 Singular 集成之前,在您的 AdMob 账户中启用广告收入报告,并为 Flutter 实施谷歌移动广告 SDK。
平台差异:AdMob 按平台报告的收入不同。Android 返回的收入单位是微米(5000 = 0.005 美元),而 iOS 返回的是十进制值(0.005 = 0.005 美元)。将 Android 值除以 1,000,000 转换后再发送到 Singular。
实施步骤
在加载广告时设置onPaidEvent 回调,以便从成功的广告展示中获取收入数据。
- 加载广告:创建并加载广告单元(奖励式、插播式、横幅广告等)
-
设置回调:指定
onPaidEvent回调,以便在广告产生收入时捕获AdValue -
转换单位:将
valueMicros除以 1,000,000 转换为美元 - 验证:检查收入是否大于 0,货币是否为空
-
发送至 Singular:使用验证数据调用
Singular.adRevenue()
奖励广告示例
通过在广告加载完成后设置onPaidEvent 回调,获取奖励视频广告的广告收入。
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:singular_flutter_sdk/singular.dart';
const String adUnitId = 'YOUR_AD_UNIT_ID';
class AdManager {
RewardedAd? _rewardedAd;
void loadRewardedAd() {
RewardedAd.load(
adUnitId: adUnitId,
request: AdRequest(),
rewardedAdLoadCallback: RewardedAdLoadCallback(
onAdLoaded: (RewardedAd ad) {
_rewardedAd = ad;
print('Rewarded ad loaded successfully');
// Set up full screen content callback
_rewardedAd?.fullScreenContentCallback = FullScreenContentCallback(
onAdShowedFullScreenContent: () {
print('Rewarded ad displayed');
},
onAdFailedToShowFullScreenContent: (AdError adError) {
print('Rewarded ad failed to show: ${adError.message}');
},
onAdDismissedFullScreenContent: () {
print('Rewarded ad dismissed');
_rewardedAd = null;
},
);
// Set up paid event callback for revenue tracking
_rewardedAd?.onPaidEvent = (AdValue adValue) {
// Convert revenue from micros to dollars
double revenue = adValue.valueMicros / 1_000_000.0;
String? currency = adValue.currencyCode;
// Validate revenue and currency before sending
if (revenue > 0 && currency != null && currency.isNotEmpty) {
final adData = {
'adPlatform': 'AdMob',
'currency': currency,
'revenue': revenue,
};
// Send ad revenue data to Singular
Singular.adRevenue(adData);
// Log for debugging
print('Ad Revenue reported: $revenue $currency');
} else {
print('Invalid ad revenue: revenue=$revenue, currency=$currency');
}
};
},
onAdFailedToLoad: (LoadAdError loadAdError) {
print('Rewarded ad failed to load: ${loadAdError.message}');
},
),
);
}
void showRewardedAd() {
if (_rewardedAd != null) {
_rewardedAd!.show(
onUserEarnedReward: (AdWithoutView ad, RewardItem reward) {
print('User earned reward: ${reward.amount} ${reward.type}');
},
);
} else {
print('Rewarded ad not ready');
}
}
}
插播广告示例
使用相同的onPaidEvent 模式跟踪来自插播广告的收入。
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:singular_flutter_sdk/singular.dart';
const String interstitialAdUnitId = 'YOUR_INTERSTITIAL_AD_UNIT_ID';
class InterstitialAdManager {
InterstitialAd? _interstitialAd;
void loadInterstitialAd() {
InterstitialAd.load(
adUnitId: interstitialAdUnitId,
request: AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: (InterstitialAd ad) {
_interstitialAd = ad;
print('Interstitial ad loaded');
// Set paid event callback
_interstitialAd?.onPaidEvent = (AdValue adValue) {
double revenue = adValue.valueMicros / 1_000_000.0;
String? currency = adValue.currencyCode;
if (revenue > 0 && currency != null && currency.isNotEmpty) {
Singular.adRevenue({
'adPlatform': 'AdMob',
'currency': currency,
'revenue': revenue,
});
print('Interstitial revenue: $revenue $currency');
}
};
// Set full screen callback
_interstitialAd?.fullScreenContentCallback = FullScreenContentCallback(
onAdDismissedFullScreenContent: () {
_interstitialAd = null;
loadInterstitialAd(); // Load next ad
},
);
},
onAdFailedToLoad: (LoadAdError error) {
print('Interstitial ad failed to load: ${error.message}');
},
),
);
}
void showInterstitialAd() {
_interstitialAd?.show();
}
}
横幅广告示例
跟踪来自 Flutter UI 中显示的横幅广告的收入。
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:singular_flutter_sdk/singular.dart';
const String bannerAdUnitId = 'YOUR_BANNER_AD_UNIT_ID';
class BannerAdWidget extends StatefulWidget {
@override
_BannerAdWidgetState createState() => _BannerAdWidgetState();
}
class _BannerAdWidgetState extends State<BannerAdWidget> {
BannerAd? _bannerAd;
bool _isBannerAdReady = false;
@override
void initState() {
super.initState();
_loadBannerAd();
}
void _loadBannerAd() {
_bannerAd = BannerAd(
adUnitId: bannerAdUnitId,
size: AdSize.banner,
request: AdRequest(),
listener: BannerAdListener(
onAdLoaded: (Ad ad) {
setState(() {
_isBannerAdReady = true;
});
print('Banner ad loaded');
// Set paid event callback
(ad as BannerAd).onPaidEvent = (AdValue adValue) {
double revenue = adValue.valueMicros / 1_000_000.0;
String? currency = adValue.currencyCode;
if (revenue > 0 && currency != null && currency.isNotEmpty) {
Singular.adRevenue({
'adPlatform': 'AdMob',
'currency': currency,
'revenue': revenue,
});
print('Banner revenue: $revenue $currency');
}
};
},
onAdFailedToLoad: (Ad ad, LoadAdError error) {
ad.dispose();
print('Banner ad failed to load: ${error.message}');
},
),
);
_bannerAd?.load();
}
@override
void dispose() {
_bannerAd?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_isBannerAdReady && _bannerAd != null) {
return Container(
alignment: Alignment.center,
width: _bannerAd!.size.width.toDouble(),
height: _bannerAd!.size.height.toDouble(),
child: AdWidget(ad: _bannerAd!),
);
}
return SizedBox.shrink();
}
}
AppLovin MAX 集成
前提条件
在与 Singular 集成之前,实施 AppLovin MAX Flutter 插件并配置印象级收入跟踪。
- MAX 设置:在 AppLovin MAX 面板中配置您的应用程序
- Flutter 插件:安装 applovin_max 软件包。请参阅入门指南
- 收入 API:在 MAX 面板设置中启用印象级用户收入 API
实施概述
为每种广告格式设置onAdRevenuePaidCallback 监听器,以便在印象产生收益时获取收入。
- 初始化MAX:使用您的SDK密钥配置AppLovin MAX SDK
-
设置监听器:为每种广告格式添加
onAdRevenuePaidCallback -
提取收入:从
ad.revenue属性获取收入 -
货币处理:AppLovin 通常以美元报告,但请与
ad.currency验证。 -
转发至 Singular:将验证后的数据发送至
Singular.adRevenue()
完整的 MAX 实施
使用统一的处理程序获取所有 MAX 广告格式(奖励、插播、横幅、MREC)的收入。
import 'package:flutter/material.dart';
import 'package:applovin_max/applovin_max.dart';
import 'package:singular_flutter_sdk/singular.dart';
class AppLovinMaxManager extends StatefulWidget {
@override
_AppLovinMaxManagerState createState() => _AppLovinMaxManagerState();
}
class _AppLovinMaxManagerState extends State<AppLovinMaxManager> {
@override
void initState() {
super.initState();
_initializeAppLovinMax();
}
Future<void> _initializeAppLovinMax() async {
// Initialize AppLovin MAX SDK
await AppLovinMAX.initialize('YOUR_SDK_KEY');
// Set up revenue listeners for all ad formats
_setupRewardedAdListeners();
_setupInterstitialAdListeners();
_setupBannerAdListeners();
_setupMRecAdListeners();
print('AppLovin MAX initialized with revenue tracking');
}
void _setupRewardedAdListeners() {
AppLovinMAX.setRewardedAdListener(RewardedAdListener(
onAdRevenuePaidCallback: (ad) {
_handleAdRevenuePaid(ad, 'Rewarded');
},
onAdLoadedCallback: (ad) {
print('Rewarded ad loaded');
},
onAdLoadFailedCallback: (adUnitId, error) {
print('Rewarded ad failed to load: $error');
},
));
}
void _setupInterstitialAdListeners() {
AppLovinMAX.setInterstitialListener(InterstitialListener(
onAdRevenuePaidCallback: (ad) {
_handleAdRevenuePaid(ad, 'Interstitial');
},
onAdLoadedCallback: (ad) {
print('Interstitial ad loaded');
},
onAdLoadFailedCallback: (adUnitId, error) {
print('Interstitial ad failed to load: $error');
},
));
}
void _setupBannerAdListeners() {
AppLovinMAX.setBannerListener(AdViewAdListener(
onAdRevenuePaidCallback: (ad) {
_handleAdRevenuePaid(ad, 'Banner');
},
onAdLoadedCallback: (ad) {
print('Banner ad loaded');
},
onAdLoadFailedCallback: (adUnitId, error) {
print('Banner ad failed to load: $error');
},
));
}
void _setupMRecAdListeners() {
AppLovinMAX.setMRecListener(AdViewAdListener(
onAdRevenuePaidCallback: (ad) {
_handleAdRevenuePaid(ad, 'MREC');
},
onAdLoadedCallback: (ad) {
print('MREC ad loaded');
},
onAdLoadFailedCallback: (adUnitId, error) {
print('MREC ad failed to load: $error');
},
));
}
void _handleAdRevenuePaid(Ad ad, String adType) {
// Extract revenue and currency from ad object
final double revenueValue = ad.revenue ?? 0.0;
final String currency = ad.revenueCurrency ?? 'USD';
// Validate revenue before sending
if (revenueValue > 0) {
final adData = {
'adPlatform': 'AppLovin',
'currency': currency,
'revenue': revenueValue,
};
// Send ad revenue to Singular
Singular.adRevenue(adData);
// Log for debugging
print('[$adType] Revenue: $revenueValue $currency');
} else {
print('[$adType] Invalid revenue: $revenueValue');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AppLovin MAX Revenue Tracking'),
),
body: Center(
child: Text('MAX revenue tracking active'),
),
);
}
}
最佳实践:在加载任何广告之前,在应用程序初始化过程中设置广告收入监听器,以确保不会错过任何印象。
IronSource(Unity LevelPlay)集成
前提条件
在与 Singular 集成之前,在 IronSource 面板中启用 "印象级收入"(ILR)并实施 IronSource Flutter 插件。
- IronSource 设置:在 IronSource 面板中启用 ARM SDK Postbacks 标志
- Flutter 插件:安装 ironsource_mediation 软件包。请参见入门指南
- ILR API:为您的应用程序配置印象级收入跟踪
实施示例
使用onImpressionDataSuccess 回调从 IronSource 中介获取印象级收入数据。
import 'package:ironsource_mediation/ironsource_mediation.dart';
import 'package:singular_flutter_sdk/singular.dart';
void setupIronSourceRevenueTracking() {
// Set up impression data listener
IronSource.setImpressionDataListener((ISImpressionData? impressionData) {
onImpressionDataSuccess(impressionData);
});
}
void onImpressionDataSuccess(ISImpressionData? impressionData) {
// Ensure impression data is not null
if (impressionData == null) {
print('No impression data available');
return;
}
// Extract and validate revenue
final revenue = impressionData.revenue?.toDouble() ?? 0.0;
if (revenue <= 0) {
print('Invalid revenue value: $revenue');
return;
}
// Create ad revenue data for Singular
final adData = {
'adPlatform': 'IronSource',
'currency': 'USD',
'revenue': revenue,
};
// Send to Singular
Singular.adRevenue(adData);
// Log for debugging
print('IronSource Revenue: $revenue USD');
}
TradPlus 集成
前提条件
设置 TradPlus 印象委托,以便在广告产生收入时捕获 eCPM 数据。
- TradPlus 设置:在 TradPlus 面板中配置您的应用程序
- 印象委托:在 TradPlus 设置中启用印象级跟踪
eCPM 转换:TradPlus 通常以毫位报告 eCPM。在发送到 Singular 之前,请除以 1000.0 转换为美元。
实施示例
使用全局印象监听器从所有 TradPlus 广告格式中获取收入数据。
import 'package:singular_flutter_sdk/singular.dart';
void setupTradPlusImpressionListener() {
// Set up global impression listener
TradPlusSdk.setGlobalImpressionListener((tpAdInfo) {
if (tpAdInfo == null) {
print('AdInfo is null');
return;
}
// Ensure eCPM is available
if (tpAdInfo.ecpm == null) {
print('eCPM value is null');
return;
}
// Convert eCPM from milli-units to dollars
double revenue = tpAdInfo.ecpm! / 1000.0;
// Validate revenue
if (revenue <= 0) {
print('Invalid revenue: $revenue');
return;
}
// Create ad revenue data
final adData = {
'adPlatform': 'TradPlus',
'currency': 'USD',
'revenue': revenue,
};
// Send to Singular
Singular.adRevenue(adData);
// Log for debugging
print('TradPlus Revenue: $revenue USD');
});
}
void main() {
// Initialize TradPlus SDK
setupTradPlusImpressionListener();
runApp(MyApp());
}
通用集成(其他平台)
自定义实施
对于上述未明确涵盖的中介平台,使用 Singular 广告收入方法实施自定义收入处理程序。
import 'package:singular_flutter_sdk/singular.dart';
/// Generic function to report ad revenue to Singular
/// Use this for any mediation platform not explicitly supported above
void reportAdRevenue({
required String adPlatform,
required String currency,
required double revenue,
}) {
// Validate revenue value
if (revenue <= 0) {
print('Invalid revenue value: $revenue');
return;
}
// Validate currency code
if (currency.isEmpty) {
print('Currency code is empty');
return;
}
// Create ad revenue data
final adData = {
'adPlatform': adPlatform,
'currency': currency,
'revenue': revenue,
};
// Send to Singular
Singular.adRevenue(adData);
// Log for debugging
print('Ad Revenue reported: $revenue $currency from $adPlatform');
}
// Example usage with a custom mediation platform
void onCustomAdImpression(Map<String, dynamic> impressionData) {
reportAdRevenue(
adPlatform: 'CustomNetwork',
currency: impressionData['currency'] ?? 'USD',
revenue: impressionData['revenue'] ?? 0.0,
);
}
最佳实践和故障排除
数据验证
- 始终验证:在发送到 Singular 之前,检查收入是否大于 0,货币是否为空。
- 单位转换:验证您的平台是以微元还是美元报告,并进行相应转换
- 货币代码:始终使用 ISO 4217 三字母代码(美元、欧元、日元
- 彻底测试:在生产发布之前,验证收入数据是否出现在 Singular 报告中
常见问题
- 收入缺失:确保在调解平台仪表板中启用了印象级收入
- 数值不正确:检查特定平台的单位转换(微元与美元)。
- 报告中没有数据:实施后,Singular 报告中需要 24-48 小时才能显示数据。
- 收入为空:验证广告回调是否在广告加载完成前正确设置
重要:广告收入数据在传输后无法更正。在调用Singular.adRevenue() 之前,请务必验证数据的准确性。