Flutter SDK
Flutter 插件 adbnk_sdk 封装了 ADBNK 的 Android 与 iOS 原生 SDK,渲染、点击处理和追踪均由各平台的原生 SDK 完成。Dart API 命名与 google_mobile_ads 一致:AdRequest、AdLoadCallback、FullScreenContentCallback 等。
版本:以你拿到的插件包为准,可通过 AdbnkSdk.sdkVersion 获取。
环境要求
| 项目 | 要求 |
|---|---|
| Flutter | 3.0+(Dart 2.19+) |
| Android | minSdk 21,compileSdk 34+ |
| iOS | 12.0+ |
每个广告位还需要一个广告位 ID。在流量主后台创建 App SDK 广告位,进入广告位管理,点击获取代码。
安装
插件及其依赖的原生 SDK 以 SDK 包形式交付。如需获取,请联系你的 ADBNK 客户经理。
把插件解压到你的项目中(例如 packages/adbnk_sdk),并以 path 依赖引入:
# pubspec.yaml
dependencies:
adbnk_sdk:
path: packages/adbnk_sdkflutter pub get插件还需要 SDK 包里的原生 SDK 文件:
- Android: 把 AAR 和 POM 放到
adbnk_sdk/android/libs/repo/app/adbnk/adbnk-sdk/<版本号>/(文件名为adbnk-sdk-<版本号>.aar与.pom)。插件会把该目录当作本地 Maven 仓库。如果你的settings.gradle把dependencyResolutionManagement设为PREFER_SETTINGS,还需在其中加入maven { url = uri("<路径>/adbnk_sdk/android/libs/repo") }。 - iOS: 把
AdbnkSDK.xcframework放到adbnk_sdk/ios/Frameworks/AdbnkSDK.xcframework,在ios/Podfile中设置platform :ios, '12.0'或更高,然后执行pod install。
缺少原生文件时,构建会停止并打印期望的路径。
初始化
import 'package:adbnk_sdk/adbnk_sdk.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final status = await AdbnkSdk.initialize(
appId: 'com.example.myapp', // your app identifier
);
// status.ready, status.sdkVersion
runApp(const MyApp());
}在 initialize 完成之前调用任何加载方法都会抛出 StateError。
AdbnkConfig 字段:
| 字段 | 默认值 | 说明 |
|---|---|---|
testMode | false | 见测试模式 |
gdprConsent | null | GDPR 同意状态。null 表示未知,按未同意处理,见隐私与合规 |
coppaCompliance | false | 将所有请求视为面向儿童 |
其他成员:AdbnkSdk.isInitialized、AdbnkSdk.sdkVersion、setGDPRConsent(bool)、setCOPPACompliance(bool)、setTestMode(bool)。
AdRequest
final request = AdRequest.builder()
.addKeyword('sports')
.setContentUrl('https://example.com/article/123')
.setNonPersonalizedAds(false)
.setTagForChildDirectedTreatment(null) // true / false / null = unspecified
.setTagForUnderAgeOfConsent(null)
.putExtra('placement', 'home_feed')
.build();const AdRequest.empty() 为默认请求。
广告格式
| 格式 | Dart API |
|---|---|
| 横幅 Banner | BannerAdWidget(widget) |
| 悬浮横幅(固定在顶部/底部) | FloatBannerAd |
| 原生 Native | NativeAdWidget(widget)/ NativeAd |
| 插屏 Interstitial | InterstitialAd |
| 激励 Rewarded | RewardedAd |
| 激励插屏 Rewarded interstitial | RewardedInterstitialAd |
| 开屏 Splash | SplashAd |
| 应用打开 App open | AppOpenAd / AppOpenAdManager |
| 弹窗 Popup | PopupAd |
全屏格式:通用流程
插屏、激励、激励插屏、开屏、应用打开和弹窗广告的用法相同:
static Future<void> load(
String zoneId, {
AdRequest? adRequest,
required AdLoadCallback<T> adLoadCallback,
});- 在
onAdLoaded中设置ad.fullScreenContentCallback。 - 调用
ad.show()。 - 广告关闭后调用
ad.dispose()。
FullScreenContentCallback<T>(
onAdShowedFullScreenContent: (ad) {},
onAdDismissedFullScreenContent: (ad) {},
onAdFailedToShowFullScreenContent: (ad, error) {},
onAdImpression: (ad) {},
onAdClicked: (ad) {},
)每个广告对象只能展示一次,展示后 ad.isReady 变为 false。
每种格式同时只保留一个已加载广告
每种格式同一时间只持有一个已加载的广告,事件会派发给最近一次加载的实例。请先展示当前插屏,再加载下一个插屏。
插屏
InterstitialAd? _interstitial;
void loadInterstitial() {
InterstitialAd.load(
'YOUR_ZONE_ID',
adLoadCallback: AdLoadCallback<InterstitialAd>(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback<InterstitialAd>(
onAdDismissedFullScreenContent: (ad) {
ad.dispose();
_interstitial = null;
loadInterstitial(); // preload the next one
},
onAdFailedToShowFullScreenContent: (ad, error) {
ad.dispose();
_interstitial = null;
},
);
_interstitial = ad;
},
onAdFailedToLoad: (error) => debugPrint('load failed: $error'),
),
);
}
void showInterstitial() {
if (_interstitial?.isReady ?? false) _interstitial!.show();
}激励与激励插屏
RewardedAd 与 RewardedInterstitialAd 的 API 相同。把奖励回调传给 show(),每个广告最多调用一次。
RewardedAd.load(
'YOUR_ZONE_ID',
adLoadCallback: AdLoadCallback<RewardedAd>(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback<RewardedAd>(
onAdDismissedFullScreenContent: (ad) => ad.dispose(),
);
ad.show(onUserEarnedReward: (RewardItem reward) {
grantReward(reward.amount, reward.type);
});
},
onAdFailedToLoad: (error) {},
),
);奖励数量和类型在流量主后台的广告位上配置。
开屏
SplashAd.load(
'YOUR_ZONE_ID',
adLoadCallback: AdLoadCallback<SplashAd>(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback<SplashAd>(
onAdDismissedFullScreenContent: (ad) { ad.dispose(); goToHome(); },
onAdFailedToShowFullScreenContent: (ad, _) { ad.dispose(); goToHome(); },
);
ad.show();
},
onAdFailedToLoad: (_) => goToHome(),
),
);应用打开
AppOpenAd.load(...) 的用法与其他全屏格式相同。如需在 App 每次回到前台时展示应用打开广告,使用 AppOpenAdManager,并在生命周期监听中调用:
class _AppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
AppOpenAdManager.instance.setup(zoneId: 'YOUR_ZONE_ID');
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
AppOpenAdManager.instance.showAdIfAvailable();
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
AppOpenAdManager.instance.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => const MaterialApp(home: HomePage());
}每次广告关闭后,管理器会预加载下一个广告;加载失败时会自动重试。
弹窗
PopupAd.load(
'YOUR_ZONE_ID',
adLoadCallback: AdLoadCallback<PopupAd>(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback<PopupAd>(
onAdDismissedFullScreenContent: (ad) => ad.dispose(),
);
ad.show();
},
),
);横幅
BannerAdWidget 是一个 platform view,创建时即加载广告。
BannerAdWidget(
zoneId: 'YOUR_ZONE_ID',
size: BannerSize.banner, // 320x50
adRequest: AdRequest.empty(), // optional
callback: BannerAdCallbackWrapper(
onLoaded: () {},
onFailed: (AdError e) {},
onImpression: () {},
onClicked: () {},
),
)BannerSize 取值:
| 值 | 尺寸 |
|---|---|
banner | 320×50 |
largeBanner | 320×100 |
mediumRectangle | 300×250 |
fullBanner | 468×60 |
leaderboard | 728×90 |
adaptive | 屏幕宽度 × 50 |
请选择与广告位一致的尺寸。屏幕上可以同时存在多个横幅。
悬浮横幅
悬浮横幅加载成功后会自动展示。
final floatBanner = FloatBannerAd(
zoneId: 'YOUR_ZONE_ID',
position: FloatBannerPosition.bottom,
height: 50,
margin: 0,
showCloseButton: true,
refreshInterval: 0, // seconds, 0 = no refresh
);
await floatBanner.load(callback: BannerAdCallbackWrapper(onFailed: (e) {}));
// floatBanner.hide(); floatBanner.show(); floatBanner.isShowing
// When you no longer need it:
floatBanner.dispose();原生
使用 NativeAdWidget 时,你提供布局,widget 负责加载、记录曝光和处理点击:
NativeAdWidget(
zoneId: 'YOUR_ZONE_ID',
builder: (context, NativeAdData? ad, bool isLoaded) {
if (!isLoaded || ad == null) return const SizedBox.shrink();
return Card(
child: ListTile(
leading: ad.iconUrl != null ? Image.network(ad.iconUrl!) : null,
title: Text(ad.headline ?? ''),
subtitle: Text(ad.body ?? ''),
trailing: Text(ad.callToAction ?? ''),
),
);
},
)点击该 widget 会打开广告。请始终在布局中展示 ad.sponsoredLabel("广告"标识)。
NativeAdData 字段:
| 字段 | 说明 |
|---|---|
headline | 广告标题 |
body | 广告正文 |
callToAction | 行动按钮文案 |
iconUrl | 图标图片 URL |
imageUrl | 主图 URL |
advertiser | 广告主名称 |
rating | 评分 |
price | 价格 |
store | 应用商店 |
sponsoredLabel | "广告"标识文案 |
如需完全自行控制,可直接使用 NativeAd:
- 调用
await ad.load(callback: ...)。 - 读取
ad.adData。 - 广告真实展示给用户时调用
ad.recordImpression()。 - 仅在用户真实点击时调用
ad.recordClick()。 - 用完后调用
ad.dispose()。
点击处理
无需自行处理点击。原生 SDK 记录点击后,若广告带有 deeplink 则打开它;没有 deeplink 或无法打开时,改为打开落地页。
隐私与合规
SDK 从不弹出同意框或权限框。由你的 App 收集同意,再把结果传给 SDK。
GDPR(欧洲经济区/英国用户)。 用你的 CMP 收集同意,然后调用:
await AdbnkSdk.setGDPRConsent(userConsented);请在加载广告之前完成。未设置时同意状态为未知,SDK 按未同意处理。
非个性化广告:
AdRequest.builder().setNonPersonalizedAds(true)COPPA / 面向儿童的 App:
await AdbnkSdk.setCOPPACompliance(true); // whole app
AdRequest.builder().setTagForChildDirectedTreatment(true); // per request
AdRequest.builder().setTagForUnderAgeOfConsent(true);iOS ATT。 SDK 不弹出 ATT 授权框。如需请求用户授权追踪:
- 在
ios/Runner/Info.plist中添加NSUserTrackingUsageDescription。 - 使用你选择的 ATT 插件请求授权。
用户未授权追踪时,SDK 不读取 IDFA,广告照常投放。详见 iOS 隐私与合规。
商店披露。 Google Play 数据安全与 App Store 隐私标签以原生 SDK 为准,见 Android 和 iOS 页面。
测试模式
testMode: true(或AdbnkSdk.setTestMode(true))会把请求标记为测试请求。ADBNK 目前不提供专用测试素材,开发期间看到的是真实广告。请勿在自己的设备上反复点击广告。- SDK 自动处理流量质量保护,无需任何配置。
错误码
AdError 包含 code 和 message。
来自原生 SDK 的错误保留各平台的错误码:
- Android:0–11,见 Android 错误码。
- iOS:1001–1008,见 iOS 错误码。
Dart 层自身产生的错误:
| 错误码 | 含义 |
|---|---|
| 1001 | 广告位 ID 无效 |
| 1002 | 无填充(原生广告未返回素材时也使用此码) |
| 1003 | 网络错误 |
| 1004 | 内部错误(message 中包含原生错误码) |
| 1005 | 广告已过期或未就绪,例如在加载完成前或已展示后调用了 show() |
| 1006 | 广告已展示过 |
| 1007 | 已有加载在进行中 |
| 1008 | 不支持的广告格式 |
| 1009 | 当前平台没有可用的原生插件 |
常见问题
横幅在桌面端或 Web 上不显示。 插件只支持 Android 和 iOS。在其他平台上,BannerAdWidget 渲染为空白区域,不会加载广告,调用原生 SDK 会失败(返回 AdError 时错误码为 1009)。
回调触发到了错误的广告上。 每种全屏格式只保留一个已加载的广告。请在上一个广告展示或 dispose 之后再加载下一个。
还能使用 loadWithCallback 和 *AdCallback 类吗? 仍可使用,但已废弃。请改用静态方法 load(..., adLoadCallback:) 配合 FullScreenContentCallback。
排查信息。 全屏广告提供 ad.responseInfo?.responseId,联系支持时请附上该 ID。