🎯 广告类型
版本 GroMore

🖼️ 开屏广告

开屏广告是在应用启动时展示的全屏广告,通常用于 App 启动页面。重构后的 gromore_ads 插件提供了结构化的 SplashAdRequest,方便同时配置底部 LOGO 以及 Android/iOS 各自的聚合参数。

基础用法

import 'package:gromore_ads/gromore_ads.dart';

Future<void> showSplashAd() async {
  final request = SplashAdRequest(
    posId: 'your_splash_ad_id',
    timeout: const Duration(seconds: 4),
    logo: SplashAdLogo.asset(
      'assets/images/logo.png',
      heightRatio: 0.18, // 可选:LOGO 占屏幕高度比例 (0, 0.25]
      backgroundColor: '#FFFFFF',
    ),
    android: SplashAdAndroidOptions(
      muted: true,
      shakeButton: true,
    ),
    ios: SplashAdIOSOptions(
      hideSkipButton: false,
    ),
  );

  final ok = await GromoreAds.showSplashAd(request);
  if (ok) {
    debugPrint('开屏广告请求成功,等待回调');
  }
}

预加载,再按需展示

Future<void> preloadSplash() async {
  await GromoreAds.showSplashAd(
    const SplashAdRequest(
      posId: 'your_splash_ad_id',
      preload: true, // 仅预加载
    ),
  );
}

Future<void> showPreloadedSplash() async {
  await GromoreAds.showSplashAd(
    const SplashAdRequest(posId: 'your_splash_ad_id'),
  );
}

启动页示例

class SplashScreen extends StatefulWidget {
  const SplashScreen({super.key});

  @override
  State<SplashScreen> createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> {
  @override
  void initState() {
    super.initState();
    _loadAndEnter();
  }

  Future<void> _loadAndEnter() async {
    await GromoreAds.showSplashAd(
      SplashAdRequest(
        posId: 'your_splash_ad_id',
        timeout: const Duration(seconds: 5),
        logo: SplashAdLogo.asset('assets/logo.png'),
      ),
    );

    // 按需延迟进入首页
    await Future<void>.delayed(const Duration(milliseconds: 600));
    if (mounted) {
      Navigator.of(context).pushReplacement(
        MaterialPageRoute(builder: (_) => const HomePage()),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(child: CircularProgressIndicator()),
    );
  }
}

参数速查

SplashAdRequest

字段类型说明
posIdString开屏广告位 ID(必填)
timeoutDuration加载超时时间,默认 3.5 秒
preloadbool是否仅预加载
logoSplashAdLogo?底部 LOGO 配置
androidSplashAdAndroidOptions?Android 端聚合参数
iosSplashAdIOSOptions?iOS 端聚合参数
构造函数说明
SplashAdLogo.asset(path, ...)使用 Flutter 资源文件(推荐)
SplashAdLogo.resource(name, ...)Android drawable/mipmap 资源或 iOS UIImage(named:)
SplashAdLogo.file(path, ...)本地文件路径
SplashAdLogo.bundle(path, ...)iOS Bundle 文件路径

可选参数:

  • height: 固定高度 (dp/pt)
  • heightRatio: 相对屏幕高度比例 (0, 0.25],未指定时默认 0.15
  • backgroundColor: 底部背景色(#AARRGGBB#RRGGBB

LOGO 总高度会自动限制在屏幕高度的 25% 以内,主体广告区域始终不小于屏幕高度的 50%。

Android 聚合参数 SplashAdAndroidOptions

字段类型说明
mutedbool?是否静音播放(仅部分 ADN 支持)
volumedouble?[Android] 播放音量 0~1(与 muted 二选一,未设置则走 SDK 默认)
useSurfaceViewbool?是否使用 SurfaceView 渲染视频/动画
bidNotifybool?竞价成功/失败是否通知 ADN
shakeButtonbool?是否开启摇一摇按钮
enablePreloadbool?聚合兜底是否允许预加载
scenarioIdString?场景 ID(用于后台投放配置)
extrasMap<String, dynamic>?透传 setExtraObject(key, value) 自定义参数
customDataMap<String, dynamic>?写入 CUSTOM_DATA_KEY_GROMORE_EXTRA(需 Map,可服务器端解析)
fallbackSplashAdFallback?聚合自定义兜底参数

SplashAdFallback 字段:adnNameslotIdappIdappKey (可选)。

iOS 聚合参数 SplashAdIOSOptions

字段类型说明
supportCardViewbool?是否启用卡片样式(true/false/null=保持默认)
supportZoomOutViewbool?是否启用缩小样式(同上,null 不改动)
hideSkipButtonbool?是否隐藏跳过按钮(null 时保持默认)
mediaExtMap<String, dynamic>?直接赋值给 slot.ext,可承载 SDK 自定义字段
buttonTypeint?BUMSplashButtonType 数字值(1=全屏可点,2=下载条可点)
extraParamsMap<String, dynamic>?通过 mediation.addParam 透传额外参数
fallbackSplashAdFallback?聚合自定义兜底参数

当字段保持 null 时,插件不会改动原生默认行为;只在明确传入 true/false/数值时才会调用对应的 setter。

监听回调

AdEventSubscription? _subscription;

// 监听特定广告位的开屏事件
_subscription = GromoreAds.onSplashEvents(
  'your_splash_ad_id',
  onLoaded: (event) {
    debugPrint('✅ 开屏广告加载成功');
  },
  onClosed: (event) {
    final closeType = event.extra?['closeType'];
    debugPrint('📌 开屏关闭 closeType=$closeType');
  },
  onClicked: (event) {
    debugPrint('👆 用户点击广告');
  },
  onError: (event) {
    debugPrint('❌ 加载失败: ${event.message}');
  },
  onEcpm: (event) {
    debugPrint('💰 ECPM: ${event.ecpm}, 平台: ${event.sdkName}');
  },
);

// 记得在页面销毁时取消订阅
@override
void dispose() {
  _subscription?.cancel();
  super.dispose();
}

💡 实际场景示例

场景1:游戏启动加载时展示开屏

需求描述: 游戏启动时需要加载资源,同时展示开屏广告,加载完成后进入主菜单。要求广告加载和资源加载并行执行,提升启动效率。

实现代码

import 'package:flutter/material.dart';
import 'package:gromore_ads/gromore_ads.dart';

class GameSplashScreen extends StatefulWidget {
  const GameSplashScreen({super.key});

  @override
  State<GameSplashScreen> createState() => _GameSplashScreenState();
}

class _GameSplashScreenState extends State<GameSplashScreen> {
  @override
  void initState() {
    super.initState();
    _loadGameAndShowSplash();
  }

  Future<void> _loadGameAndShowSplash() async {
    try {
      // 并行执行:资源加载 + 开屏广告
      await Future.wait([
        _loadGameResources(),
        _showSplashAd(),
      ]);

      // 进入主菜单
      if (mounted) {
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => const MainMenu()),
        );
      }
    } catch (e) {
      debugPrint('启动失败: $e');
      // 加载失败也要进入游戏,避免卡在启动页
      if (mounted) {
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => const MainMenu()),
        );
      }
    }
  }

  Future<void> _loadGameResources() async {
    // 模拟资源加载(实际项目中替换为真实的资源加载逻辑)
    await Future.delayed(const Duration(seconds: 2));
    debugPrint('游戏资源加载完成');
  }

  Future<void> _showSplashAd() async {
    await GromoreAds.showSplashAd(
      SplashAdRequest(
        posId: 'your_game_splash_id',
        timeout: const Duration(seconds: 5), // 游戏加载时间较长,给予足够的展示时间
        logo: SplashAdLogo.asset(
          'assets/images/game_logo.png',
          heightRatio: 0.15,
          backgroundColor: '#000000', // 黑色背景,适合游戏风格
        ),
        android: SplashAdAndroidOptions(
          muted: true, // 静音播放,避免打扰用户
          shakeButton: true, // 启用摇一摇功能
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      backgroundColor: Colors.black,
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CircularProgressIndicator(color: Colors.white),
            SizedBox(height: 16),
            Text(
              '加载中...',
              style: TextStyle(color: Colors.white),
            ),
          ],
        ),
      ),
    );
  }
}

关键配置说明

  • timeout: 5秒 - 游戏加载时间较长,给予足够的广告展示时间
  • 使用 Future.wait 并行加载,提升用户体验
  • muted: true - 静音播放,避免突然的声音打扰用户
  • 完善的错误处理,确保即使广告加载失败也能正常进入游戏

预期效果: 用户看到开屏广告的同时,游戏在后台加载资源,提升启动效率。广告关闭或超时后立即进入主菜单。


场景2:电商App首次启动展示品牌开屏

需求描述: 电商App首次启动时展示品牌开屏广告,带有品牌LOGO和自定义背景色,营造品牌氛围。

实现代码

import 'package:flutter/material.dart';
import 'package:gromore_ads/gromore_ads.dart';

class ShopSplashScreen extends StatefulWidget {
  const ShopSplashScreen({super.key});

  @override
  State<ShopSplashScreen> createState() => _ShopSplashScreenState();
}

class _ShopSplashScreenState extends State<ShopSplashScreen> {
  bool _adClosed = false;
  AdEventSubscription? _adEventSubscription;

  @override
  void initState() {
    super.initState();
    _registerAdListener();
    _showSplashAd();
  }

  void _registerAdListener() {
    _adEventSubscription = GromoreAds.onSplashEvents(
      'your_shop_splash_id',
      onLoaded: (event) {
        debugPrint('✅ 开屏广告加载成功');
      },
      onClosed: (event) {
        debugPrint('📌 开屏广告关闭');
        _navigateToHome();
      },
      onError: (event) {
        debugPrint('❌ 广告加载失败: ${event.message}');
        _navigateToHome(); // 失败也要进入首页
      },
      onEcpm: (event) {
        debugPrint('💰 展示ECPM: ${event.ecpm}, 广告平台: ${event.sdkName}');
      },
    );
  }

  Future<void> _showSplashAd() async {
    try {
      await GromoreAds.showSplashAd(
        SplashAdRequest(
          posId: 'your_shop_splash_id',
          timeout: const Duration(seconds: 4),
          logo: SplashAdLogo.asset(
            'assets/images/shop_brand_logo.png',
            height: 80, // 固定高度80dp
            backgroundColor: '#FFFFFF', // 白色背景,品牌风格
          ),
          ios: SplashAdIOSOptions(
            hideSkipButton: false, // 显示跳过按钮,让用户有控制权
          ),
        ),
      );
    } catch (e) {
      debugPrint('展示开屏异常: $e');
      _navigateToHome();
    }
  }

  void _navigateToHome() {
    if (_adClosed || !mounted) return;
    _adClosed = true;

    Future.delayed(const Duration(milliseconds: 300), () {
      if (mounted) {
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(builder: (_) => const HomePage()),
        );
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: CircularProgressIndicator(),
      ),
    );
  }
}

关键配置说明

  • height: 80 - 固定LOGO高度,保持品牌视觉一致性
  • backgroundColor: '#FFFFFF' - 白色底部背景,符合品牌调性
  • 完整的事件监听,包括ECPM信息上报
  • 防重复跳转逻辑,使用 _adClosed 标志位

预期效果: 用户启动App看到品牌开屏广告,底部显示品牌LOGO,白色背景营造简洁品牌氛围。广告关闭后平滑进入首页。


场景3:处理广告加载失败的降级方案

需求描述: 网络不稳定或广告无填充时,展示默认品牌启动页,避免用户长时间等待或看到空白页面。

实现代码

import 'package:flutter/material.dart';
import 'package:gromore_ads/gromore_ads.dart';

class RobustSplashScreen extends StatefulWidget {
  const RobustSplashScreen({super.key});

  @override
  State<RobustSplashScreen> createState() => _RobustSplashScreenState();
}

class _RobustSplashScreenState extends State<RobustSplashScreen> {
  bool _showDefaultSplash = false;
  bool _navigated = false;
  AdEventSubscription? _adEventSubscription;

  @override
  void initState() {
    super.initState();
    _setupAdListener();
    _loadSplashWithFallback();
  }

  void _setupAdListener() {
    _adEventSubscription = GromoreAds.onSplashEvents(
      'your_splash_id',
      onClosed: (event) {
        _navigateToHome();
      },
      onError: (event) {
        debugPrint('❌ 广告加载失败: ${event.message}');
        _showFallbackSplash();
      },
    );
  }

  Future<void> _loadSplashWithFallback() async {
    try {
      // 设置超时检测
      final loadResult = await GromoreAds.showSplashAd(
        const SplashAdRequest(
          posId: 'your_splash_id',
          timeout: Duration(seconds: 3),
        ),
      ).timeout(
        const Duration(seconds: 4),
        onTimeout: () {
          debugPrint('开屏广告加载超时');
          return false;
        },
      );

      if (!loadResult && mounted) {
        _showFallbackSplash();
      }
    } catch (e) {
      debugPrint('开屏广告异常: $e');
      _showFallbackSplash();
    }
  }

  void _showFallbackSplash() {
    if (_navigated || !mounted) return;

    setState(() {
      _showDefaultSplash = true;
    });

    // 展示默认启动页2秒后进入首页
    Future.delayed(const Duration(seconds: 2), () {
      _navigateToHome();
    });
  }

  void _navigateToHome() {
    if (_navigated || !mounted) return;
    _navigated = true;

    Navigator.of(context).pushReplacement(
      MaterialPageRoute(builder: (_) => const HomePage()),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    if (!_showDefaultSplash) {
      return const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );
    }

    // 默认品牌启动页
    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [
              Theme.of(context).primaryColor,
              Theme.of(context).primaryColor.withOpacity(0.8),
            ],
          ),
        ),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Image.asset(
                'assets/images/brand_logo.png',
                width: 120,
                height: 120,
              ),
              const SizedBox(height: 24),
              const Text(
                'Your Brand',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

关键配置说明

  • timeout + .timeout() - 双重超时保护,确保不会无限等待
  • _showDefaultSplash - 状态标志,控制是否显示降级页面
  • _navigated - 防重复跳转标志位
  • 完善的错误处理和降级逻辑

预期效果: 正常情况下展示开屏广告;网络异常或广告无填充时,自动切换到品牌启动页,2秒后进入首页,用户体验流畅。


事件回调

开屏广告的事件都会通过 GromoreAds.onEventListener 派发,常见事件包含:

  • splash_loaded / splash_showed / splash_clicked / splash_closed
  • splash_ecpm:展示后返回的 ECPM 信息 extra,包含 ecpmsdkNamechannel 等字段
  • splash_card_ready / splash_card_clicked / splash_card_closed
  • splash_zoom_out_ready / splash_zoom_out_clicked / splash_zoom_out_closed
  • splash_resume:关闭落地页返回 App

可以在示例应用的「事件日志」卡片中实时观察这些事件。

⚠️ 注意事项

1. SDK初始化顺序

重要提示:在调用 showSplashAd 之前,必须先完成SDK初始化,否则会导致广告加载失败。

// ✅ 正确:先初始化,再展示广告
await GromoreAds.initAd(appId, useMediation: true, debugMode: true);
await GromoreAds.showSplashAd(request);

// ❌ 错误:未初始化就展示广告
await GromoreAds.showSplashAd(request); // 会失败

2. 超时时间设置建议

建议将超时时间设置为 3-5秒

场景推荐超时说明
首页启动3-4秒用户容忍度较低,快速展示
游戏加载4-5秒加载时间较长,用户容忍度高
关卡间4-5秒用户处于游戏状态,容忍度高

注意

  • 过短(<2秒):可能导致广告未加载完成就超时
  • 过长(>6秒):影响用户体验,用户等待时间过长
  • 不传递:使用SDK默认值3.5秒(推荐)

3. LOGO高度配置规则

SDK会自动限制LOGO区域的高度:

  • 最大高度:不超过屏幕高度的 25%
  • 主广告区域:始终保持不小于屏幕高度的 50%
  • 推荐方式:使用 heightRatio 而非固定 height,以适配不同屏幕
// ✅ 推荐:使用比例,自动适配不同屏幕
SplashAdLogo.asset('logo.png', heightRatio: 0.15)

// ⚠️ 不推荐:固定高度,可能在不同设备上显示不一致
SplashAdLogo.asset('logo.png', height: 100)

4. 事件监听顺序

必须在调用 showSplashAd 之前注册事件监听器,否则可能错过早期事件:

// ✅ 正确:先注册监听,再展示广告
_subscription = GromoreAds.onSplashEvents('posId', ...);
await GromoreAds.showSplashAd(request);

// ❌ 错误:可能错过加载成功等早期事件
await GromoreAds.showSplashAd(request);
_subscription = GromoreAds.onSplashEvents('posId', ...);

最佳实践:在 initState() 中注册事件监听器,在 dispose() 中取消订阅。

5. 平台差异说明

Android专属功能

以下参数仅Android支持,iOS会自动忽略

  • mutedvolume - 音量控制
  • useSurfaceView - 视频渲染方式
  • shakeButton - 摇一摇按钮
  • enablePreload - 聚合预加载

iOS专属功能

以下参数仅iOS支持,Android会自动忽略

  • supportCardView - 卡片样式
  • supportZoomOutView - 缩小样式
  • hideSkipButton - 隐藏跳过按钮
  • buttonType - 点击区域类型

跨平台开发建议

如果需要统一体验,建议:

  • 不依赖平台特定的UI样式(卡片、缩小)
  • 音量控制统一使用静音模式
  • 测试时分别在Android和iOS设备上验证

6. 预加载注意事项

使用预加载模式时,参数必须完全一致才能复用:

// 预加载
await GromoreAds.showSplashAd(
  SplashAdRequest(posId: 'id1', preload: true, timeout: Duration(seconds: 4)),
);

// ✅ 正确:参数一致,会使用预加载的广告
await GromoreAds.showSplashAd(
  SplashAdRequest(posId: 'id1', timeout: Duration(seconds: 4)),
);

// ❌ 错误:timeout不一致,会重新加载
await GromoreAds.showSplashAd(
  SplashAdRequest(posId: 'id1', timeout: Duration(seconds: 5)),
);

🐛 常见问题

Q1: 开屏广告一直无法展示怎么办?

排查步骤

  1. 检查SDK是否初始化成功
   final initResult = await GromoreAds.initAd(...);
   print('SDK初始化结果: $initResult');
  1. 检查广告位ID是否正确

    • 确认在穿山甲平台创建了开屏广告位
    • 确认传入的 posId 与平台一致
  2. 检查网络连接

    • 确保设备能正常访问网络
    • 测试环境可能有网络限制
  3. 查看错误日志

   _subscription = GromoreAds.onSplashEvents(
     'your_splash_id',
     onError: (event) {
       print('❌ 广告加载失败: ${event.message}');
       print('错误码: ${event.code}');
     },
   );
  1. 常见错误码
    • 40006 - 配置拉取失败,检查网络和appId
    • 1 - 物料加载失败,广告无填充或网络问题
    • 23 - 加载超时,增加timeout或检查网络

Q2: 如何处理用户快速跳过开屏的情况?

监听开屏关闭事件,通过 closeType 参数区分不同的关闭原因:

_subscription = GromoreAds.onSplashEvents(
  'your_splash_id',
  onClosed: (event) {
    final closeType = event.extra?['closeType'];
    switch (closeType) {
      case 1:
        print('👆 用户点击跳过按钮');
        // 可以统计跳过率
        break;
      case 2:
        print('⏱️ 倒计时结束自然关闭');
        // 广告完整展示
        break;
      case 3:
        print('🔗 用户点击广告跳转');
        // 产生了转化
        break;
      default:
        print('📌 其他关闭原因');
    }
  },
);

Q3: LOGO图片不显示怎么办?

检查清单

  1. 确认图片路径正确
   // ✅ 正确:使用Flutter assets
   SplashAdLogo.asset('assets/images/logo.png')

   // ❌ 错误:使用绝对路径
   SplashAdLogo.asset('/path/to/logo.png')
  1. 确认图片已添加到 pubspec.yaml
   flutter:
     assets:
       - assets/images/logo.png
  1. 确认图片格式支持

    • 推荐:PNG、JPG
    • 避免:WebP(某些平台可能不支持)
  2. 检查图片加载日志

    • 查看控制台是否有"无法加载logo"的错误信息
    • Android: 查看 Logcat
    • iOS: 查看 Xcode Console

Q4: Android和iOS行为差异如何处理?

双端行为差异

功能AndroidiOS处理建议
音量控制支持不支持统一使用静音
卡片样式不支持支持不依赖此UI
摇一摇支持不支持作为可选功能
底部LOGO布局拼接customBottomView使用统一API

跨平台代码示例

import 'dart:io';

SplashAdRequest buildSplashRequest() {
  return SplashAdRequest(
    posId: 'your_splash_id',
    logo: SplashAdLogo.asset('assets/logo.png'),
    // 根据平台传递不同参数
    android: Platform.isAndroid ? SplashAdAndroidOptions(
      muted: true,
      shakeButton: true,
    ) : null,
    ios: Platform.isIOS ? SplashAdIOSOptions(
      hideSkipButton: false,
    ) : null,
  );
}

Q5: 如何调试开屏广告不展示的问题?

调试步骤

  1. 启用详细日志
   await GromoreAds.initAd(
     appId,
     useMediation: true,
     debugMode: true, // 启用调试模式
   );
  1. 使用测试工具
   // 启动GroMore官方测试工具
   await GromoreAds.launchTestTools();
  1. 监听所有事件
   _subscription = GromoreAds.onSplashEvents(
     'your_splash_id',
     onEvent: (event) {
       print('📌 收到事件: ${event.action}');
       print('数据: ${event.toJson()}');
     },
     onError: (event) {
       print('❌ 错误: ${event.message}');
     },
   );
  1. 查看ECPM信息
   _subscription = GromoreAds.onSplashEvents(
     'your_splash_id',
     onEcpm: (event) {
       print('💰 广告平台: ${event.sdkName}, ECPM: ${event.ecpm}');
     },
   );

最佳实践

  • 超时时间:建议 3~5 秒,首页启动3-4秒,游戏加载4-5秒
  • LOGO 资源:推荐使用 Flutter assets + heightRatio,自动适配不同屏幕
  • 聚合兜底:填写 SplashAdFallback 时需确保 ADN 的 AppID/代码位与平台配置一致
  • 事件监听:在 initState() 中注册订阅,在 dispose() 中取消订阅
  • 错误处理:实现完善的降级方案,广告失败也要正常进入应用
  • 跨平台测试:分别在Android和iOS设备上测试,验证平台差异处理

下一步

需要进一步协助?

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