You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter集成google_mobile_ads Banner广告加载异常及资源释放问题求帮助

核心问题根因

1. Banner广告随机重复触发加载的原因

  • 广告初始化逻辑写在了didChangeDependencies生命周期中,该方法会在页面依赖变化、父组件重建、路由栈状态更新时被频繁调用,叠加初始化Future的异步回调,就会出现日志随机触发加载事件的现象。
  • 获取Provider状态时没有关闭监听绑定,只要全局广告状态发生变动,页面就会触发重建流程,再次走广告加载逻辑。
  • main函数中的根路由指向了不存在的BannerAdWidget类,导致路由栈反复触发异常重建,进一步加剧广告重复加载的概率。
  • 全局复用的AdListener没有和当前Banner广告实例做绑定,多实例的回调混在一起,让日志输出看起来无规律。

2. 广告资源泄漏、持续生成WebView日志的原因

  • dispose方法没有做空值兜底,如果页面触发销毁时广告还未加载完成(bannerAd为null),直接调用bannerAd.dispose()会触发空指针异常,导致销毁流程中断,底层WebView广告资源无法被正常回收,就会在后台持续输出WebView调试日志占用性能。
  • 没有设置广告加载的唯一性标记,重复创建多个BannerAd实例时,旧实例没有被及时释放,多个WebView实例驻留内存。
  • 加载失败的广告没有主动调用dispose方法释放,长期堆积无效原生资源。
针对性修正实现

1. 修正页面生命周期逻辑

把广告加载逻辑从didChangeDependencies移到仅执行一次的启动流程,增加防重复加载、页面销毁状态判断,dispose逻辑增加空值兜底:

class _HomePageState extends State<HomePage> {
  BannerAd? bannerAd;
  bool _isAdLoaded = false; // 防重复加载标记

  @override
  void initState() {
    super.initState();
    // 等第一帧Build完成后再获取Provider状态,规避didChangeDependencies的重复调用问题
    WidgetsBinding.instance.addPostFrameCallback((_) {
      // 关闭Provider状态监听,避免依赖变化触发页面重建
      final adState = Provider.of<AdState>(context, listen: false);
      adState.initialization.then((status) {
        // 页面已销毁时不再执行后续逻辑
        if (!mounted) return;
        // 已加载过广告则不重复创建实例
        if (_isAdLoaded) return;
        setState(() {
          bannerAd = BannerAd(
            adUnitId: adState.bannerAdUnitId,
            size: AdSize.banner,
            request: const AdRequest(),
            listener: adState.adListener,
          )..load();
          _isAdLoaded = true;
        });
      });
    });
  }

  @override
  void dispose() {
    // 空值兜底,避免未加载完成时调用dispose触发崩溃
    bannerAd?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text("banner ads checking"),
          if (bannerAd == null)
            const SizedBox(height: 50)
          else
            SizedBox(
              height: 50,
              child: AdWidget(ad: bannerAd!),
            )
        ],
      ),
    );
  }
}

2. 修正AdState类的复用逻辑

补充加载失败的资源释放逻辑,修正跨平台测试广告ID的配置错误:

class AdState {
  Future<InitializationStatus> initialization;
  AdState(this.initialization);

  // 谷歌官方测试Banner广告ID,修正原来Android/iOS使用同一ID的错误
  String get bannerAdUnitId => Platform.isAndroid
      ? 'ca-app-pub-3940256099942544/6300978111'
      : 'ca-app-pub-3940256099942544/2934735716';

  AdListener get adListener => _adListener;

  final AdListener _adListener = AdListener(
    onAdLoaded: (ad) => debugPrint("Ad Loaded: ${ad.adUnitId}."),
    onAdFailedToLoad: (ad, error) {
      debugPrint("Ad failed to load: ${ad.adUnitId}. $error");
      ad.dispose(); // 加载失败的广告及时释放,避免资源泄漏
    },
    onAdOpened: (ad) => debugPrint("Ad opened: ${ad.adUnitId}."),
    onAdClosed: (ad) => debugPrint("Ad closed: ${ad.adUnitId}."),
    onAppEvent: (ad, name, data) =>
        debugPrint("Ad event: ${ad.adUnitId}. $name, $data"),
    onApplicationExit: (ad) => debugPrint("App Exit: ${ad.adUnitId}."),
  );
}

3. 修正main入口的路由错误

替换原本指向不存在类的占位路由,避免路由栈异常重建:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomePage(), // 指向实际编写的HomePage类,替换错误的占位组件
    );
  }
}
性能优化补充
  • 应用切到后台时,主动调用当前活动Banner广告的dispose方法释放资源,回到前台时再重新加载广告,避免WebView在后台驻留消耗性能。
  • 不要在全局复用的AdListener里绑定Native广告、激励视频的专属回调,不同类型广告的回调参数不匹配,会触发原生层异常日志输出。
  • 开发阶段可以通过关闭WebView verbose日志的方式减少无意义的日志输出,降低调试时的性能占用。

内容的提问来源于stack exchange,提问作者Sharyer Ilyas khan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.22 17:36:10