はじめに
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
本件は In-App Messaging version 19.0.7 で改修されました 
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
Android で Firebase In-App Messaging を表示させてようとしても、アプリの作り次第では、 表示されない or すぐに消えてしまう問題 が発生することが確認されました。
全てのケースで解決しないかも知れませんが、意図通りに表示できる workaround を見つけたので記しておきます。
Firebase In-App Messaging 導入時の参考になれば幸いです。
※調査した技術内容が多めです
※キャンペーン情報の取得完了のタイミングや、メッセージを表示したい Activity の lifecycle の状態次第では上手く行かないケースが存在するかも知れません
→ 技術的背景を理解した上で、最適な workaround を使うことをオススメします。
本記事での用語
| 用語 | 意味 | 
|---|---|
| LaunchActivity | AndroidManifest.xmlでandroid.intent.category.LAUNCHERが指定されている Activity | 
| MainActivity | LaunchActivity から起動される Activity | 
| In-App Message | Firebase In-App Messaging が表示するメッセージ (com.google.firebase.inappmessaging.model.InAppMessage というクラスがあります) | 
再現条件
In-App Message を表示した後、何らかの Activity(最前面になくても良い)が destroy されると、表示されている In-App Message が消える。
(タイミングによっては、表示されたことに気付けない)
ありがちな例
LaunchActivity が、 onCreate で他の Activity を呼び出して、 LaunchActivity は即閉じるような場合には再現します。
実装例
class LaunchActivity : AppCompatActivity() {
    // Firebase In-App Messaging SDK はこの Activity にメッセージを表示させようとするが、
    // すぐに画面遷移してしまうため、メッセージが表示されない(or すぐ消える)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_launch)
        if (isLogin) {
            openMainActivity()
        } else {
            openLoginActivity()
        }
        // ...
    }
    private fun openMainActivity() {
        val intent = Intent(this, MainActivity::class.java)
        //finish() で閉じなくても、↓でも再現
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
        startActivity(intent)
    }
}
なぜ消えるのか?
Firebase In-App Messaging の内部では、 Application.ActivityLifecycleCallbacks を実装したクラス( com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay )が、アプリケーション内の Activity の Lifecycle を監視して、In-App Message の制御を行っています。
onActivityDestroyed の override 実装を見ると、
https://github.com/firebase/firebase-android-sdk/blob/master/firebase-inappmessaging-display/src/main/java/com/google/firebase/inappmessaging/display/FirebaseInAppMessagingDisplay.java#L222-L228
@Override
public void onActivityDestroyed(Activity activity) {
  // clear all state scoped to activity and dismiss fiam
  headlessInAppMessaging.clearDisplayListener();
  imageLoader.cancelTag(activity.getClass());
  removeDisplayedFiam(activity);
  super.onActivityDestroyed(activity);
}
のようになっており、 Activity が destroy されたら、 removeDisplayedFiam(...) が呼び出されています。
※ Fiam は Firebase In-App Messaging のことです
特筆すべきは、最前面にある Activity かどうかは考慮していないということです。
(つまり、最前面にない Activity が destroy されることを SDK 側が考慮できていません)
workaround
MainActivity にて、 LaunchActivity が onDestroy されるまで In-App Messaging の表示を遅らせる。
と、端的に言っても、いくつかステップがあります。
- In-App Message の表示を抑制しておく
- 
MainActivityにて、LaunchActivityが onDestroy されたことを検出する
- In-App Message の表示を抑制を解除して、In-App Message の表示処理を呼び出す
です。
1. 表示の抑制方法
FirebaseInAppMessaging.getInstance().setMessagesSuppressed(true) を呼び出せば、In-App Message の表示を抑制することができます。
/**
 * Enable or disable suppression of Firebase In App Messaging messages
 *
 * <p>When enabled, no in app messages will be rendered until either you either disable
 * suppression, or the app restarts, as this state is not preserved over app restarts.
 *
 * <p>By default, messages are not suppressed.
 *
 * @param areMessagesSuppressed Whether messages should be suppressed
 */
@Keep
public void setMessagesSuppressed(@NonNull Boolean areMessagesSuppressed) {
  this.areMessagesSuppressed = areMessagesSuppressed;
}
実装を見てもわかるとおり、単に suppress するか否かのフラグの書き換えだけです。
suppress を false にしたからといって、そのタイミングで In-App Message の表示処理が実行されるわけではありません。
(JavaDoc コメントにもそのあたりの言及はありませんでした…)
2. LaunchActivity#onDestroy の検出
Application.ActivityLifecycleCallbacks を使えば検出が可能です。
後述しますが、In-App Messaging は最前面の Activity が resumed になったら表示処理が実行されるので、
- 
LaunchActivity#onDestroyになった
- 
MainActivity#onResumeになった
の2つの条件を満たしたときに、 MainActivity で何らかの callback を受け取れるようにすれば OK です。
もうちょっとやりようはあるかも知れませんが…
object InAppMessagingDelayHelper : Application.ActivityLifecycleCallbacks {
    private val targetActivityName: String = MainActivity::class.simpleName.orEmpty()
    private val backgroundActivityName: String = LaunchActivity::class.simpleName.orEmpty()
    private val destroyed = MutableLiveData<Boolean>().apply { value = false }
    private val resumed = MutableLiveData<Boolean>().apply { value = false }
    init {
        FirebaseInAppMessaging.getInstance().setMessagesSuppressed(true)
    }
    val canShow: LiveData<Boolean> = Transformations.distinctUntilChanged(
        MediatorLiveData<Boolean>().apply {
            value = false
            listOf(destroyed, resumed).forEach { liveData ->
                addSource(liveData) {
                    val isDestroyed = destroyed.value ?: false
                    val isResumed = resumed.value ?: false
                    value = isDestroyed && isResumed
                }
            }
        }
    )
    override fun onActivityDestroyed(activity: Activity?) {
        destroyed.value = (backgroundActivityName == activity?.localClassName)
    }
    override fun onActivityResumed(activity: Activity?) {
        resumed.value = (targetActivityName == activity?.localClassName)
    }
    override fun onActivityPaused(activity: Activity?) = Unit
    override fun onActivityStarted(activity: Activity?) = Unit
    override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) = Unit
    override fun onActivityStopped(activity: Activity?) = Unit
    override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) = Unit
}
を作って、 Application で registerActivityLifecycleCallbacks しておけば
override fun onCreate() {
    super.onCreate()
    registerActivityLifecycleCallbacks(InAppMessagingDelayHelper)
    ...
}
MainActivity で LaunchActivity is destroyed && MainActivity is resumed の状態になったコールバックを受け取ることができます。
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ....
    InAppMessagingDelayHelper.canShow.observe(this, Observer { canShow ->
        if (canShow) {
            // LaunchActivity is destroyed && MainActivity is resumed
            // TODO : In-App Message の表示処理の呼び出し
        }
    })
    ...
}
3. In-App Message の表示処理を呼び出す
In-App Message の表示処理は、 FirebaseInAppMessagingDisplay#onActivityResumed から呼び出されています。
@Override
public void onActivityResumed(Activity activity) {
  super.onActivityResumed(activity);
  if (inAppMessage != null) {
    showActiveFiam(activity);
  }
}
...
private void showActiveFiam(@NonNull final Activity activity) {
  if (inAppMessage == null || headlessInAppMessaging.areMessagesSuppressed()) {
    Logging.loge("No active message found to render");
    return;
  }
  // 表示処理
}
showActiveFiam(...) は private ですが、呼び出し元の onActivityResumed(...) は public なので、
FirebaseInAppMessagingDisplay.getInstance() でインスタンスを取得すれば呼び出すことができます。
つまり、
InAppMessagingDelayHelper.canShow.observe(this, Observer { canShow ->
    if (canShow) {
-       // LauncherActivity is destroyed && MainActivity is resumed
-       // TODO : In-App Message の表示処理の呼び出し
+       FirebaseInAppMessaging.getInstance().setMessagesSuppressed(false) // 抑制解除
+       FirebaseInAppMessagingDisplay.getInstance().onActivityResumed(this) // 表示処理の呼び出し
    }
})
のようにすれば、In-App Message がちゃんと表示されるようになります。
アプリケーションによって、Landing する Activity までに、どんな Activity が表示されては消えるのかがまちまちだと思うので、それぞれのアプリケーションに応じた回避方法を採る必要があると考えています。
あくまでも参考程度にして下さい。
本件は Firebase Android SDK の GitHub Issue で報告しているので、あわよくば、将来的にはこの workaround は不要になるかも知れません。
(不要になることを祈ってます。)
https://github.com/firebase/firebase-android-sdk/issues/1324
それ以外に調べた事
調査したときにわかったことを、ついでなので記しておきます。
メッセージレイアウトのトップバナーは Push 通知ではない
これは、単に勘違いしていただけですが、一応記載。

は

こんな感じで Push 通知っぽく表示されますが、Push 通知ではありません。
つまり、アプリを開かないと表示されません。
アプリの起動を促進させるためには、Cloud Messaging を使う必要があります。
※In-App Messaging と Cloud Messaging を併用すれば、Push 通知経由でアプリを開いた場合のみに、特定の In-App Message を表示させることも可能です。
In-App Message を表示している状態で別 Activity を表示させたときの挙動
新たに開いた Activity の上に、In-App Message が新たに表示される。
(既に表示している In-App Message が、新たに開いた Activity に隠れる…と思ってましたが、大丈夫でした!)
In-App Message 表示させる方法
1. キャンペーンを作成して、テストデバイスに送る
https://firebase.google.com/docs/in-app-messaging/get-started?authuser=0&platform=android
によると、
電力を節約するため、Firebase アプリ内メッセージングはサーバーからのメッセージの取得を 1 日に 1 回だけ行います。この設定の場合、テストが困難になることがあるため、メッセージをオンデマンドで表示するテストデバイスを Firebase コンソールで指定できます。
と書かれています。
この手ももちろん使えます。
(でも、若干めんどくさい…)
2. コードでダミーのメッセージを生成して、メッセージの表示処理を呼び出す
Firebase Android SDK のソースコード を見てみると、どうやら、コードで In-App Messaging の表示処理を実行することができそうです。
リンク先が変わるかも知れないので、コードを引用しておきます。
ModalMessage message =
    builder
        .setBackgroundHexColor(bodyBackgroundColorString)
        .setTitle(title)
        .setBody(body)
        .setImageData(imageData)
        .setAction(modalAction)
        .build(campaignMetadata, data);
FirebaseInAppMessagingDisplay.getInstance()
    .testMessage(this, message, new NoOpDisplayCallbacks());
これを LaunchActivity#onCreate(...) などで実行します。
この手を使うと、色んなタイミングで In-App Messaging の表示処理の呼び出しを再現することが可能です。
表示処理の呼び出しのタイミングを変えてみたり、表示時や表示中の Activity の状態を変えてみるには、この方法が手軽でオススメです。
具体的に、どんな場合に In-App Messaging の表示処理が呼び出されるのかは後述します。
In-App Message の表示処理の発動
public Flowable<TriggeredInAppMessage> createFirebaseInAppMessageStream() {
  return Flowable.merge(
          appForegroundEventFlowable,
          analyticsEventsManager.getAnalyticsEventsFlowable(),
          programmaticTriggerEventFlowable)
      .doOnNext(e -> Logging.logd("Event Triggered: " + e))
      .observeOn(schedulers.io())
      ...色んな処理... // キャンペーン取得の通信が絡めば時間が掛かる可能性がある
      .observeOn(schedulers.mainThread())
これを、 FirebaseInAppMessaging の constructor で subscribe しています。
(内部では RxJava を使ってるんですね!)
public class FirebaseInAppMessaging {
  FirebaseInAppMessaging(...) {
    ...
    Disposable unused =
        inAppMessageStreamManager
            .createFirebaseInAppMessageStream()
            .subscribe(FirebaseInAppMessaging.this::triggerInAppMessage);
  }
  ...
  private void triggerInAppMessage(TriggeredInAppMessage inAppMessage) {
    if (this.fiamDisplay != null) {
      fiamDisplay.displayMessage(
          inAppMessage.getInAppMessage(),
          displayCallbacksFactory.generateDisplayCallback(
              inAppMessage.getInAppMessage(), inAppMessage.getTriggeringEvent()));
    }
  }
- アプリが最前面に来たとき
- Analytics の Event が発行されたとき
- programmaticTriggerEvent が発行されたとき
に In-App Message の表示処理が開始されます。
ただし、バックグラウンド処理が完了してから、UI への表示処理の実行になるので、その間に Activity が遷移・終了している可能性も十分あり得ます。
しかしながら、UI への表示処理を行ったタイミングで最前面の Activity に表示されるようになっていました。
(逆に言えば、 MainActivity#onCreate でイベントを送信し、それをトリガーに In-App Messaging を表示するように設定しても、画面遷移をしてしまえば、別の Activity で表示されることもある)