Flutter SDK - 广告收入跟踪

文档

广告收入归属

跟踪来自中介平台的广告收入,并将其归因于为您的应用程序带来用户的特定营销活动,从而实现全面的投资回报率分析。

广告收入归属将移动应用广告收入与产生用户的营销活动联系起来,在统一的报告中将营销活动成本、应用内收入和广告收入结合起来。这些数据还可回流至广告网络,以优化营销活动的效果。

了解更多信息:有关归因方法和支持的中介平台的详细信息,请参阅Singular Ad Revenue Attribution FAQ


广告收入归因如何工作

归因流程

您的中介平台通过回调报告印象级或用户级收入数据,您对这些数据进行验证并转发给 Singular 进行归因分析。

  • 活动归因:将广告收入与获取每个用户的特定营销活动联系起来,显示每个营销活动的真实投资回报率。
  • 数据来源:收入数据来自您的调解平台,粒度为用户级或印象级
  • 网络优化:Singular 将收入数据反馈给广告网络,以改进定位和竞价策略

实施要求

在实施广告收入跟踪之前确保数据的准确性,因为错误的收入数据在传输后无法更正。

关键要求:

  • 货币代码:使用三个字母的 ISO 4217 代码(美元、欧元、印度卢比)。大多数调解平台默认使用美元
  • 数据验证:在发送到 Singular 之前,始终验证收入值为正数,货币代码为非空。
  • 单位转换:有些平台以微米(1,000,000 = 1.00 美元)为单位报告收入。在发送到 Singular 之前,请转换为美元。

设置步骤

按照以下步骤在您的中介平台上实施广告收入归属。

  1. 更新 SDK:确保您运行的是最新版本的 Singular Flutter SDK。
  2. 配置平台:在您的中介平台仪表板(AdMob、AppLovin 等)上启用广告收入报告。
  3. 实施回调:从您的中介 SDK 添加收入事件监听器,以捕获印象数据
  4. 验证数据:在转发给 Singular 之前,检查收入是否大于 0,货币是否有效
  5. 测试集成:验证收入数据在 24 小时内出现在 Singular 报告中

AdMob 集成

前提条件

在与 Singular 集成之前,在您的 AdMob 账户中启用广告收入报告,并为 Flutter 实施谷歌移动广告 SDK。

  • AdMob 设置:在您的 AdMob 面板中启用印象级广告收入。请参见AdMob 支持
  • Flutter 软件包:安装 google_mobile_ads 软件包。请参见入门指南

平台差异:AdMob 按平台报告的收入不同。Android 返回的收入单位是微米(5000 = 0.005 美元),而 iOS 返回的是十进制值(0.005 = 0.005 美元)。将 Android 值除以 1,000,000 转换后再发送到 Singular。


实施步骤

在加载广告时设置onPaidEvent 回调,以便从成功的广告展示中获取收入数据。

  1. 加载广告:创建并加载广告单元(奖励式、插播式、横幅广告等)
  2. 设置回调:指定onPaidEvent 回调,以便在广告产生收入时捕获AdValue
  3. 转换单位:valueMicros 除以 1,000,000 转换为美元
  4. 验证:检查收入是否大于 0,货币是否为空
  5. 发送至 Singular:使用验证数据调用Singular.adRevenue()

奖励广告示例

通过在广告加载完成后设置onPaidEvent 回调,获取奖励视频广告的广告收入。

Dart
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 模式跟踪来自插播广告的收入。

Dart
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 中显示的横幅广告的收入。

Dart
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 监听器,以便在印象产生收益时获取收入。

  1. 初始化MAX:使用您的SDK密钥配置AppLovin MAX SDK
  2. 设置监听器:为每种广告格式添加onAdRevenuePaidCallback
  3. 提取收入:ad.revenue 属性获取收入
  4. 货币处理:AppLovin 通常以美元报告,但请与ad.currency验证。
  5. 转发至 Singular:将验证后的数据发送至Singular.adRevenue()

完整的 MAX 实施

使用统一的处理程序获取所有 MAX 广告格式(奖励、插播、横幅、MREC)的收入。

Dart
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 中介获取印象级收入数据。

Dart
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 广告格式中获取收入数据。

Dart
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 广告收入方法实施自定义收入处理程序。

Dart
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() 之前,请务必验证数据的准确性。