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

在新闻 Feed 或社交 Feed 等长列表或无限滚动列表中,为每个商品位置创建横幅广告可能会导致内存消耗过高和界面延迟。为了提高性能,请使用横幅广告回收功能来维护一个大小固定的 BannerAd 实例缓存,并在用户滚动时重复使用屏幕外的广告。

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

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

图表:显示横幅广告从屏幕外滚动到广告缓存中,然后在屏幕上的 Feed 底部重复使用。

前提条件

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

跟踪分配的列表位置

当用户滚动时,横幅回收会将有限数量的 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 示例。