在可滚动的信息流中回收横幅广告

在长列表或无限滚动列表(例如新闻信息流或社交动态)中,为每个内容项位置创建横幅广告可能会导致内存消耗过高和界面卡顿。为了提升性能,请使用横幅广告回收机制来维护一个固定大小的小型 BannerAd 实例缓存,并在用户滚动时重复使用屏幕外的广告。

本页面介绍了如何调整广告缓存大小、检查 BannerAd 实例是否在屏幕外,以及如何将缓存的广告重新分配到列表位置。

下图展示了当用户滚动屏幕时,屏幕外的横幅广告如何返回缓存并在列表位置重复使用:

图表显示,横幅广告滚动到屏幕外并进入广告缓存,然后在屏幕上信息流的底部重复使用。

前提条件

在继续操作之前,请对滚动显示的横幅广告使用内嵌自适应功能。

跟踪分配的列表位置

当用户滚动时,横幅广告回收机制会将有限数量的 BannerAd 实例保留在内存中,并在列表位置之间移动这些实例。

如需了解要重复使用哪个广告,请跟踪缓存中的每个广告所占用的列表位置。

在 widget 中维护以下状态:

  • BannerAd 实例的固定大小缓存。您设置的缓存大小是内存中一次可容纳的广告数量上限。
  • 广告间隔,用于确定广告在列表中出现的频率。
  • 一个位置 Map 类,用于将每个 BannerAd 实例与该实例占用的列表位置相关联。回收广告时,请更新此 Map 类中的条目,使其指向最新位置。
  • 一个大小为 Map 的类,用于存储每个已加载广告的平台解析 AdSize 对象。广告加载后,内嵌自适应横幅广告的高度便会确定。

以下示例会跟踪缓存中每个广告的分配列表位置:

// Maximum number of banner ad instances kept in the cache.
static const int _cacheSize = 8;

// Spacing between banner ads. For example, index 0, 6, 12, and so on
// display ads.
static const int _adInterval = 6;

// The cache of allocated BannerAd instances.
final List<BannerAd> _banners = [];

// Maps each BannerAd instance to its currently assigned list item position.
final Map<BannerAd, int> _bannerPositions = {};

// Stores the platform-resolved size of each loaded banner ad.
final Map<BannerAd, AdSize> _bannerSizes = {};

选择缓存大小

使缓存大于可同时在屏幕上展示的广告数量上限。如果缓存小于该大小,则选择回收的广告通常仍可见,并且您的应用会分配其他广告,而不是重复使用这些广告。

如需选择尺寸,请执行以下操作:

  1. 用视口高度除以一个广告间隔的高度,即可估算出屏幕上一次可容纳多少个广告。
  2. 为在快速滚动期间仍处于分离状态的广告添加空间。
  3. 如果您发现选择回收的广告仍在屏幕上,请增加缓存大小。

本页中的示例每六行显示一个广告,并使用大小为 8 的缓存。此示例为屏幕上同时可见的少数广告留出了空间。

检查广告是否在屏幕上

所有呈现平台视图的 Google Mobile Ads Flutter Plugin 类都会实现 AdWithView 类并提供 isMounted getter。在将缓存的广告重新分配到列表位置之前,请检查 isMounted getter,以确认广告已滚动到屏幕外,可以安全地重复使用。

检索和回收横幅广告

当列表请求在给定位置展示横幅广告时,请执行以下操作:

  1. 重复使用已分配到该位置的广告(如果有)。
  2. 如果缓存未满,则请求广告。
  3. 如果缓存已满,请选择一个缓存的广告,并验证 isMounted getter 是否为 false 值,然后再重新分配广告。
  4. 当 widget 从 widget 树中移除时,对缓存中的每个广告调用 dispose() 方法。

以下示例会检索并回收横幅广告:

BannerAd _getRecycledBannerAd(BuildContext context, int bannerPosition) {
  // 1. If an ad is already mapped to this position, reuse it.
  final BannerAd? existingBanner = _bannerPositions.entries
      .firstWhereOrNull((entry) => entry.value == bannerPosition)
      ?.key;
  if (existingBanner != null) {
    return existingBanner;
  }

  // 2. If the cache is not full, allocate a new ad instance.
  if (_banners.length < _cacheSize) {
    final BannerAd newBanner = _createBannerAd(context);
    _banners.add(newBanner);
    _bannerPositions[newBanner] = bannerPosition;
    return newBanner;
  }

  // 3. Select an existing ad from the cache using modulo arithmetic.
  final BannerAd targetBanner = _banners[bannerPosition % _cacheSize];

  // 4. Verify the ad is detached from the screen before recycling.
  if (targetBanner.isMounted) {
    // If still on screen during fast scrolls, allocate a temporary instance.
    return _createBannerAd(context);
  } else {
    // Safe to reuse: reassign to the new position.
    _bannerPositions[targetBanner] = bannerPosition;
    return targetBanner;
  }
}

如需查看完整示例,请参阅 Google Mobile Ads Flutter Plugin 示例。