向 Flutter 应用添加 AdMob 广告

1. 简介

要构建的内容

此 Codelab 将指导您向一款名为 Awesome Drawing Quiz(一款让玩家猜测图画中所描绘对象的游戏)的应用添加横幅广告、插页式广告和激励广告。

如果您在学习此 Codelab 时遇到任何问题(代码错误、语法错误、措辞含义不明),请通过 Codelab 左下角的报告错误链接报告相应问题。

要学习的内容

  • 如何配置 Google 移动广告 AdMob 插件
  • 如何在 Flutter 应用中植入横幅广告、插页式广告和激励广告

所需条件

  • Android Studio 4.1 或更高版本
  • Xcode 12 或更高版本(对于 iOS 开发)

您如何评价自己在 AdMob 方面的经验水平?

新手 中等 熟练

您如何评价自己在 Flutter 方面的经验水平?

新手 中等 熟练

2. 设置您的 Flutter 开发环境

您需要使用两款软件才能完成此 Codelab:Flutter SDK一款编辑器

您可使用以下任一设备学习此 Codelab:

  • 一台连接到计算机并设置为开发者模式的实体 AndroidiOS 设备。
  • iOS 模拟器(需要安装 Xcode 工具)。
  • Android 模拟器(需要在 Android Studio 中设置)。
  • 浏览器(需要 Chrome 才能进行调试)。
  • 对于 WindowsLinuxmacOS 桌面应用,您必须在打算实施部署的平台上进行开发。因此,如果您要开发 Windows 桌面应用,则必须在 Windows 上进行开发,才能使用相应的构建链。如需详细了解针对各种操作系统的具体要求,请访问 docs.flutter.dev/desktop

下载代码

下载 ZIP 文件后,解压其内容。您将会看到名为 admob-ads-in-flutter-master 的文件夹。

或者,您也可以通过命令行克隆 GitHub 代码库:

$ git clone https://github.com/googlecodelabs/admob-ads-in-flutter

该代码库包含两个文件夹:

  • android_studio_folder.pngstarter - 您在此 Codelab 中用作基础的起始代码。
  • android_studio_folder.pngcomplete - 此 Codelab 完成后的代码。

3. 设置 AdMob 应用和广告单元

由于 Flutter 是一个多平台 SDK,您需要在 AdMob 中添加同时适用于 Android 和 iOS 的应用和广告单元。

针对 Android 进行设置

如需针对 Android 进行设置,您需要添加 Android 应用并创建广告单元。

添加 Android 应用

  1. AdMob 控制台中,点击应用菜单中的添加应用
  2. 当系统询问“您是否已将应用发布到 Google Play 或 App Store?”时,点击
  3. 在应用名称字段中输入 Awesome Drawing Quiz,然后选择 Android 作为平台。

ddafee37a6f92229.png

  1. 您不必启用用户指标就能完成此 Codelab。不过,我们仍建议您启用,以便更详细地了解用户行为。点击添加,完成此流程。

b918bf44362813a9.png

创建广告单元

如需开始向 AdMob 添加广告单元,请按以下步骤操作:

  1. AdMob 控制台的应用菜单中选择 Awesome Drawing Quiz
  2. 点击广告单元菜单。

横幅广告

  1. 点击添加广告单元按钮。
  2. 选择横幅广告作为格式。
  3. 广告单元名称字段中输入 android-adq-banner
  4. 点击创建广告单元,完成此流程。

插页式广告

  1. 点击添加广告单元按钮。
  2. 选择插页式广告作为格式。
  3. 广告单元名称字段中输入 android-adq-interstitial
  4. 点击创建广告单元,完成此流程。

激励广告

  1. 点击添加广告单元按钮。
  2. 选择激励广告作为格式。
  3. 广告单元名称字段中输入 android-adq-rewarded
  4. 保留奖励设置的默认值。
  5. 点击创建广告单元,完成此流程。

新广告单元通常要过几个小时才能投放广告。

如果您想立即测试广告的行为,请使用“Android 应用 ID/广告单元 ID”和“iOS 应用 ID/广告单元 ID”表格中列出的测试应用 ID 和广告单元 ID。

针对 iOS 进行设置

如需针对 iOS 进行设置,您需要添加 iOS 应用并创建广告单元。

添加 iOS 应用

  1. AdMob 控制台中,点击应用菜单中的添加应用
  2. 当系统询问“您是否已将应用发布到 Google Play 或 App Store?”时,点击
  3. 在应用名称字段中输入 Awesome Drawing Quiz,然后选择 iOS 作为平台。

93e7f9f114232402.png

  1. 您不必启用用户指标就能完成此 Codelab。不过,我们仍建议您启用,以便更详细地了解用户行为。点击添加,完成此流程。

b918bf44362813a9.png

创建广告单元

如需添加广告单元,请按以下步骤操作:

  1. AdMob 控制台的应用菜单中选择 Awesome Drawing Quiz 应用。
  2. 点击广告单元菜单。

横幅广告

  1. 点击添加广告单元按钮。
  2. 选择横幅广告作为格式。
  3. 广告单元名称字段中输入 ios-adq-banner
  4. 点击创建广告单元,完成此流程。

插页式广告

  1. 点击添加广告单元按钮。
  2. 选择插页式广告作为格式。
  3. 广告单元名称字段中输入 ios-adq-interstitial
  4. 点击创建广告单元,完成此流程。

激励广告

  1. 点击添加广告单元按钮。
  2. 选择激励广告作为格式。
  3. 广告单元名称字段中输入 ios-adq-rewarded
  4. 保留奖励设置的默认值。
  5. 点击创建广告单元,完成此流程。

新广告单元通常要过几个小时才能投放广告。

如果您想立即测试广告的行为,请使用下方表格中列出的测试应用 ID 和广告单元 ID。

可选:使用测试 AdMob 应用和广告单元

如果您希望按照此 Codelab 操作,而不是自行创建新的应用和广告单元,可以使用下方表格中列出的测试 AdMob 应用 ID 和广告单元 ID。

Android 应用 ID/广告单元 ID

项目

应用 ID/广告单元 ID

AdMob 应用 ID

ca-app-pub-3940256099942544~3347511713

横幅广告

ca-app-pub-3940256099942544/6300978111

插页式广告

ca-app-pub-3940256099942544/1033173712

激励广告

ca-app-pub-3940256099942544/5224354917

iOS 应用 ID/广告单元 ID

项目

应用 ID/广告单元 ID

AdMob 应用 ID

ca-app-pub-3940256099942544~1458002511

横幅广告

ca-app-pub-3940256099942544/2934735716

插页式广告

ca-app-pub-3940256099942544/4411468910

激励广告

ca-app-pub-3940256099942544/1712485313

如需详细了解测试广告,请参阅 Android 测试广告iOS 测试广告开发者文档。

4. 添加 Google 移动广告 Flutter 插件

Flutter 使用插件来提供各种平台特有的服务。借助插件,您可以访问各个平台上的服务和 API。

google_mobile_ads 插件让您可使用 AdMob API 来加载和展示横幅广告、插页式广告、激励广告和原生广告。

由于 Flutter 是一个多平台 SDK,google_mobile_ads 插件既适用于 iOS 也适用于 Android。因此,如果您将该插件添加到 Flutter 应用,Android 版和 iOS 版 AdMob 内嵌广告应用都会使用该插件。

以依赖项形式添加 Google 移动广告插件

如需从 AdMob 内嵌广告项目访问 AdMob API,请以依赖项形式将 google_mobile_ads 添加到项目根目录下的 pubspec.yaml 文件中。

pubspec.yaml

...
environment:
  # TODO: Update the minimum sdk version to 2.12.0 to support null safety.
  sdk: ">=2.17.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  google_fonts: ^3.0.1

  # TODO: Add google_mobile_ads as a dependency
  google_mobile_ads: ^1.2.0

...

点击 Pub get,在 Awesome Drawing Quiz 项目中安装该插件。

9ce73858eedbd8fc.png

更新 AndroidManifest.xml (Android)

  1. 在 Android Studio 中打开 android/app/src/main/AndroidManifest.xml 文件。
  2. 添加一个名为 com.google.android.gms.ads.APPLICATION_ID<meta-data> 标记,以添加 AdMob 应用 ID。例如,如果您的 AdMob 应用 ID 为 ca-app-pub-3940256099942544~3347511713,则需要将以下几行代码添加到 AndroidManifest.xml 文件。

AndroidManifest.xml

<manifest>
    ...
    <application>
       ...
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-3940256099942544~3347511713"/>
    </application>

</manifest>

更新 Info.plist (iOS)

  1. 在 Android Studio 中打开 ios/Runner/Info.plist 文件。
  2. 添加一个字符串值为您的 AdMob 应用 ID 的 GADApplicationIdentifier 键。例如,如果您的 AdMob 应用 ID 为 ca-app-pub-3940256099942544~1458002511,则需要将以下几行代码添加到 Info.plist 文件。

ios/Runner/Info.plist

...
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string>
...

5. 为广告添加辅助类

lib 目录下新建一个名为 ad_helper.dart 的文件。然后,实现 AdHelper 类,该类会提供对 Android 和 iOS 都适用的 AdMob 应用 ID 和广告单元 ID。

请务必将 AdMob 应用 ID (ca-app-pub-xxxxxx~yyyyy) 和广告单元 ID (ca-app-pub-xxxxxxx/yyyyyyyy) 替换为您在上一步中创建的 ID。

lib/ad_helper.dart

import 'dart:io';

class AdHelper {

  static String get bannerAdUnitId {
    if (Platform.isAndroid) {
      return '<YOUR_ANDROID_BANNER_AD_UNIT_ID>';
    } else if (Platform.isIOS) {
      return '<YOUR_IOS_BANNER_AD_UNIT_ID>';
    } else {
      throw UnsupportedError('Unsupported platform');
    }
  }

  static String get interstitialAdUnitId {
    if (Platform.isAndroid) {
      return '<YOUR_ANDROID_INTERSTITIAL_AD_UNIT_ID>';
    } else if (Platform.isIOS) {
      return '<YOUR_IOS_INTERSTITIAL_AD_UNIT_ID>';
    } else {
      throw UnsupportedError('Unsupported platform');
    }
  }

  static String get rewardedAdUnitId {
    if (Platform.isAndroid) {
      return '<YOUR_ANDROID_REWARDED_AD_UNIT_ID>';
    } else if (Platform.isIOS) {
      return '<YOUR_IOS_REWARDED_AD_UNIT_ID>';
    } else {
      throw UnsupportedError('Unsupported platform');
    }
  }
}

如果您希望使用测试 AdMob 应用 ID 和测试广告单元 ID,请使用以下代码段。

lib/ad_helper.dart

import 'dart:io';

class AdHelper {

  static String get bannerAdUnitId {
    if (Platform.isAndroid) {
      return 'ca-app-pub-3940256099942544/6300978111';
    } else if (Platform.isIOS) {
      return 'ca-app-pub-3940256099942544/2934735716';
    } else {
      throw new UnsupportedError('Unsupported platform');
    }
  }

  static String get interstitialAdUnitId {
    if (Platform.isAndroid) {
      return "ca-app-pub-3940256099942544/1033173712";
    } else if (Platform.isIOS) {
      return "ca-app-pub-3940256099942544/4411468910";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }

  static String get rewardedAdUnitId {
    if (Platform.isAndroid) {
      return "ca-app-pub-3940256099942544/5224354917";
    } else if (Platform.isIOS) {
      return "ca-app-pub-3940256099942544/1712485313";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }
}

6. 初始化 Google 移动广告 SDK

加载广告之前,您需要初始化 Google 移动广告 SDK。打开 lib/home_route.dart 文件,并修改 _initGoogleMobileAds() 以在加载首页之前初始化 SDK。

请注意,您需要将 _initGoogleMobileAds() 方法的返回值类型从 Future<dynamic> 更改为 Future<InitializationStatus>,以在完成后获取 SDK 初始化结果。

home_route.dart

// TODO: Import google_mobile_ads.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';

import 'package:flutter/material.dart';

...

class HomeRoute extends StatelessWidget {

  ...

  Future<InitializationStatus> _initGoogleMobileAds() {
    // TODO: Initialize Google Mobile Ads SDK
    return MobileAds.instance.initialize();
  }
}

7. 添加横幅广告

在这一部分,您将在游戏屏幕顶部展示一个横幅广告,如下方屏幕截图所示。

276b4cfa283ea6c7.png

  1. 打开 lib/game_route.dart 文件,然后导入 ad_manager.dart
  2. 添加以下几行代码,以导入 ad_helper.dartgoogle_mobile_ads.dart

lib/game_route.dart

...

// TODO: Import ad_helper.dart
import 'package:awesome_drawing_quiz/ad_helper.dart';

// TODO: Import google_mobile_ads.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';

class GameRoute extends StatefulWidget {
  ...
}
  1. _GameRouteState 类中,为横幅广告添加以下成员。

lib/game_route.dart

class _GameRouteState extends State<GameRoute> implements QuizEventListener {

  ...

  // TODO: Add _bannerAd
  BannerAd? _bannerAd;

  ...
}
  1. initState() 方法中,为 320x50 横幅广告 (AdSize.banner) 创建并加载 BannerAd。请注意,广告事件监听器已配置为在加载广告后更新界面 (setState())。

lib/game_route.dart

@override
void initState() {
  ...

  // TODO: Load a banner ad
  BannerAd(
    adUnitId: AdHelper.bannerAdUnitId,
    request: AdRequest(),
    size: AdSize.banner,
    listener: BannerAdListener(
      onAdLoaded: (ad) {
        setState(() {
          _bannerAd = ad as BannerAd;
        });
      },
      onAdFailedToLoad: (ad, err) {
        print('Failed to load a banner ad: ${err.message}');
        ad.dispose();
      },
    ),
  ).load();
}
  1. 修改 build() 方法,以在适当时展示横幅广告。

lib/game_route.dart

@override
Widget build(BuildContext context) {
  return Scaffold(
    ...
    body: SafeArea(
      child: Stack(
        children: [
          Center(
            ...
          ),
          // TODO: Display a banner when ready
          if (_bannerAd != null)
            Align(
              alignment: Alignment.topCenter,
              child: Container(
                width: _bannerAd!.size.width.toDouble(),
                height: _bannerAd!.size.height.toDouble(),
                child: AdWidget(ad: _bannerAd!),
              ),
            ),
        ],
      ),
    ),
    ...
  );
}
  1. 通过在 dispose() 回调方法中调用 BannerAd.dispose() 方法,释放与 BannerAd 对象关联的资源。

lib/game_route.dart

@override
void dispose() {
  // TODO: Dispose a BannerAd object
  _bannerAd?.dispose();

  ...

  super.dispose();
}

大功告成!运行项目,并开始新游戏。广告加载完毕后,您将在屏幕顶部看到横幅广告。

276b4cfa283ea6c7.png

8. 添加插页式广告

在这一部分,您将在游戏(共 5 个关卡)结束后展示一个插页式广告。

  1. 打开 lib/game_route.dart 文件
  2. _GameRouteState 类中,为插页式广告添加以下成员和方法。

请注意,广告事件监听器已配置为检查广告是否已准备就绪 (onAdLoaded()onAdFailedToLoad()),并在广告关闭时显示应用的主屏幕 (onAdDismissedFullScreenContent())

lib/game_route.dart

class _GameRouteState extends State<GameRoute> implements QuizEventListener {

  ...

  // TODO: Add _interstitialAd
 InterstitialAd? _interstitialAd;

  // TODO: Implement _loadInterstitialAd()
  void _loadInterstitialAd() {
    InterstitialAd.load(
      adUnitId: AdHelper.interstitialAdUnitId,
      request: AdRequest(),
      adLoadCallback: InterstitialAdLoadCallback(
        onAdLoaded: (ad) {
          ad.fullScreenContentCallback = FullScreenContentCallback(
            onAdDismissedFullScreenContent: (ad) {
              _moveToHome();
            },
          );

          setState(() {
            _interstitialAd = ad;
          });
        },
        onAdFailedToLoad: (err) {
          print('Failed to load an interstitial ad: ${err.message}');
        },
      ),
    );
  }

  ...
}
  1. 在此 Codelab 中,系统会在用户完成 5 个关卡后展示一个插页式广告。为了尽可能减少不必要的广告请求,请在用户玩到第 3 关时请求广告。

onNewLevel() 方法中,添加以下几行代码。

lib/game_route.dart

@override
void onNewLevel(int level, Drawing drawing, String clue) {
  ...

  // TODO: Load an Interstitial Ad
  if (level >= 3 && _interstitialAd == null) {
    _loadInterstitialAd();
  }
}
  1. 游戏结束后,系统会显示游戏得分对话框。在用户关闭该对话框后,系统会将用户转到 Awesome Drawing Quiz 的主屏幕。

由于插页式广告应在屏幕过渡间隙展示,我们将在用户点击 CLOSE 按钮时展示插页式广告。

按如下方式修改 onGameOver() 方法:

lib/game_route.dart

@override
void onGameOver(int correctAnswers) {
  showDialog(
    context: _scaffoldKey.currentContext,
    builder: (context) {
      return AlertDialog(
        title: Text('Game over!'),
        content: Text('Score: $correctAnswers/5'),
        actions: [
          FlatButton(
            child: Text('close'.toUpperCase()),
            onPressed: () {

              // TODO: Display an Interstitial Ad
              if (_interstitialAd != null) {
                _interstitialAd?.show();
              } else {
                _moveToHome();
              }
            },
          ),
        ],
      );
    },
  );
}
  1. 通过在 dispose() 回调方法中调用 InterstitialAd.dispose() 方法,释放与 InterstitialAd 对象关联的资源。

lib/game_route.dart

@override
void dispose() {
  ...

  // TODO: Dispose an InterstitialAd object
  _interstitialAd?.dispose();

  ...

  super.dispose();
}

大功告成!运行项目并完成游戏。如果插页式广告已加载完毕,您将在点击得分对话框中的 CLOSE 按钮后看到一个插页式广告。

c546e438c405e941.gif

9. 添加激励广告

在这一部分,您将添加一个激励广告,以便为用户提供额外提示作为奖励。

  1. 打开 lib/game_route.dart 文件
  2. _GameRouteState 类中,为激励广告添加成员,然后实现 _loadRewardedAd() 方法。请注意,该方法会在广告关闭后 (onAdDismissedFullScreenContent) 加载另一个激励广告,以尽早缓存广告。

lib/game_route.dart

class _GameRouteState extends State<GameRoute> implements QuizEventListener {

  ...

  // TODO: Add _rewardedAd
  RewardedAd? _rewardedAd;

  // TODO: Implement _loadRewardedAd()
  void _loadRewardedAd() {
    RewardedAd.load(
      adUnitId: AdHelper.rewardedAdUnitId,
      request: AdRequest(),
      rewardedAdLoadCallback: RewardedAdLoadCallback(
        onAdLoaded: (ad) {
          ad.fullScreenContentCallback = FullScreenContentCallback(
            onAdDismissedFullScreenContent: (ad) {
              setState(() {
                ad.dispose();
                _rewardedAd = null;
              });
              _loadRewardedAd();
            },
          );

          setState(() {
            _rewardedAd = ad;
          });
        },
        onAdFailedToLoad: (err) {
          print('Failed to load a rewarded ad: ${err.message}');
        },
      ),
    );
  }

  ...
}
  1. initState() 方法中调用 _loadRewardedAd(),以在游戏开始时请求激励广告。

lib/game_route.dart

class _GameRouteState extends State<GameRoute> implements QuizEventListener {

  ...

  @override
  void initState() {
    ...

    // COMPLETE: Load a Rewarded Ad
    _loadRewardedAd();
  }

  ...
}
  1. 让用户能够通过点击悬浮操作按钮观看激励广告。只有在用户未在当前关卡中使用提示,并且激励广告已加载完毕的情况下,该按钮才会显示。

按以下方式修改 _buildFloatingActionButton() 方法,以便显示悬浮操作按钮。请注意,如果返回 null,则会在屏幕中隐藏该按钮。

请注意,onUserEarnedReward 是激励广告中最重要的广告事件。该事件会在用户符合奖励条件(例如,看完某个视频)时触发。

在此 Codelab 中,我们从该回调中调用 QuizManager.instance.useHint() 方法,该方法会在提示字符串中再显示一个字符。应用会在 onAdClosed 回调中重新加载一个激励广告,以确保广告尽早准备就绪。

lib/game_route.dart

Widget? _buildFloatingActionButton() {
  // TODO: Return a FloatingActionButton if a rewarded ad is available
  return (!QuizManager.instance.isHintUsed && _rewardedAd != null)
      ? FloatingActionButton.extended(
          onPressed: () {
            showDialog(
              context: context,
              builder: (context) {
                return AlertDialog(
                  title: Text('Need a hint?'),
                  content: Text('Watch an Ad to get a hint!'),
                  actions: [
                    TextButton(
                      child: Text('cancel'.toUpperCase()),
                      onPressed: () {
                        Navigator.pop(context);
                      },
                    ),
                    TextButton(
                      child: Text('ok'.toUpperCase()),
                      onPressed: () {
                        Navigator.pop(context);
                        _rewardedAd?.show(
                          onUserEarnedReward: (_, reward) {
                            QuizManager.instance.useHint();
                          },
                        );
                      },
                    ),
                  ],
                );
              },
            );
          },
          label: Text('Hint'),
          icon: Icon(Icons.card_giftcard),
        )
      : null;
}
  1. 通过在 dispose() 回调方法中调用 RewardedAd.dispose() 方法,释放与 RewardedAd 对象关联的资源。

lib/game_route.dart

@override
void dispose() {
  ...

  // TODO: Dispose a RewardedAd object
  _rewardedAd?.dispose();

  ...

  super.dispose();
}

大功告成!运行项目并玩游戏。激励广告加载完毕后,您将在屏幕底部看到一个提示按钮。点击 Hint 按钮,即可获得额外提示。

4a114d243ae3e71d.gif

10. 全部完成!

您已完成此 Codelab。您可以在 android_studio_folder.pngcomplete 文件夹中找到此 Codelab 完成后的代码。

如需了解如何植入横幅内嵌广告和原生内嵌广告,请参阅向 Flutter 应用添加 AdMob 横幅内嵌广告和原生内嵌广告 Codelab。

如需了解更多信息,请尝试学习其他 Flutter Codelab