🖼️ 开屏广告
开屏广告是在应用启动时展示的全屏广告,通常用于 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
字段 | 类型 | 说明 |
---|---|---|
posId | String | 开屏广告位 ID(必填) |
timeout | Duration | 加载超时时间,默认 3.5 秒 |
preload | bool | 是否仅预加载 |
logo | SplashAdLogo? | 底部 LOGO 配置 |
android | SplashAdAndroidOptions? | Android 端聚合参数 |
ios | SplashAdIOSOptions? | iOS 端聚合参数 |
LOGO 配置 SplashAdLogo
构造函数 | 说明 |
---|---|
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.15backgroundColor
: 底部背景色(#AARRGGBB
或#RRGGBB
)
LOGO 总高度会自动限制在屏幕高度的 25% 以内,主体广告区域始终不小于屏幕高度的 50%。
Android 聚合参数 SplashAdAndroidOptions
字段 | 类型 | 说明 |
---|---|---|
muted | bool? | 是否静音播放(仅部分 ADN 支持) |
volume | double? | [Android] 播放音量 0~1(与 muted 二选一,未设置则走 SDK 默认) |
useSurfaceView | bool? | 是否使用 SurfaceView 渲染视频/动画 |
bidNotify | bool? | 竞价成功/失败是否通知 ADN |
shakeButton | bool? | 是否开启摇一摇按钮 |
enablePreload | bool? | 聚合兜底是否允许预加载 |
scenarioId | String? | 场景 ID(用于后台投放配置) |
extras | Map<String, dynamic>? | 透传 setExtraObject(key, value) 自定义参数 |
customData | Map<String, dynamic>? | 写入 CUSTOM_DATA_KEY_GROMORE_EXTRA (需 Map,可服务器端解析) |
fallback | SplashAdFallback? | 聚合自定义兜底参数 |
SplashAdFallback
字段:adnName
、slotId
、appId
、appKey
(可选)。
iOS 聚合参数 SplashAdIOSOptions
字段 | 类型 | 说明 |
---|---|---|
supportCardView | bool? | 是否启用卡片样式(true/false/null =保持默认) |
supportZoomOutView | bool? | 是否启用缩小样式(同上,null 不改动) |
hideSkipButton | bool? | 是否隐藏跳过按钮(null 时保持默认) |
mediaExt | Map<String, dynamic>? | 直接赋值给 slot.ext ,可承载 SDK 自定义字段 |
buttonType | int? | BUMSplashButtonType 数字值(1=全屏可点,2=下载条可点) |
extraParams | Map<String, dynamic>? | 通过 mediation.addParam 透传额外参数 |
fallback | SplashAdFallback? | 聚合自定义兜底参数 |
当字段保持
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
,包含ecpm
、sdkName
、channel
等字段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会自动忽略:
muted
、volume
- 音量控制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: 开屏广告一直无法展示怎么办?
排查步骤:
- 检查SDK是否初始化成功
final initResult = await GromoreAds.initAd(...);
print('SDK初始化结果: $initResult');
检查广告位ID是否正确
- 确认在穿山甲平台创建了开屏广告位
- 确认传入的
posId
与平台一致
检查网络连接
- 确保设备能正常访问网络
- 测试环境可能有网络限制
查看错误日志
_subscription = GromoreAds.onSplashEvents(
'your_splash_id',
onError: (event) {
print('❌ 广告加载失败: ${event.message}');
print('错误码: ${event.code}');
},
);
- 常见错误码
40006
- 配置拉取失败,检查网络和appId1
- 物料加载失败,广告无填充或网络问题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图片不显示怎么办?
检查清单:
- 确认图片路径正确
// ✅ 正确:使用Flutter assets
SplashAdLogo.asset('assets/images/logo.png')
// ❌ 错误:使用绝对路径
SplashAdLogo.asset('/path/to/logo.png')
- 确认图片已添加到
pubspec.yaml
flutter:
assets:
- assets/images/logo.png
确认图片格式支持
- 推荐:PNG、JPG
- 避免:WebP(某些平台可能不支持)
检查图片加载日志
- 查看控制台是否有"无法加载logo"的错误信息
- Android: 查看 Logcat
- iOS: 查看 Xcode Console
Q4: Android和iOS行为差异如何处理?
双端行为差异:
功能 | Android | iOS | 处理建议 |
---|---|---|---|
音量控制 | 支持 | 不支持 | 统一使用静音 |
卡片样式 | 不支持 | 支持 | 不依赖此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: 如何调试开屏广告不展示的问题?
调试步骤:
- 启用详细日志
await GromoreAds.initAd(
appId,
useMediation: true,
debugMode: true, // 启用调试模式
);
- 使用测试工具
// 启动GroMore官方测试工具
await GromoreAds.launchTestTools();
- 监听所有事件
_subscription = GromoreAds.onSplashEvents(
'your_splash_id',
onEvent: (event) {
print('📌 收到事件: ${event.action}');
print('数据: ${event.toJson()}');
},
onError: (event) {
print('❌ 错误: ${event.message}');
},
);
- 查看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设备上测试,验证平台差异处理