#はじめに
最近、コードレビューをする機会も多くなりネット上に古い記事もあるので2020年版のアンチパターンをご紹介します。
今回は Activity の Lifecycle のアンチパターンについて最新の実装について紹介します。
と言っても Android の Lifecycle 自体は大きく変わっていません。コールバックの検出や使い方でのアンチパターン紹介になります。
#少し前のよく見るコード
サンプルアプリのアプリ構成は MainActivity と AActivity の2画面のシンプルな作りとします。 MainActivity の ActionBar をクリックすると AActivity に遷移する実装とします。
各ライフサイクルでログを取得しようとする実装を記述すると大体以下のような実装になると思います。
class MainActivity : AppCompatActivity(), LifecycleObserver {
override fun onCreate(savedInstanceState: Bundle?) {
Log.d("MainActivity", "_onCreate")
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
// AActivity への遷移
startActivityForResult(Intent(this, AActivity::class.java), 1)
}
}
override fun onStart() {
Log.d("MainActivity", "_onStart")
super.onStart()
}
override fun onResume() {
Log.d("MainActivity", "_onResume")
super.onResume()
}
override fun onPause() {
Log.d("MainActivity", "_onPause")
super.onPause()
}
override fun onStop() {
Log.d("MainActivity", "_onStop")
super.onStop()
}
override fun onDestroy() {
Log.d("MainActivity", "_onDestroy")
super.onDestroy()
}
override fun onRestart() {
Log.d("MainActivity", "_onRestart")
super.onRestart()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
Log.d("MainActivity", "_onActivityResult")
super.onActivityResult(requestCode, resultCode, data)
}
}
もしくは Application クラスで ActivityLifecycleCallbacks を使用して管理する方法もあったかと思います。
class MyApplication : Application(), Application.ActivityLifecycleCallbacks {
override fun onCreate() {
super.onCreate()
Log.d("Application", "_onCreate")
registerActivityLifecycleCallbacks(this);
}
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {
Log.d("LifecycleCallbacks", "_onActivityCreated" + activity?.getLocalClassName());
}
override fun onActivityStarted(activity: Activity?) {
Log.d("LifecycleCallbacks", "_onActivityStarted" + activity?.getLocalClassName());
}
override fun onActivityResumed(activity: Activity?) {
Log.d("LifecycleCallbacks", "_onActivityResumed" + activity?.getLocalClassName());
}
override fun onActivityPaused(activity: Activity?) {
Log.d("LifecycleCallbacks", "_onActivityResumed" + activity?.getLocalClassName());
}
override fun onActivityStopped(activity: Activity?) {
Log.d("LifecycleCallbacks", "_onActivityStopped" + activity?.getLocalClassName());
}
override fun onActivityDestroyed(activity: Activity?) {
Log.d("LifecycleCallbacks", "_onActivityDestroyed" + activity?.getLocalClassName());
}
override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {
Log.d("LifecycleCallbacks", "_onActivitySaveInstanceState" + activity?.getLocalClassName());
}
}
#アンチパターンについて
さて、上記の実装で何が大変だったかというと、画面回転や別のActivity から戻ってくる時やバックグラウンドからの復帰の区別が大変でした。それ以外にはダイヤログが閉じた場合といった様々なケースに対応する必要があります。
onResume で通信させるアプリも多いかと思いますが、例えば、画面生成時とバックグラウンドからの復帰時のみ通信させたい場合に実装は大変だったと思います。
以下のようなフラグを使って管理している処理をよく見かけます。
var isActivityResultAfter: Boolean = false
override fun onResume() {
Log.d("MainActivity", "_onResume")
super.onResume()
// フラグが true の時は onActivityResult の後
if (isActivityResultAfter) {
isActivityResultAfter = false
} else {
// ここで通信処理をする
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
Log.d("MainActivity", "_onActivityResult")
super.onActivityResult(requestCode, resultCode, data)
// フラグで頑張る実装
isActivityResultAfter = true
}
少し前はこう言った実装もOKだったかと思いますが、この実装は今ではアンチパターンになります。
#バックグラウンドからの復帰処理ベストな実装
ではどう実装するのが良いかというと
ProcessLifecycleOwner
を使用して管理します。
(Google公式サイト)ProcessLifecycleOwner
##ProcessLifecycleOwnerを使用する準備
gradle ファイルに以下を記述して LifecycleComponent を取り込みます。
dependencies {
// 最新のバージョンを使ってください
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
}
##ProcessLifecycleOwner の使い方
LifecycleObserver を継承したクラスで addObserver() をして @OnLifecycleEvent(Lifecycle.Event.***) のアノテーションを付けたメソッド(メソッド名は任意でOK)を用意するだけ。
実装例は以下です。
class MyApplication : Application(), LifecycleObserver {
override fun onCreate() {
super.onCreate()
// addObserver する
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
// バックグラウンドの復帰検出は LifecycleOwner でなく ProcessLifecycleOwner を使うこと
// ↓これはだと LifecycleOwner
// object:LifecycleOwner {
// override fun getLifecycle(): Lifecycle {
// }
// }.lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onApplicationCreate() {
Log.d("arch.lifecycle", "_onCreate")
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onApplicationStart() {
Log.d("arch.lifecycle", "_onStart")
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onApplicationResume() {
Log.d("arch.lifecycle", "_onResume")
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onApplicationPause() {
Log.d("arch.lifecycle", "_onPause")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onApplicationStop() {
Log.d("arch.lifecycle", "_onStop")
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onApplicationDestroy() {
// ここは通知されない
Log.d("arch.lifecycle", "_onDestroy")
}
}
これで準備完了!
##デバックしてみる
実際これでデバックして、それぞれの onResume の通知を確認してみると LifecycleObserver の onResume には画面回転と 別のActivity から戻ってくる時に通知が来ないのがわかります。要はバックグラウンドからフォアグラウンド復帰が検出できたということになります。
##onResumeにおける挙動をまとめてみる
挙動確認箇所 | Activityへの通知 | ProcessLifecycleOwnerへの通知 |
---|---|---|
画面生成時の表示 (onCreate直後) |
○ | ○ |
画面回転時の再表示(生成)時 (Actvityが破棄される回転) |
○ | × |
ホームボタンでホームに遷移して履歴画面からアプリ起動 (onRestart経由での起動) |
○ | ○ |
別のActivityから戻ってくる時 | ○ | × |
マルチウインドウモード切り替え時 | ○ | ○ |
マルチウインドウモード終了時 | ○ | × |
○:通知される ×:通知されない |
ProcessLifecycleOwner を Activity で使う
ProcessLifecycleOwner は Application クラスでの実装例が多いですが、Activity 内に実装して使用することも可能です。
class MainActivity : AppCompatActivity(), LifecycleObserver {
override fun onCreate(savedInstanceState: Bundle?) {
Log.d("MainActivity", "_onCreate")
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
startActivityForResult(Intent(this, AActivity::class.java), 1)
}
// addObserver する
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onApplicationCreate() {
Log.d("arch.lifecycle", "_onCreate")
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onApplicationStart() {
Log.d("arch.lifecycle", "_onStart")
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onApplicationResume() {
Log.d("arch.lifecycle", "_onResume")
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onApplicationPause() {
Log.d("arch.lifecycle", "_onPause")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onApplicationStop() {
Log.d("arch.lifecycle", "_onStop")
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onApplicationDestroy() {
Log.d("arch.lifecycle", "_onDestroy")
}
}
イメージが難しいければ実装してデバックした方が理解が早いかと思います。
#なぜアンチパターンなのか
少し前のフラグを使った実装だと管理が複雑になり大変ということもありますが、理由として一番はマルチウインドウの登場です。今後はマルチウインドウ対応するアプリが当たり前になります。マルチウインドウモードに移行・解除時にも onResume が通知されます。さらにマルチウインドウモードでの回転も考慮しないといけません。これを ProcessLifecycleOwner を使用せず管理しようとするのはとても大変です。
#アンチパターンを修正してみる
上記の画面生成時とバックグラウンドからの復帰時のみ通信する例を ProcessLifecycleOwner を使って修正してみました。結果、とてもシンプルになりました。
class MainActivity : AppCompatActivity(), LifecycleObserver {
override fun onCreate(savedInstanceState: Bundle?) {
Log.d("MainActivity", "_onCreate")
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
startActivityForResult(Intent(this, AActivity::class.java), 1)
}
// addObserver する
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onApplicationResume() {
Log.d("arch.lifecycle", "_onResume")
// ここで通信処理をする フラグはいらない
}
}
#最後に
Android も進化していっているので便利な機能を使ってバグが少ないアプリを作りましょう。