开屏广告

开屏广告是一种特殊的广告格式,适合希望通过应用加载屏幕获利的发布商。开屏广告在用户将您的应用切换为在前台运行时展示,用户可随时关闭。

开屏广告会自动显示一个较小的区域并在其中展示品牌信息,让用户知道他们在使用您的应用。以下是一个开屏广告示例:

前提条件

  • Flutter 插件 0.13.5 或更高版本。
  • 完成入门指南的学习。您的 Flutter 应用应该已经导入了 Google 移动广告 Flutter 插件。

务必用测试广告进行测试

在构建和测试应用时,请确保使用的是测试广告,而不是实际投放的广告。否则,可能会导致您的帐号被暂停。

对于 Android 和 iOS 激励广告,加载测试广告最简便的方法就是使用下面的专用测试广告单元 ID:

Android

ca-app-pub-3940256099942544/3419835294

iOS

ca-app-pub-3940256099942544/5662855259

这些测试广告单元 ID 已经过专门配置,可为每个请求返回测试广告,您可以在自己应用的编码、测试和调试过程中随意使用这些测试广告单元 ID。只需确保您会在发布应用前用自己的广告单元 ID 替换这些测试广告单元 ID 即可。

实现

植入开屏广告的主要步骤如下所示:

  1. 创建实用工具类,用于先加载广告,以备需要展示时使用。
  2. 加载广告。
  3. 注册回调并展示广告。
  4. 监听 WidgetsBindingObserver.didChangeAppLifecycleState,以便在出现前台事件期间展示广告。

创建实用工具类

创建名为 AppOpenAdManager 的新类以加载广告。此类会管理实例变量,以便跟踪各个平台加载的广告和广告单元 ID。

import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'dart:io' show Platform;

class AppOpenAdManager {

  String adUnitId = Platform.isAndroid
    ? 'ca-app-pub-3940256099942544/3419835294'
    : 'ca-app-pub-3940256099942544/5662855259';

  AppOpenAd? _appOpenAd;
  bool _isShowingAd = false;

  /// Load an [AppOpenAd].
  void loadAd() {
    // We will implement this below.
  }

  /// Whether an ad is available to be shown.
  bool get isAdAvailable {
    return _appOpenAd != null;
  }
}

加载广告

在用户进入应用之前,您的开屏广告需要准备就绪。请实现实用工具类,以便在需要展示广告之前发出广告请求。

广告的加载是通过针对 AppOpenAd 类使用 loadAd() 方法完成的。加载方法需要广告单元 ID、屏幕方向模式、AdRequest 对象以及在广告加载成功或失败时调用的完成处理程序。已加载的 AppOpenAd 对象以完成处理程序中的参数的形式提供。以下示例展示了如何加载 AppOpenAd

public class AppOpenAdManager {
  ...

  /// Load an [AppOpenAd].
  void loadAd() {
    AppOpenAd.load(
      adUnitId: adUnitId,
      orientation: AppOpenAd.orientationPortrait,
      request: AdRequest(),
      adLoadCallback: AppOpenAdLoadCallback(
        onAdLoaded: (ad) {
          _appOpenAd = ad;
        },
        onAdFailedToLoad: (error) {
          print('AppOpenAd failed to load: $error');
          // Handle the error.
        },
      ),
    );
  }
}

展示广告并处理全屏回调

在展示广告之前,请为您要监听的每个广告事件注册 FullScreenContentCallback

public class AppOpenAdManager {
  ...

  public void showAdIfAvailable() {
    if (!isAdAvailable) {
      print('Tried to show ad before available.');
      loadAd();
      return;
    }
    if (_isShowingAd) {
      print('Tried to show ad while already showing an ad.');
      return;
    }
    // Set the fullScreenContentCallback and show the ad.
    _appOpenAd!.fullScreenContentCallback = FullScreenContentCallback(
      onAdShowedFullScreenContent: (ad) {
        _isShowingAd = true;
        print('$ad onAdShowedFullScreenContent');
      },
      onAdFailedToShowFullScreenContent: (ad, error) {
        print('$ad onAdFailedToShowFullScreenContent: $error');
        _isShowingAd = false;
        ad.dispose();
        _appOpenAd = null;
      },
      onAdDismissedFullScreenContent: (ad) {
        print('$ad onAdDismissedFullScreenContent');
        _isShowingAd = false;
        ad.dispose();
        _appOpenAd = null;
        loadAd();
      },
    );
  }
}

如果用户在离开您的应用后,通过点击开屏广告返回到您的应用,确保不向此类用户展示其他开屏广告。

监听应用前台事件

要接收有关应用前台事件的通知,您需要实现 WidgetsBindingObserver 并监听 WidgetsBindingObserver.didChangeAppLifecycleState() 中的 resumed 事件。

import 'package:app_open_example/app_open_ad_manager.dart';
import 'package:flutter/material.dart';

class AppLifecycleReactor extends WidgetsBindingObserver {
  final AppOpenAdManager appOpenAdManager;

  AppLifecycleReactor({required this.appOpenAdManager});

  @override
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    // Try to show an app open ad if the app is being resumed and
    // we're not already showing an app open ad.
    if (state == AppLifecycleState.resumed) {
      appOpenAdManager.showAdIfAvailable();
    }
  }
}

现在,您可以将 AppLifecycleReactor 添加为观察者。例如,从您的首页:

import 'package:app_open_example/app_open_ad_manager.dart';
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

import 'app_lifecycle_reactor.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  MobileAds.instance.initialize();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'App Open Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'App Open Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

/// Example home page for an app open ad.
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  @override
  void initState() {
    super.initState();
    
    AppOpenAdManager appOpenAdManager = AppOpenAdManager()..loadAd();
    WidgetsBinding.instance!
        .addObserver(AppLifecycleReactor(appOpenAdManager: appOpenAdManager));
    
  }

考虑广告有效期

为确保您不会展示过期的广告,请在 AppOpenAdManager 中添加时间戳,用于查看广告加载后经过了多长时间。然后,使用该时间戳检查广告是否仍然有效。

/// Utility class that manages loading and showing app open ads.
class AppOpenAdManager {
  ...
  
  /// Maximum duration allowed between loading and showing the ad.
  final Duration maxCacheDuration = Duration(hours: 4);

  /// Keep track of load time so we don't show an expired ad.
  DateTime? _appOpenLoadTime;
  
  ...

  /// Load an [AppOpenAd].
  void loadAd() {
    AppOpenAd.load(
      adUnitId: adUnitId,
      orientation: AppOpenAd.orientationPortrait,
      request: AdRequest(),
      adLoadCallback: AppOpenAdLoadCallback(
        onAdLoaded: (ad) {
          print('$ad loaded');
          _appOpenLoadTime = DateTime.now();
          _appOpenAd = ad;
        },
        onAdFailedToLoad: (error) {
          print('AppOpenAd failed to load: $error');
        },
      ),
    );
  }

  /// Shows the ad, if one exists and is not already being shown.
  ///
  /// If the previously cached ad has expired, this just loads and caches a
  /// new ad.
  void showAdIfAvailable() {
    if (!isAdAvailable) {
      print('Tried to show ad before available.');
      loadAd();
      return;
    }
    if (_isShowingAd) {
      print('Tried to show ad while already showing an ad.');
      return;
    }
    if (DateTime.now().subtract(maxCacheDuration).isAfter(_appOpenLoadTime!)) {
      print('Maximum cache duration exceeded. Loading another ad.');
      _appOpenAd!.dispose();
      _appOpenAd = null;
      loadAd();
      return;
    }
    // Set the fullScreenContentCallback and show the ad.
    _appOpenAd!.fullScreenContentCallback = FullScreenContentCallback(...);
    _appOpenAd!.show();
  }
}

冷启动和加载屏幕

到现在为止,本文档都假定您仅在以下情况下展示开屏广告:用户将在内存中挂起的应用切换为在前台运行。用户启动您的应用,但该应用之前未在内存中挂起,这种情况就称为“冷启动”。

例如,用户首次打开您的应用便属于冷启动。对于冷启动,您没有之前已加载的开屏广告可供立即展示。请求广告和收到相应广告之间的延迟会导致出现以下情况:用户能够暂时使用您的应用,然后突然看到一条无关广告。应避免出现这种情况,因为这会导致用户体验不佳。

在冷启动时使用开屏广告的首选方法是,使用加载屏幕来加载游戏或应用素材资源,并且仅在加载屏幕展示广告。如果您的应用已加载完毕,并且用户已经访问应用的主要内容,则不要展示广告。

最佳做法

借助开屏广告,您可以在用户首次启动应用和切换应用期间通过应用的加载屏幕获利,不过,还请务必考虑一些最佳做法,以便用户喜欢使用您的应用。最佳做法如下所示:

  • 在用户使用几次您的应用后展示第一个开屏广告。
  • 在用户等待您的应用加载时展示开屏广告。
  • 如果有加载屏幕位于开屏广告之下,并且加载屏幕在用户关闭广告之前已加载完毕,您可能需要通过 onAdDismissedFullScreenContent 事件处理脚本关闭加载屏幕。