LoginSignup
31
34

More than 3 years have passed since last update.

【Android】ライフサイクル(Lifecycle)アンチパターン 〜2020年版〜 その1

Last updated at Posted at 2020-01-05

はじめに

最近、コードレビューをする機会も多くなりネット上に古い記事もあるので2020年版のアンチパターンをご紹介します。

今回は Activity の Lifecycle のアンチパターンについて最新の実装について紹介します。
と言っても Android の Lifecycle 自体は大きく変わっていません。コールバックの検出や使い方でのアンチパターン紹介になります。

少し前のよく見るコード

サンプルアプリのアプリ構成は MainActivity と AActivity の2画面のシンプルな作りとします。 MainActivity の ActionBar をクリックすると AActivity に遷移する実装とします。

各ライフサイクルでログを取得しようとする実装を記述すると大体以下のような実装になると思います。

MainActivity.kt
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 を使用して管理する方法もあったかと思います。

MyApplication.kt
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 で通信させるアプリも多いかと思いますが、例えば、画面生成時とバックグラウンドからの復帰時のみ通信させたい場合に実装は大変だったと思います。
以下のようなフラグを使って管理している処理をよく見かけます。

MainActivity.kt
    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 を取り込みます。

build.gradle
    dependencies {
        // 最新のバージョンを使ってください
        implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
    }

ProcessLifecycleOwner の使い方

LifecycleObserver を継承したクラスで addObserver() をして @OnLifecycleEvent(Lifecycle.Event.***) のアノテーションを付けたメソッド(メソッド名は任意でOK)を用意するだけ。

実装例は以下です。

MyApplication.kt
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 内に実装して使用することも可能です。

MainActivity.kt
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 を使って修正してみました。結果、とてもシンプルになりました。

MainActivity.kt
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 も進化していっているので便利な機能を使ってバグが少ないアプリを作りましょう。

【Android】ライフサイクル(Lifecycle)アンチパターン 〜2020年版〜 その2

31
34
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
34