Blog

本当は怖い。透明activityのlifecycleの罠。

androidアプリでは、activityの背景を透明にすることで、dialogのように扱ったり、様々なUI表現をすることができます。

最近はFragment周りの環境が整ってきた影響であまり使われなくなってきた印象がありますが、まだまだ使っているプロジェクトもあるのではないかと思います。

透明activityを使った場合、実はactivityのlifecycleが通常とは異なる動作をしており、少し詰まったのでまとめます。

透明activityの作り方

念の為、透明activityの作り方をおさらいしておきましょう。

activityに指定しているthemeに以下の指定を行うと、透明activityを作ることができます。

<item name=“android:windowBackground”>@android:color/transparent</item>
<item name=“android:colorBackgroundCacheHint”>@null</item>
<item name=“android:windowIsTranslucent”>true</item>

簡単ですね。

画面遷移時

透明Activityの動作検証の前に、通常のActivity Activity1 Activity2 を用意して、それらの間で通常通り遷移するときのlifecycleを確認しておきましょう。

lifecycleを観測するように以下のようなLifecycleObserverを用意します。

class LifecycleLogObserver : LifecycleEventObserver {
    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        Log.d(source::class.java.simpleName, event.name)
    }
}

これをactivityのinitでaddObserverします。

class Activity1: AppCompatActivity() {
    init {
        lifecycle.addObserver(LifecycleLogObserver())
    }
}

Activity1からActivity2に画面遷移し、その後戻った場合、このようなログが出力されます。

// Activity1を起動
D/Activity1: ON_CREATE
D/Activity1: ON_START
D/Activity1: ON_RESUME
// Activity1 -> Activity2
D/Activity1: ON_PAUSE
D/Activity2: ON_CREATE
D/Activity2: ON_START
D/Activity2: ON_RESUME
D/Activity1: ON_STOP
// Activity2 -> Activity1 (Back)
D/Activity2: ON_PAUSE
D/Activity1: ON_START
D/Activity1: ON_RESUME
D/Activity2: ON_STOP
D/Activity2: ON_DESTROY

見慣れたlifecycleですね。

では次に、通常のActivity Activity1 と透明なActivity TransparentActivity を用意して、 Activity1 からTransparentActivity への画面遷移を考えましょう。

// Activity1を起動
D/Activity1: ON_CREATE
D/Activity1: ON_START
D/Activity1: ON_RESUME
// Activity1 -> TransparentActivity
D/Activity1: ON_PAUSE
D/TransparentActivity: ON_CREATE
D/TransparentActivity: ON_START
D/TransparentActivity: ON_RESUME
// TransparentActivity -> Activity1 (Back)
D/TransparentActivity: ON_PAUSE
D/Activity1: ON_RESUME
D/TransparentActivity: ON_STOP
D/TransparentActivity: ON_DESTROY

そうです、 Activity1 はTransparentActivityが起動しても ON_RESUME で止まっており、 ON_STOPON_START が省かれていることがわかりますね。

これにより、TransparentActivityが上に乗っている間でもLiveData等はactiveのままになります。

透けて見えているUIは更新したいことが多いと思いますので、この性質は役立つと思いますが、少し注意が必要だと思います。

もっと画面を重ねて見る。

では次に、Activity1 -> TransparentActivity -> Activity2 の画面遷移を考えましょう。

このようになります。

// Activity1を起動
D/Activity1: ON_CREATE
D/Activity1: ON_START
D/Activity1: ON_RESUME
// Activity1 -> TransparentActivity
D/Activity1: ON_PAUSE
D/TransparentActivity: ON_CREATE
D/TransparentActivity: ON_START
D/TransparentActivity: ON_RESUME
// TransparentActivity -> Activity2
D/TransparentActivity: ON_PAUSE
D/Activity2: ON_CREATE
D/Activity2: ON_START
D/Activity2: ON_RESUME
D/Activity1: ON_STOP
D/TransparentActivity: ON_STOP

Activity2 が起動したタイミングで、 TransparentActivity だけでなく Activity1 も合わせて ON_STOP が呼ばれました。

さらに、 透明のActivityを2つ用意し( TransparentActivity1 TransparentActivity2) 、 Activity1 -> TransparentActivity1 -> TransparentActivity2 の画面遷移を考えます。

// Activity1を起動
D/Activity1: ON_CREATE
D/Activity1: ON_START
D/Activity1: ON_RESUME
// Activity1 -> TransparentActivity1
D/Activity1: ON_PAUSE
D/TransparentActivity1: ON_CREATE
D/TransparentActivity1: ON_START
D/TransparentActivity1: ON_RESUME
// TransparentActivity1 -> TransparentActivity2
D/TransparentActivity1: ON_PAUSE
D/TransparentActivity2: ON_CREATE
D/TransparentActivity2: ON_START
D/TransparentActivity2: ON_RESUME

なんと、まだActivity1の ON_STOP が呼ばれません。

透明Activityが2枚重なった状態だと、3つのactivityがすべてactiveな状態で有ることがわかります。

このまま何枚も透明Activityを重ねていくと、非常に重くなっていきます。( ON_STOP が呼ばれていないので、ON_DESTORY も呼ばれないようです)

あまりしないとは思いますが、透明Activityは重ねないように気をつけましょう。

HOMEに戻る

次に、HOMEボタンを押したときの挙動を見てみましょう。

通常の画面遷移の後にHOMEに戻るとこうなります。

// Activity1を起動
D/Activity1: ON_CREATE
D/Activity1: ON_START
D/Activity1: ON_RESUME
// Activity1 -> Activity2
D/Activity1: ON_PAUSE
D/Activity2: ON_CREATE
D/Activity2: ON_START
D/Activity2: ON_RESUME
D/Activity1: ON_STOP
// HOMEボタンを押す
D/Activity2: ON_PAUSE
D/Activity2: ON_STOP
// アプリに戻る
D/Activity2: ON_START
D/Activity2: ON_RESUME

画面が重なっていても、当然動作するのは Activity2 のlifecycleのみです。

次に透明Activityで考えます。

// Activity1を起動
D/Activity1: ON_CREATE
D/Activity1: ON_START
D/Activity1: ON_RESUME
// Activity1 -> TransparentActivity
D/Activity1: ON_PAUSE
D/TransparentActivity: ON_CREATE
D/TransparentActivity: ON_START
D/TransparentActivity: ON_RESUME
// HOMEボタンを押す
D/TransparentActivity: ON_PAUSE
D/Activity1: ON_STOP
D/TransparentActivity: ON_STOP
// アプリに戻る
D/Activity1: ON_START
D/TransparentActivity: ON_START
D/TransparentActivity: ON_RESUME

HOME画面が表示されたタイミングで、 TransparentActivity だけでなく、 Activity1ON_STOP が呼ばれていることがわかります。

また、戻ってくるとちゃんと Activity1ON_START の状態まで復元しています。

一貫性がありますね。

画面回転

最後に画面回転です。

こちらも、通常のActivityが重なった状態での画面回転を確認しておきましょう。

// Activity1を起動
D/Activity1: ON_CREATE
D/Activity1: ON_START
D/Activity1: ON_RESUME
// Activity1 -> Activity2
D/Activity1: ON_PAUSE
D/Activity2: ON_CREATE
D/Activity2: ON_START
D/Activity2: ON_RESUME
D/Activity1: ON_STOP
// 画面回転	
D/Activity2: ON_PAUSE
D/Activity2: ON_STOP
D/Activity2: ON_DESTROY
D/Activity2: ON_CREATE
D/Activity2: ON_START
D/Activity2: ON_RESUME

画面回転時にActivity2のみが再生成されています。

では、 Activity1TransparentActivity が重なった状態で画面回転が行われたらどうなるか調べてみましょう。

// Activity1を起動
D/Activity1: ON_CREATE
D/Activity1: ON_START
D/Activity1: ON_RESUME
// Activity1 -> TransparentActivity
D/Activity1: ON_PAUSE
D/TransparentActivity: ON_CREATE
D/TransparentActivity: ON_START
D/TransparentActivity: ON_RESUME
// 画面回転
D/TransparentActivity: ON_PAUSE
D/TransparentActivity: ON_STOP
D/TransparentActivity: ON_DESTROY
D/TransparentActivity: ON_CREATE
D/TransparentActivity: ON_START
D/TransparentActivity: ON_RESUME
D/Activity1: ON_STOP
D/Activity1: ON_DESTROY
D/Activity1: ON_CREATE
D/Activity1: ON_START
D/Activity1: ON_RESUME
D/Activity1: ON_PAUSE

何ということでしょう、TransparentActivity と合わせて Activity1 も再起動されているだけではなく、一度ON_RESUME まで進んだ後に ON_PAUSE になっています。

Activity1ON_RESUME でなにか動作させている場合、予期せぬ動きをするかもしれません。

まとめ

今回、透明Activityが絡んだ様々なケースのlifecycleについて見ていきました。

今回のLogからわかりにくかった方は、こちらの図も参考になるかもしれません。

android-lifecycles/cheatsheettranslucent.pdf

基本的には、透明Activityが起動しても裏のActivityはactiveのままになっていることを把握しておけば大丈夫だと思います。

一方、ところどころ変な動きをしたり、条件によって8系のみクラッシュする等の問題があります。

参考: 透明Activityの罠 - Studyplus Engineering Blog

個人的には、今ならdialog fragment等fragmentを使ってオーバレイさせたほうが変にはまらなくて良いのではないかと思っています。

人気の記事

kotlin coroutinesのFlow, SharedFlow, StateFlowを整理する

Jetpack ComposeとKotlin Coroutinesを連携させる

Layout Composableを使って複雑なレイアウトを組む【Jetpack Compose】

テスト用Dispatcherの使い分け【Kotlin Coroutines】

Flow.combineの内部実装がすごい話

Jetpack ComposeのRippleエフェクトを深堀り、カスタマイズも