MoPubのRewarded Video実装をする時など、ドキュメントを読んでいると下記のような記述に遭遇する:
Step 5. Optionally Implement Lifecycle Callbacks Network SDKs need to be notified of changes in the activity lifecycle so they can keep track of the current activity and load and show rewarded videos. For example, you can add lifecycle callbacks to all of your activities (including your Launcher Activity) that will load or show rewarded videos. For Mediation SDKs that track Activity lifecycle events, you must notify MoPub when those events happen, so that MoPub can pass them onto mediated network SDKs, as shown:
MoPubの提供する Lifecycle Callbacks
と称するAPI郡に関する記述なのだが、 "Optionally" と書いてあるし、実装しなくても動作する場合があるし、何より複数に渡る画面へこれを埋め込むのは現実的ではない。そんなわけで無視して進めてしまいそうになる。
しかし "Network SDKs need to be notified of changes in the activity lifecycle" とあるように、実はメディエーション機能を使う場合には実態として必須と言って過言ではない。
Custom SDK Network側ではどのように見えてくるか
今回はリワード広告を例に取る。
前提として、MoPubでリワードを表示するには下記のような手順を取る:
// MyActivity.kt val conf = SdkConfiguration.Builder(adUnitId) .withAdditionalNetwork(MyAdapterConfiguration::class.java.name) .withLogLevel(MoPubLog.LogLevel.DEBUG) .build() MoPub.initializeSdk(this, conf, listener) MoPubRewardedVideos.loadRewardedVideo(adUnitId, MyMediationSettings()) MoPubRewardedVideos.showRewardedVideo(adUnitId)
この要求を受け、メディエーション先アドネットワークのSDKは下記のようなコールバックを受ける:
// MyRewardedVideoAdapter.kt override fun checkAndInitializeSdk(launcherActivity: Activity, adData: AdData): Boolean { // do something return true } override fun load(context: Context, adData: AdData) { // do something mLoadListener.onAdLoaded() } override fun show() { // do something mInteractionListener.onAdShown() mInteractionListener.onAdImpression() MoPubReward.success(MoPubReward.NO_REWARD_LABEL, MoPubReward.DEFAULT_REWARD_AMOUNT).let { mInteractionListener.onAdComplete(it) } mInteractionListener.onAdDismissed() }
これらの詳細な実装は下記を参考にしてほしい。
loadRewardedVideo
の時にはContextを渡していないが、Custom Event側はContextを受け取っている。これは何か?
MoPubの実装を辿ると、これが MoPub::initializeSdk
の際に渡したものであることがわかる:
// MoPubRewardedVideoManager.java public static synchronized void init(@NonNull Activity mainActivity, MediationSettings... mediationSettings) { if (sInstance == null) { sInstance = new MoPubRewardedVideoManager(mainActivity, mediationSettings); } else { MoPubLog.log(CUSTOM, "Tried to call initializeRewardedVideo more than once. Only the first " + "initialization call has any effect."); } } // https://github.com/mopub/mopub-android-sdk/blob/3a7bd85201cb9cc7c91b14505e2ba3d16bd67393/mopub-sdk/mopub-sdk-fullscreen/src/main/java/com/mopub/mobileads/MoPubRewardedVideoManager.java#L170-L177
// MoPubRewardedVideoManager.java final AdAdapter adAdapter = (AdAdapter) adAdapterConstructor.newInstance( sInstance.mMainActivity.get(), baseAdClassName, adDataBuilder.build() ); // https://github.com/mopub/mopub-android-sdk/blob/3a7bd85201cb9cc7c91b14505e2ba3d16bd67393/mopub-sdk/mopub-sdk-fullscreen/src/main/java/com/mopub/mobileads/MoPubRewardedVideoManager.java#L600-L604
そしてこの値は最初のinit時に与えられたものを保持し続けていることも確認できる。(これを書き換えるのがLifecycle Callbacksである。後述)
// MoPubRewardedVideoManager.java public static synchronized void init(@NonNull Activity mainActivity, MediationSettings... mediationSettings) { if (sInstance == null) { sInstance = new MoPubRewardedVideoManager(mainActivity, mediationSettings); } else { MoPubLog.log(CUSTOM, "Tried to call initializeRewardedVideo more than once. Only the first " + "initialization call has any effect."); } } // https://github.com/mopub/mopub-android-sdk/blob/3a7bd85201cb9cc7c91b14505e2ba3d16bd67393/mopub-sdk/mopub-sdk-fullscreen/src/main/java/com/mopub/mobileads/MoPubRewardedVideoManager.java#L170-L177
すなわち最初に与えられたActivityを保持しつづけ、以後Contextが必要になった際はこれを使い回す。これらのコードを字面だけで読み取るなら、MoPubは下記のようなアプリのみを想定した仕様であることになる:*1
- シングルアクティビティ・マルチフラグメント等、Activityインスタンスがアプリケーション全体を通して共通であるアプリ
- Activity利用数はともかく、広告の初期化〜読込〜表示をするポイントが単一であるアプリ
問題が起きうるパターン
しかし上記には問題があり、たとえば下記のようなパターンにおいては当然ながら問題を引き起こしうる:
- Splash表示をするActivityがあり、ここでinitializeを行う。この画面は数秒後にfinishされる。
- たとえばショップ画面、たとえばゲームのリザルト画面など、異なる複数種類のActivityから動画リワードが繋ぎこまれる
- メディエーション先SDKが開放されていない(= 通常の)Contextを用いて何かしらの処理を行うため、解放済みのContextでは広告表示に失敗する仕様になっている。
Android F/Wでは開放されていないContextのインスタンスがないと利用できないAPIが多数存在し、また動画リワードのような商材においては表示元となるActivityとの関係性が正しく定義されていることが期待されるため、MoPubのAPI仕様では対応できないことになる。
Lifecycle Callbacksは何をしているか
上記のようなパターンへの対処手段としてMoPubが用意しているのが Lifecycle Callbacks
と呼ばれるAPI郡で、これはinit時に握ったActivityインスタンスを入れ替える処理が実行されるものだ。
// MoPub.java public static void onCreate(@NonNull final Activity activity) { MoPubLifecycleManager.getInstance(activity).onCreate(activity); updateActivity(activity); } // https://github.com/mopub/mopub-android-sdk/blob/3a7bd85201cb9cc7c91b14505e2ba3d16bd67393/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/MoPub.java#L306-L309
// MoPub.java try { Class moPubRewardedVideoManagerClass = Class.forName( MOPUB_REWARDED_VIDEO_MANAGER); sUpdateActivityMethod = Reflection.getDeclaredMethodWithTraversal( moPubRewardedVideoManagerClass, "updateActivity", Activity.class); } catch (ClassNotFoundException e) { // https://github.com/mopub/mopub-android-sdk/blob/3a7bd85201cb9cc7c91b14505e2ba3d16bd67393/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/MoPub.java#L412-L417
// MoPubRewardedVideoManager.java @ReflectionTarget public static void updateActivity(@Nullable Activity activity) { if (sInstance != null) { sInstance.mMainActivity = new WeakReference<>(activity); } else { logErrorNotInitialized(); } } // https://github.com/mopub/mopub-android-sdk/blob/3a7bd85201cb9cc7c91b14505e2ba3d16bd67393/mopub-sdk/mopub-sdk-fullscreen/src/main/java/com/mopub/mobileads/MoPubRewardedVideoManager.java#L179-L186
これにより load
の際に渡されるContextが入れ替わるため、呼び出し元画面のContextを要求する多くのSDKは初めて期待どおりの挙動を実現可能になる。
load
にはContextが渡されるのに対し show
ではContextが渡されない問題*2については、Custom Event側で getLifecycleListener
を実装することでなんとか回避することも可能だろう。
まとめ
- 本来MoPubは複数Activityを跨いだ利用を考慮したAPI設計ではない
- MoPubの提供するLifecycle Callbacksは、メディエーションを利用する限り事実上の必須実装に等しい
- Custom Eventを実装する場合、期待するContextが常に渡らないことを考慮に入れて設計する必要がある