3詳解AdMobAndroidiOS広告adMobFirebase広告に関する実装をしてきて、やっと納得のいく実装ができた気がしたので備忘録的にまとめようと思います。今後モバイルアプリに広告の導入を検討されている方の手助けになれば幸いです。ユーザー影響大きいのでご利用は計画的に...下記のような実装は経験できたので関連でもし質問などあれば回答できることもあるかもしれません。全部知っておかないと開発できないわけではないと思います。本記事の用語集としてリンク先に一読はおすすめします収益に関わるパフォーマンスチューニングのTips業種やアプリの規模、採用しているアドネットワークによって全く異なるので言及しません。AdMob管理画面の使い方ちゃんと触ってません!マルチモジュールを使った広告管理やりたいことに近いことを実現されている記事がありましたhttps://phicdy.hatenablog.com/entry/admob-with-multi-moduleこういうのやってみたい....CoroutineFlowを使った広告管理こういうのやってみたい....実装時の開発以外でのつらみやっていて色々思うことはありましたが割愛します。共感したスライドがあったので置いておきますね。https://speakerdeck.com/ryokosuge/guang-gao-shi-zhuang-falseku-simi広告SDKの内部実装について広告のSDKは大半が実装部分は難読化されていて読み解くことが難しいです。以下でつらつら書いていることも動作確認した時の動きだったりおそらくこういうことだろう、という考察で説明しているところも多いので実際は間違っているということもあるかもしれませんのでご理解ください。基本編おすすめの実装、ハマりそうなポイントなど広告の表示AdMobは機能自体はそこまで多くないので公式ドキュメントを一周してもらえればある程度の使い方を理解するのは簡単だと思います。テスト用の枠IDは公式で用意されているのでアプリを識別するためのAppIDが発行できればすぐ表示まで確認できると思います。広告はAACのViewModelで管理しない方が良い?上でも書いたのですが、広告をインスタンス化する時はActivityContextが必要になります。AndroidViewModelから取れるApplicationクラスのContextを使うことはできません。外からActivityContextを入れてもViewModelはActivity/Fragmentよりも長生きするのでメモリーリークの原因になり得ます。付けてON_DESTROYのイベントを受け取った時に広告インスタンスを破棄したりすればまあいけるのかもしれないですね。)ただ、バナー広告は基本的にキャッシュされることを想定されていないので、Fragmentをまたいで広告インスタンスを使い回すのはあまりオススメしません(広告SDKが計測しているimpの定義ともずれてしまいがち)。ViewModelを使うメリットがあまり活かせないのかなと思っています。destroy()が用意されていて、広告を表示しているUIのライフサイクルに応じてそれぞれのメソッドを叩く必要があります。この3つのメソッドはAdMobに関わらず他社の大半の広告SDKでも同じように設計されている印象です。各メソッドの役割は例えば以下のようなことがあると言えます(これらは各社SDKの仕様に左右されます。叩いておいた方がいいことには変わりないという認識は持っておいた方が良いでしょう)。resume()広告のオートリフレッシュ間隔の計測を再開するImpression(収益に関わる)の計測を再開するメディエーション実装クラスにライフサイクルの変更イベントを通知するpause()広告のオートリフレッシュ間隔の計測を中断するImpression(収益に関わる)の計測を中断するメディエーション実装クラスにライフサイクルの変更イベントを通知するdestroy()広告のインスタンスを破棄するメディエーション実装クラスにライフサイクルの変更イベントを通知するこれらは記述漏れが発生しやすいので注意が必要です。Androidでは広告の管理クラスにLifecycleObserverをセットしてライフサイクルの処理も管理クラス内部で処理しておくとイマドキな気がします。実装メモ広告で収益を発生させるにはざっくりクリックされると多く見られるの2種類があるクリックされたことと画面に見えているというのは広告のView内で計測しているので外からはいじれないAdMobでは広告が見えていない時にオートリフレッシュのタイミングを迎えるとリフレッシュはスキップされるような動きをするdestroy()を呼んだ広告インスタンスはリロードできなくなるので作り直すしかありませんメディエーションはどう動くかメディエーションというのは、AdMob以外の広告SDKから表示される広告コンテンツをAdMobのAdViewの中で表示できる機能です。TwitterやFacebook、Amazonなど広告SDKを提供している企業が抱える広告コンテンツもAdMobの中で表示できるようになります。AdMob自身が持っている広告コンテンツも様々な広告配信ネットワークから価値があるものが優先して表示されるのですが、ただこれだとAdMobの配信システムに大きく依存しているので、アドネットワーク内に競合を増やして収益獲得の効率化を目指そうよというものです(ヘッダービディングも思想は同じようなものだと認識してますが違うのかな?)。View)みたいなことをすれば中身を上書きできるよというだけです。(ちなみにAdMobのネイティブ広告はViewGroupではなくて、UnifiedNativeAdというデータクラスを使っていて、パーツごとに情報をセットするコンバート処理を挟むことでメディエーションを実現していると言えます)それでは実装を解説していきましょう。メディエーション実装クラスを作るCustomEventBannerを継承していることで他社の広告SDKのメディエーションをしたいんだなとAdMob側に認識してもらえる準備になります。以下のようなメソッドを持っています。onAdLeftApplication();}requestBannerAd内で行なった他社SDKの読み込み処理やイベントに応じていずれかのメソッドを叩く必要があります。View)されるようなイメージです。errorCode)が呼ばれるとAdMobが違うメディエーション先へ広告を取りに行くというわけです。実装メモLogcatを見る感じ、広告の読み込み時に枠IDごとに設定されている管理画面上のClassNameの文字列とメディエーション実装クラスの絶対パスが一致しているものがあるかをチェックしている挙動も見て取れました。AdMob管理画面でClassNameとParameterを設定する1で作ったメディエーション実装クラスのrequestBannerAdが呼ばれるためには、枠IDごとにどんなメディエーションが設定されているかを管理画面上で定義しておく必要があります。それを紐付けるのがClassNameとParameterという概念です。1のようなクラスを作った場合は対象の枠IDに対して以下のように設定することになります。文字列を1つだけ設定可能iOSは実装クラスのクラス名だけでいけるみたいですねAdMobの管理画面で設定してみてください!上記2点で最低限のメディエーションの実装は完了です!あとは管理画面で複数のメディエーションに対してどういう順序でメディエーションするとより収益化が見込めるかという視点で運用しながらチューニングをしていくと良いでしょう。最初の頃はこの実装だけでなぜ動くのか不思議なはずです。Usageしてもどこからも使われていません。「なんで参照されていないクラスのrequestBannerAdが呼ばれるねん!!!」と突っ込みたくなりますが、実装自体は合っています。内部実装が読み取れないので具体的な解説は難しいのですが、リフレクションとかでうまいことやったりしてるんでしょう(適当)補足ですが、requestBannerAdのそれぞれの引数と実装者が外から設定した場合の値の紐付き方を考察混じりになりますが書いてみます。AdViewのload時にセットしたAdRequestの中のaddCustomEventExtrasBundleで対象のクラスにセットしたBundle実装メモカスタムイベントとメディエーションはだいたい同じ意味ですhttps://support.google.com/admob/answer/3083407?hl=ja&ref_topic=7383089Parameterにはメディエーション先で使いたい何かしらの文字列を管理画面から送りたい要件があるときに使うと良いです画面ごとにAdMobの枠IDを発行して収益性を比較したいのでメディエーション先の枠IDも同様に発行して柔軟に変更させたい。その場合はParameterにそれぞれの枠IDを設定して管理画面側で動的に読み込む枠IDを変更できるバナー広告とレクタングル広告で同じ広告SDKに対してメディエーションしたい。メディエーション実装クラスに外から値を受け渡ししたい実際に運用していて困るのが、リリース後に管理画面の設定やメディエーション内の挙動を変えたい場合です。枠IDやメディエーションは一度リリースしてしまうと古いバージョンのアプリの動きを担保したまま運用していくのが難しくなります。これはアプリのソースコード上でも管理画面上でも同様です。どれだけ意識して開発していても広告SDK側の破壊的なアップデートや先方との契約の都合など変更せざるを得ないケースはいくつか挙げられるでしょう。メディエーションの実装に関してはどのような柔軟性を持たせておけばいいのか。管理画面で設定したParameterを受け取れるserverParameterを使うようにするRemoteConfigを使って必要なパラメーターをアプリ側でcustomEventExtrasに詰めて渡すcustomEventExtrasを使った値の受け渡しは以下のように実装します。}).build()adView.loadAd(adRequest)絶対にやらないほうがいいのは決め打ちで固定の枠IDや特定の文字列をメディエーション実装クラス内で使ってしまうということで、上の2つのやり方に関しては実装しやすい方を選択すると良いでしょう。個人的には、serverParameterを使う方法は初めて実装を見た人がどうやって動いているのか理解しづらいと思うので、RemoteConfigを使った処理に寄せてしまった方が良いと考えています。customEventExtrasに詰める箇所はJavaもしくはKotlinで書くことになるので実装が追いやすいし、SampleBannerAdCustomEventのような実行クラスへの参照を作ることができます。実務でつまづいたこと、運用してみて良かったこと、分かったことなどメディエーション実装には大きく2種類あったどういうことかというと、メディエーションするにあたり必要な実装は1つしかないのですが、自前で実装する場合とすでに実装されたjar/aarファイルやGradleで取り込めるライブラリが提供されている場合があります。提供品は圧倒的に手軽で、ライブラリを入れるだけでアプリ側の作業が完了したりするのですが、内部実装をいじれないので要件によってはうまく適合しないケースもあります。提供品がある広告SDKであれば一旦そちらを使ってみて、うまくいかなかった時に自前で実装するというアプローチが良いように思います(最近はもうメディエーションを自前実装する機会は多くないかなと思います)。最近はGoogleがAdMobに組み込むための各社SDK用のアダプターを公開してくれているので実装がとても楽になってきていると言えます。AdMobのメディエーションアダプター一覧https://developers.google.com/admob/android/mediate?hl=jaこれまでに自前実装をしたケースメディエーションしたい広告SDKがアダプターを提供していなかったメディエーション先で特定のキーワードを設定して広告をロードしたかったが、提供されているアダプターが対応していなかったファーストリクエストの広告がNO_AD(在庫がない)を返してきたらフォーマットの異なる広告を読み込みたい要件があった広告フォーマットが違うのでメディエーションで対応できないケース実装メモアダプターを利用する場合は管理画面に設定するClassNameが他社のパッケージのパスになり違和感がありますが間違ってはないのでご安心ください広告テストがしづらいので枠IDの管理を考えるこれは運用してみると出てくる問題の一部で、例えば以下のような問題がありました。対策と合わせて書いてみます。TestModeを使用せずにDev環境やQAで広告をテストしすぎると垢BAN食らって2週間ほどアカウント停止されがち問題AdMobでTestModeを使うには、一度ビルドしてログから一意なKeyを取得してきて都度設定する必要があります。QAのために社内全ての端末のIDを管理するのは大規模なプロジェクトほど骨が折れます。判定の仕組みは全く不明ですが、StoreにリリースされているApplicationIdと異なるビルドやDebugビルドからTestModeを設定せずに広告を読み込みすぎる(おそらくクリックすると更に良くないです)とAdMobのアカウントが停止されて問答無用で広告も読み込めなくなるし管理画面もいじれなくなります。本番にリリースされている状態で垢BANされると相当クリティカルな問題ですので、広告の挙動テストではせめてReleaseビルドを使う、不正に収益を得ることがまずいのでクリックするテストは避けるは心がけておいたほうが良いでしょう。Debugビルドでは広告を表示しない仕組みがあると安心ですね。広告機能のリリース後に広告に関する機能追加をしようとした時、本番の枠IDの設定をテストのためにいじりづらい問題これはもう解決策は明確で、枠IDにもProduction、Staging、Developの概念を取り入れると良いです。自社では下記のような思想で運用していました。ここで機能開発を行う。メディエーション先の広告が表示されているのかわかりづらい問題2の案でStaging枠でメディエーション設定は自由に弄れるようになるのですが、実際にメディエーションが成功しているかを目視で判断するのは難しいケースもあります。広告出稿側がAdMob以外にも同じ広告を提供しているケースがあるためです。そんな時は以下のような判断方法を検討してみてください。意図したメディエーションを試すのに手間がかかる問題2で解説したStaging枠を更に拡張させるやり方の提案です。管理画面で設定を都度変更するのは反映ラグ(1~2時間程度)もあり、複数人からの一斉の要望に答えづらい問題があります。QAを置いている企業だと連携コストがかかってしまうので、弊社ではStaging枠IDをメディエーション分だけ複数発行するというやり方を取っています。1つの枠IDに対して例えば3つのメディエーションが紐づいていると目視での確認がしんどくなってくるので、1つの枠IDに対して1つのメディエーションというセットを複数作って参照する枠IDに応じて表示される広告が切り替わるような仕組みにしています。アプリのデバッグ設定などで動的に枠IDを切り替えられるようにしておくと即反映できるし便利ですね!実装メモ設定してすぐの時はある程度広告リクエストを送らないとメディエーションが反応しないので辛抱強くリクエスト送ってみましょう白板を防ぐ広告コンテンツが読み込めない、在庫がない、広告の読み込み中などの状態でユーザーに広告のViewが見えてしまうと真っ白に見えるようになっています。これは白板(しろいた/はくばん)と呼ばれていて、これが見えている状態というのは、ユーザー側にも収益的に見てもベネフィットが限りなく小さいです。SDK側の仕様に大きく左右されるということもあり、この白板をゼロにすることは難しいので、なるだけ起きない方法を考えていきたいと思います。広告の読み込み中もしくはエラーになった場合は広告表示領域を隠す読み込み中かどうかはAdMobでは、AdView.isLoadingというメソッドがあり、状態を取得することが可能です。エラー状態もAdListenerのonAdFailedToLoadで受け取れるので状態管理の仕組みを作れば簡単に判定できます。広告表示領域はデフォルトGoneにしておいて、読み込みに成功したらVisibleに切り替えるのが良さそうです。}}ちなみにこの辺の実装は各広告SDKの仕様によって書き方が異なるので、状態管理を内部で行うことも含めて下記のようなInterfaceを切っておくと便利です。}}実装メモオートリフレッシュが有効な場合、AdListenerをセットしていた場合はオートリフレッシュ間隔でコールバックが返ってきます。成功->失敗のようなケースに広告が白板になることもあるので考慮できると良いです。Adの実行クラスはDaggerなどでManagerクラスに注入できるようにすればテストが簡単にできそうです。プリフェッチしておくバナー広告でキャッシュがNGと書いてあるものの、見えることがほぼ確実な広告を先に読み込んでおいてもバチは当たらないと思います。プリフェッチを実装した公式サンプルもあります。このサンプルおそらく問題があるケースがあって、オートリフレッシュが有効な広告の場合だとプリフェッチがループしてしまいます。}}サンプルコードのように広告実装を抽象化している場合はCallbackを、AdMobを使っている場合はAdListenerをnullにしてあげましょう。非同期処理がループしているので画面のライフサイクルに応じて処理を止めてあげる仕組みがあるとさらに良いかなと思います。(任意)リスト内で広告を取り扱う場合の白板防止策リスト内で広告を扱う実装方法はまだ確立したベストプラクティスがないので実装方法によりますが、プリフェッチを採用していて、かつList型で広告を管理している場合、以下のようなUtilメソッドを作っておくと便利です。nextAd}これで白板が見えてしまう可能性は大きく下がるはずです!これでもAdがnullで返ってきてしまう場合は広告表示領域をGONEにすると良いでしょう。Testingを使えば広告のパフォーマンス比較ができる例えばバナー広告とネイティブ広告AdMobを使っていると収益性やインプレッション数もFirebase上でわかるので比較しやすいTargetingにPredictionsを活用できて非常にイケている広告の詳細な実装はInterfaceで隠してテスタブル設計にしておく最近のdex.fmでも話題に上がってたので少しテンション上がりましたAdViewはリロード時に再利用できるか、作り直すべきかAdViewはdestroy()を呼ばない限りはリロードが可能みたいですリングバッファ管理できますね!広告SDKによっては作り直さないとダメなやつもあるっぽいです広告は比較的プレミアム機能と絡むことが多いので、destroy時とcreate時の処理を外から叩けるようにしておくと良いと思いますたまーーーーーに行儀の悪い広告コンテンツだと、画面に見えていなくても、広告の読み込みが成功していると画面を奪ったり、動画広告であれば裏で音声を流し始めたりするものもあるので、プリフェッチの取り扱いには気をつけるのと、例えばプレミアム機能が有効なユーザーはそもそも広告を読み込まない、という設計にするべきです。TestingとPredictionsの連携がアツいできるだけ公式の実装から外れないようにするメディエーションは各社SDKがアダプターを提供してくれているかをまず探すバナー広告はキャッシュしすぎないようにする自前で実装するメディエーション用のクラスはパッケージを安易に移動させないように気をつけるメディエーションをたくさん付けるよりもパフォーマンスチューニングをうまくやるほうが有益RequestクラスとViewクラスをそれぞれ使いまわしてもいいかはSDKごとに確認するユーザーに悪影響がある分、良い実装をして出来る限りストレスは軽減してあげる努力をしていきましょう最後に長らく読んでいただきありがとうございました!最初は取っつきづらい領域なんですが、実装方法を理解してしまえば拡張もしやすく、色々な要件にも柔軟に対応できて収益の最大化に貢献できるはずなのでネット上にもっと情報が増えてくれると良いなぁと思っています。広告のSDKはAndroidXに対応されてなかったりJavaのままだったりSDKによって挙動が大きく違ったりと最新のトレンドと比べると遅れているなぁと感じますが、技術としてはとても面白いと思うのでどんどん盛り上がってくれるといいなぁと切に願っています!MoPubはAndroidX対応をすでに諦めているっぽいhttps://github.com/mopub/mopub-android-sdk/issues/311そして、リストの中で広告を表示させたい要件ってあると思うんですが、公式のBannerRecyclerViewSampleはリストのデータをObject型で管理してたりと苦しい感じだし、ViewHolderにAdViewを設定してonBindのタイミングで読み込みを開始するだけだと読み込みのレイテンシで白板の状態が見えてしまって見栄えが良くないです。いうことで良い感じにリスト内でも動くサンプルを作ってみました。https://github.com/FujiKinaga/AdMobSampleまだこれがベストプラクティスな実装ではないと思っているのでアドバイスなども頂けると大変嬉しく思います!個人的にはRecyclerViewPoolをうまく扱えるともっと良い感じになるのかなと思ったりします。upLoginユーザーは見つかりませんでした。