‘This AdWidget is already in the Widget tree’ — 仅在某个特定流程后崩溃

发布: (2026年4月3日 GMT+8 04:01)
4 分钟阅读
原文: Dev.to

Source: Dev.to

错误

我在使用 google_mobile_ads 显示横幅广告的 Flutter 应用中,某天在 MyPage 页面出现了以下错误:

This AdWidget is already in the Widget tree

Make sure you are not using the same ad object in more than one AdWidget.

错误信息很明确,但崩溃并不总是出现。

观察到的行为

流程结果
正常切换标签页不会崩溃
前往其他页面后再返回不会崩溃
更新地址 → 返回主页面 → 打开 MyPage 标签页崩溃

只有地址更新的流程会触发该错误。

产生原因

应用使用 BottomNavigationBarIndexedStack。在地址更新后,代码通过以下方式返回主页面:

Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => MainPage()),
);

Navigator.push 会在栈顶添加一个新页面,而 不会 移除旧页面。因此,旧的 MainPage(以及其中的 MyPage)仍然保留在内存中。

当在新的 MainPage 上打开 MyPage 时,旧的 MyPage 状态仍持有一个已挂载 AdWidgetBannerAd。于是两个 AdWidget 同时尝试使用同一广告资源,导致崩溃。

在普通的标签切换中只有一个 MainPage 与一个 MyPage 实例,所以不会出现此问题。

解决方案:清除导航栈

push 替换为 pushAndRemoveUntil

Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (_) => MainPage()),
  (route) => false,
);

这样会移除所有之前的路由,确保旧的 MainPage 及其 MyPage 被销毁。用户再次打开 MyPage 时会创建全新的实例,从而消除重复的 AdWidget。更改后崩溃不再出现。

didUpdateWidget 与广告

原来的代码在每次 widget 更新时都会重新创建横幅广告:

@override
void didUpdateWidget(covariant MyPage oldWidget) {
  super.didUpdateWidget(oldWidget);
  _bannerAd?.dispose();
  _bannerAd = AdHelper.createBannerAd();
}

如果旧的 AdWidget 仍在树中,销毁并重新创建广告会导致同样的重复问题。解决办法是 完全移除这段逻辑,仅在 initStatedispose 中管理横幅广告:

@override
void initState() {
  super.initState();
  _bannerAd = AdHelper.createBannerAd();
}

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

插件如何强制此规则

google_mobile_ads 插件会跟踪广告是否已经挂载在 widget 树中。在 ad_containers.dart 中:

  • _AdWidgetState.initState 通过 instanceManager.isWidgetAdIdMounted() 检查广告的 ID。
  • 若广告已挂载,build 方法会抛出 “already in the Widget tree” 错误。
  • 只有当 _AdWidgetState.dispose 调用 unmountWidgetAdId 时,ID 才会被释放。

因此,同一个 BannerAd 只能 被一个 AdWidget 使用;插件在代码层面强制了这一限制。

教训

Navigator.push 会保留之前的页面(以及它拥有的任何原生资源)。当这些资源——例如广告——需要被释放时,必须将旧页面的状态从导航栈中移除。使用 pushAndRemoveUntil(或其他清栈的导航方式)来确保正确销毁,避免因重复的 AdWidget 导致的崩溃。

0 浏览
Back to Blog

相关文章

阅读更多 »