高级功能
版本 GroMore

📊 事件处理

GromoreAds 提供了灵活的事件订阅系统,支持全局监听、按广告位过滤和按广告类型分类监听。

1. 基础用法:全局事件监听

使用 onEvent() 监听所有广告事件:

class MyAdPage extends StatefulWidget {
  @override
  State<MyAdPage> createState() => _MyAdPageState();
}

class _MyAdPageState extends State<MyAdPage> {
  AdEventSubscription? _subscription;

  @override
  void initState() {
    super.initState();

    // 订阅全局广告事件
    _subscription = GromoreAds.onEvent(
      onEvent: (event) {
        debugPrint('📌 ${event.action} - ${event.posId}');

        // 处理特定事件
        if (event.action == 'reward_video_completed') {
          debugPrint('✅ 视频播放完成');
        }
      },
      onError: (event) {
        debugPrint('❌ 错误: ${event.message} (${event.code})');
      },
      onReward: (event) {
        if (event.verified) {
          debugPrint('🎁 发放奖励: ${event.rewardAmount}');
          _grantReward(event.rewardAmount);
        }
      },
      onEcpm: (event) {
        debugPrint('💰 eCPM: ${event.ecpm} - ${event.networkName}');
      },
    );
  }

  @override
  void dispose() {
    _subscription?.cancel();  // 取消订阅
    super.dispose();
  }

  void _grantReward(int? amount) {
    // 发放奖励逻辑
  }
}

2. 进阶用法:按广告位过滤

使用 onAdEvents(posId) 只监听特定广告位的事件:

class RewardVideoPage extends StatefulWidget {
  @override
  State<RewardVideoPage> createState() => _RewardVideoPageState();
}

class _RewardVideoPageState extends State<RewardVideoPage> {
  static const String REWARD_POS_ID = 'reward_video_001';
  AdEventSubscription? _subscription;

  @override
  void initState() {
    super.initState();

    // 只监听特定广告位的事件
    _subscription = GromoreAds.onAdEvents(
      REWARD_POS_ID,
      onEvent: (event) {
        // 这里只会收到 reward_video_001 的事件
        debugPrint('事件: ${event.action}');
      },
      onError: (event) {
        debugPrint('加载失败: ${event.message}');
        _showRetryDialog();
      },
    );

    // 加载广告
    GromoreAds.loadRewardVideoAd(REWARD_POS_ID);
  }

  @override
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }

  void _showRetryDialog() {
    // 显示重试对话框
  }
}

3. 便捷方法:按广告类型监听

插件为每种广告类型提供了便捷的监听方法:

3.1 激励视频广告

_subscription = GromoreAds.onRewardVideoEvents(
  'reward_video_001',
  onLoaded: (event) => debugPrint('✅ 加载成功'),
  onShowed: (event) => debugPrint('👀 开始展示'),
  onRewarded: (event) {
    debugPrint('🎁 获得奖励: ${event.rewardAmount}');
    _grantReward(event.rewardAmount);
  },
  onCompleted: (event) => debugPrint('✅ 播放完成'),
  onSkipped: (event) => debugPrint('⏭️ 跳过视频'),
  onClicked: (event) => debugPrint('👆 点击广告'),
  onClosed: (event) => debugPrint('🚪 广告关闭'),
  onError: (event) => debugPrint('❌ ${event.message}'),
);

3.2 开屏广告

_subscription = GromoreAds.onSplashEvents(
  'splash_001',
  onLoadSuccess: (event) => debugPrint('✅ 加载成功'),
  onShow: (event) => debugPrint('👀 开始展示'),
  onClicked: (event) => debugPrint('👆 点击广告'),
  onClosed: (event) {
    debugPrint('🚪 广告关闭');
    _navigateToHomePage();
  },
  onSkip: (event) => debugPrint('⏭️ 跳过广告'),
  onError: (event) => debugPrint('❌ ${event.message}'),
);

3.3 插屏广告

_subscription = GromoreAds.onInterstitialEvents(
  'interstitial_001',
  onLoadSuccess: (event) => debugPrint('✅ 加载成功'),
  onShow: (event) => debugPrint('👀 开始展示'),
  onClicked: (event) => debugPrint('👆 点击广告'),
  onClosed: (event) => debugPrint('🚪 广告关闭'),
  onError: (event) => debugPrint('❌ ${event.message}'),
);

3.4 信息流广告

_subscription = GromoreAds.onFeedEvents(
  'feed_001',
  onLoaded: (event) => debugPrint('✅ 加载成功'),
  onLoadSuccess: (event) {
    final count = event.extra?['count'] ?? 0;
    debugPrint('✅ 批量加载成功: $count个');
  },
  onDestroyed: (event) => debugPrint('🗑️ 广告销毁'),
  onError: (event) => debugPrint('❌ ${event.message}'),
);

3.5 Draw信息流广告

_subscription = GromoreAds.onDrawFeedEvents(
  'draw_feed_001',
  onLoaded: (event) => debugPrint('✅ 加载成功'),
  onLoadSuccess: (event) {
    final count = event.extra?['count'] ?? 0;
    debugPrint('✅ 批量加载成功: $count个');
  },
  onDestroyed: (event) => debugPrint('🗑️ 广告销毁'),
  onError: (event) => debugPrint('❌ ${event.message}'),
);

4. 多订阅支持

新版API支持同时创建多个订阅,不会相互覆盖:

class MultiAdPage extends StatefulWidget {
  @override
  State<MultiAdPage> createState() => _MultiAdPageState();
}

class _MultiAdPageState extends State<MultiAdPage> {
  AdEventSubscription? _rewardSubscription;
  AdEventSubscription? _splashSubscription;
  AdEventSubscription? _globalSubscription;

  @override
  void initState() {
    super.initState();

    // 订阅1:监听激励视频
    _rewardSubscription = GromoreAds.onRewardVideoEvents(
      'reward_001',
      onRewarded: (event) => _grantReward(event.rewardAmount),
    );

    // 订阅2:监听开屏广告
    _splashSubscription = GromoreAds.onSplashEvents(
      'splash_001',
      onClosed: (event) => _navigateToHome(),
    );

    // 订阅3:全局监听用于统计
    _globalSubscription = GromoreAds.onEvent(
      onEvent: (event) => _logEvent(event),
    );
  }

  @override
  void dispose() {
    _rewardSubscription?.cancel();
    _splashSubscription?.cancel();
    _globalSubscription?.cancel();
    super.dispose();
  }
}

💡 提示:每个订阅都是独立的,互不影响。记得在 dispose() 中取消所有订阅。

5. 事件数据结构

类型触发场景关键字段
AdEvent所有事件基类action, posId, timestamp, extra
AdErrorEvent加载/展示失败继承 AdEvent,额外包含 code, message
AdRewardEvent激励验证继承 AdEvent,额外包含 rewardType, rewardAmount, verified
AdEcpmEvent聚合 eCPM继承 AdEvent,额外包含 ecpm, networkName, adnId, channel, subChannel, requestId, ritType, scenarioId, levelTag, biddingType, customData

extra 字段会合并原生透传参数(若存在),可用于读取自定义回调数据。

6. 常见动作一览

广告成功事件失败事件其他关键事件
开屏splash_loaded, splash_showedsplash_load_fail, splash_render_failsplash_clicked, splash_closed
插屏interstitial_loaded, interstitial_showedinterstitial_load_failinterstitial_clicked, interstitial_closed
激励reward_video_loaded, reward_video_showedreward_video_load_failreward_video_rewarded, reward_video_completed
Bannerbanner_loaded, banner_render_successbanner_load_fail, banner_render_failbanner_clicked, banner_destroyed
信息流feed_loaded, feed_showedfeed_load_failfeed_clicked, feed_destroyed
Drawdraw_feed_loaded, draw_feed_showeddraw_feed_load_faildraw_feed_clicked, draw_feed_destroyed

使用 event.actionAdEventAction 常量对比可以更安全地判断事件类型。

7. 实战:统计分析

使用多订阅实现实时广告统计:

class AdAnalytics {
  final Map<String, _AdMetrics> _metrics = {};
  final List<AdEventSubscription> _subscriptions = [];

  void startTracking(List<String> posIds) {
    for (final posId in posIds) {
      // 为每个广告位创建订阅
      final subscription = GromoreAds.onAdEvents(
        posId,
        onEvent: (event) {
          final metrics = _metrics.putIfAbsent(posId, () => _AdMetrics());

          if (event.action.endsWith('_loaded')) {
            metrics.loaded++;
          } else if (event.action.endsWith('_showed')) {
            metrics.shown++;
          } else if (event.action.endsWith('_clicked')) {
            metrics.clicked++;
          }
        },
        onError: (event) {
          final metrics = _metrics.putIfAbsent(posId, () => _AdMetrics());
          metrics.failed++;
        },
      );

      _subscriptions.add(subscription);
    }
  }

  void stopTracking() {
    for (final sub in _subscriptions) {
      sub.cancel();
    }
    _subscriptions.clear();
  }

  Map<String, dynamic> export() {
    return _metrics.map((posId, value) => MapEntry(posId, value.toJson()));
  }
}

class _AdMetrics {
  int loaded = 0;
  int shown = 0;
  int clicked = 0;
  int failed = 0;

  double get ctr => shown > 0 ? (clicked / shown * 100) : 0;
  double get fillRate => (loaded + failed) > 0
      ? (loaded / (loaded + failed) * 100) : 0;

  Map<String, dynamic> toJson() => {
    'loaded': loaded,
    'shown': shown,
    'clicked': clicked,
    'failed': failed,
    'ctr': ctr.toStringAsFixed(2),
    'fillRate': fillRate.toStringAsFixed(2),
  };
}

8. 最佳实践

8.1 订阅管理

class AdManager {
  final List<AdEventSubscription> _subscriptions = [];

  void addSubscription(AdEventSubscription subscription) {
    _subscriptions.add(subscription);
  }

  void cancelAll() {
    for (final sub in _subscriptions) {
      sub.cancel();
    }
    _subscriptions.clear();
  }
}

8.2 错误重试

void _handleError(AdErrorEvent event, String posId) {
  final errorCode = event.code;

  // 网络错误,3秒后重试
  if (errorCode == -1000 || event.message.contains('network')) {
    Future.delayed(Duration(seconds: 3), () {
      GromoreAds.loadRewardVideoAd(posId);
    });
  }

  // 广告位无填充,停止加载
  else if (errorCode == -2000) {
    debugPrint('广告位无填充,停止加载');
  }
}

8.3 奖励验证

void _handleReward(AdRewardEvent event) {
  // 仅在服务端验证通过时发放奖励
  if (!event.verified) {
    debugPrint('⚠️ 奖励验证失败,不发放奖励');
    return;
  }

  // 发放奖励到用户账户
  final userId = event.extra?['userId'];
  final customData = event.extra?['customData'];

  _apiService.grantReward(
    userId: userId,
    rewardType: event.rewardType,
    rewardAmount: event.rewardAmount,
    customData: customData,
  );
}

9. 迁移指南

如果你正在从旧版API迁移,以下是对照表:

旧API新API
class MyListener extends OnAdEventListener使用函数回调 onEvent: (event) {}
GromoreAds.onEventListener(listener)GromoreAds.onEvent(...)
GromoreAds.removeEventListener()subscription.cancel()
onSplashEvent(AdEvent event)GromoreAds.onSplashEvents(posId, ...)
单一监听器支持多订阅
手动检查 posId自动 posId 过滤

迁移示例

旧代码

class MyListener extends OnAdEventListener {
  @override
  void onRewardVideoEvent(AdEvent event) {
    if (event.posId == 'my_pos_id') {  // 手动检查posId
      if (event.action == 'reward_video_completed') {
        // 处理逻辑
      }
    }
  }
}

await GromoreAds.onEventListener(MyListener());

新代码

_subscription = GromoreAds.onRewardVideoEvents(
  'my_pos_id',  // 自动过滤posId
  onCompleted: (event) {
    // 处理逻辑
  },
);

代码量减少 60%,更加简洁易读!

需要进一步协助?

与 LightCore 技术顾问沟通,获取商业化策略与集成支持。