androidアプリでは、activityの背景を透明にすることで、dialogのように扱ったり、様々なUI表現をすることができます。
最近はFragment周りの環境が整ってきた影響であまり使われなくなってきた印象がありますが、まだまだ使っているプロジェクトもあるのではないかと思います。
透明activityを使った場合、実はactivityのlifecycleが通常とは異なる動作をしており、少し詰まったのでまとめます。
念の為、透明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_STOP
と ON_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に戻るとこうなります。
// 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
だけでなく、 Activity1
も ON_STOP
が呼ばれていることがわかります。
また、戻ってくるとちゃんと Activity1
が ON_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のみが再生成されています。
では、 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
// 画面回転
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
になっています。
Activity1
で ON_RESUME
でなにか動作させている場合、予期せぬ動きをするかもしれません。
今回、透明Activityが絡んだ様々なケースのlifecycleについて見ていきました。
今回のLogからわかりにくかった方は、こちらの図も参考になるかもしれません。
android-lifecycles/cheatsheettranslucent.pdf
基本的には、透明Activityが起動しても裏のActivityはactiveのままになっていることを把握しておけば大丈夫だと思います。
一方、ところどころ変な動きをしたり、条件によって8系のみクラッシュする等の問題があります。
参考: 透明Activityの罠 - Studyplus Engineering Blog
個人的には、今ならdialog fragment等fragmentを使ってオーバレイさせたほうが変にはまらなくて良いのではないかと思っています。