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" とあるように、実はメディエーション機能を使う場合には実態として必須と言って過言ではない。
今回はリワード広告を例に取る。
前提として、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
しかし上記には問題があり、たとえば下記のようなパターンにおいては当然ながら問題を引き起こしうる:
Android F/Wでは開放されていないContextのインスタンスがないと利用できないAPIが多数存在し、また動画リワードのような商材においては表示元となるActivityとの関係性が正しく定義されていることが期待されるため、MoPubのAPI仕様では対応できないことになる。
上記のようなパターンへの対処手段として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
を実装することでなんとか回避することも可能だろう。