【JetPack】Lifecycle监听应用前后台切换

539 阅读3分钟

lifecycle-process.png

前言

最近开发应用有这么一个需求,需要监听app的生命周期,能够感知到用户划到后台,回到前台这些事件。

方案

一般前后台监听有两种方案:

  • 利用 ActivityLifecycleCallbacks 监听所有activity的生命周期
  • 使用 ProcessLifecycleOwner

代码实现

方案一:利用 ActivityLifecycleCallbacks 监听所有activity的生命周期

定制前后台切换监听类:ForegroundCallbacks.kt

/**
 * 实现方式1:前后台监听
 */
private const val TAG = "ForegroundCallbacks"

class ForegroundCallbacks(private val mOnAppStatusListener: OnAppStatusListener?) :
    Application.ActivityLifecycleCallbacks {

    // 当前Activity数量
    private var activityStartCount = 0

    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
        APieLog.d(TAG, "onActivityCreated: $activity")
    }

    override fun onActivityStarted(activity: Activity) {
        APieLog.d(TAG, "onActivityStarted: $activity")
        activityStartCount++
        // 数值从0变到1说明是从后台切到前台
        if (activityStartCount == 1) {
            //从后台切到前台
            mOnAppStatusListener?.onForeground()
        }
    }

    override fun onActivityResumed(activity: Activity) {
        APieLog.d(TAG, "onActivityResumed: $activity")
    }

    override fun onActivityPaused(activity: Activity) {
        APieLog.d(TAG, "onActivityPaused: $activity")
    }

    override fun onActivityStopped(activity: Activity) {
        APieLog.d(TAG, "onActivityStopped: $activity")
        activityStartCount--
        // 数值从1到0说明是从前台切到后台
        if (activityStartCount == 0) {
            // 从前台切到后台
            mOnAppStatusListener?.onBackground()
        }
    }

    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
        APieLog.d(TAG, "onActivitySaveInstanceState: $activity")
    }

    override fun onActivityDestroyed(activity: Activity) {
        APieLog.d(TAG, "onActivityDestroyed: $activity")
    }
}

使用ForegroundCallbacks.kt

在项目的 application 里注册监听

class APieApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        registerActivityLifecycleCallbacks(ForegroundCallbacks(object : OnAppStatusListener {
            override fun onForeground() {
                APieLog.d("ForegroundCallbacks", "正在前台")
            }

            override fun onBackground() {
                APieLog.d("ForegroundCallbacks", "进入后台")
            }
        }))
    }
}

效果

方案二:使用 ProcessLifecycleOwner

ProcessLifecycleOwner是Google Lifecycle中的一个类,更优雅的监听前后台的切换

关于**ProcessLifecycleOwner,官方文字是这样介绍的**

使用方法

1、 引入依赖库

implementation  "androidx.lifecycle:lifecycle-process:2.2.0"

2、代码中使用

class MainActivity : AppCompatActivity() {

    private var binding: ActivityMainBinding? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        runOnUiThread {
			// 前后台监听
            ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleEventObserver {
                override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
                    APieLog.d("ForegroundCallbacks-1", "onProcessLifecycleChanged: $event")

            })
        }
    }
															  }

效果

源码解读

核心代码ProcessLifecycleOwner

根据上图得知 ProcessLifecycleOwner 实现了 LifecycleOwner 接口

由于在ProcessLifecycleOwnerInitializer中初始化时传入了 Context,因此 ProcessLifecycleOwner在 attach 方法中借助 Context 拿到了 Application 实例,并调用了 registerActivityLifecycleCallbacks

@Suppress("DEPRECATION")
    internal fun attach(context: Context) {
        handler = Handler()
        registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
        val app = context.applicationContext as Application
        app.registerActivityLifecycleCallbacks(object : EmptyActivityLifecycleCallbacks() {
            @RequiresApi(29)
            override fun onActivityPreCreated(
                activity: Activity,
                savedInstanceState: Bundle?
            ) {
                // We need the ProcessLifecycleOwner to get ON_START and ON_RESUME precisely
                // before the first activity gets its LifecycleOwner started/resumed.
                // The activity's LifecycleOwner gets started/resumed via an activity registered
                // callback added in onCreate(). By adding our own activity registered callback in
                // onActivityPreCreated(), we get our callbacks first while still having the
                // right relative order compared to the Activity's onStart()/onResume() callbacks.
                Api29Impl.registerActivityLifecycleCallbacks(activity,
                    object : EmptyActivityLifecycleCallbacks() {
                        override fun onActivityPostStarted(activity: Activity) {
                            activityStarted()
                        }

                        override fun onActivityPostResumed(activity: Activity) {
                            activityResumed()
                        }
                    })
            }

            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                // Only use ReportFragment pre API 29 - after that, we can use the
                // onActivityPostStarted and onActivityPostResumed callbacks registered in
                // onActivityPreCreated()
                if (Build.VERSION.SDK_INT < 29) {
                    activity.reportFragment.setProcessListener(initializationListener)
                }
            }

            override fun onActivityPaused(activity: Activity) {
                activityPaused()
            }

            override fun onActivityStopped(activity: Activity) {
                activityStopped()
            }
        })
    }

EmptyActivityLifecycleCallbacksApplication.ActivityLifecycleCallbacks的实现类,内部为空实现

内部也维护了几个变量,用来记录 start 和 resume 的数量

private var startedCounter = 0
private var resumedCounter = 0
private var pauseSent = true
private var stopSent = true

在不同的时机调用

internal fun activityStarted() {
	startedCounter++
	if (startedCounter == 1 && stopSent) {
		registry.handleLifecycleEvent(Lifecycle.Event.ON_START)
		stopSent = false
	}
}

internal fun activityResumed() {
	resumedCounter++
	if (resumedCounter == 1) {
		if (pauseSent) {
			registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
			pauseSent = false
		} else {
			handler!!.removeCallbacks(delayedPauseRunnable)
		}
	}
}

internal fun activityPaused() {
	resumedCounter--
	if (resumedCounter == 0) {
		handler!!.postDelayed(delayedPauseRunnable, TIMEOUT_MS)
	}
}

internal fun activityStopped() {
	startedCounter--
	dispatchStopIfNeeded()
}

internal fun dispatchPauseIfNeeded() {
	if (resumedCounter == 0) {
		pauseSent = true
		registry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
	}
}

internal fun dispatchStopIfNeeded() {
	if (startedCounter == 0 && pauseSent) {
		registry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
		stopSent = true
	}
}

在 activityStarted 和 activityResumed 方法中对 这两个数值进行 ++,同时更新 lifecycle 状态

在 activityPaused 和 activityStopped 方法对这两个数值进行 --

并且在 activityPaused 时做了个 700ms 的延时

@VisibleForTesting
internal const val TIMEOUT_MS: Long = 700 // mls
internal fun activityPaused() {
	resumedCounter--
	if (resumedCounter == 0) {
		handler!!.postDelayed(delayedPauseRunnable, TIMEOUT_MS)
	}
}

这里为什么要延时呢?

在测试的时候发现一个很奇怪的问题:

当退出应用又立马切回来的时候,并不会调用onStop和onResume方法

反复试了好几遍,终于找到了原因:就是这里的延时,当切换的时间太短时,就监听不到了。

经过思考,发现:

我们先来看看这几个场景:在 A-Activity 中打开 B-Activity,生命周期是这样的:

A: onPause

B: onCreate

B: onStart

B: onResume

A: onStop

接着:关闭 B-Activity 返回 A-Activity

B: onPause

A: onRestart

A: onStart

A: onResume

B: onStop

B: onDestroy

再接着:从 App 返回桌面

A:onPause

A:onStop

ProcessLifecycleOwner 的原理其实是监听Activity的生命周期,当你在 App内部新开一个Activity或者销毁Activity时,都会引起Activity调用onPause,onStop方法。

所以,当ProcessLifecycleOwner接受到onPause,onStop这些事件时,并不知道是来自用户退到后台,还是来自app内部的Activity变化。

因此,这里才设置了一个延时,如果是app内部Activity变动,那么一个Activity的onPause必然会伴随另一个Activity的onResume。

从前面的流程可以看出:不管什么情况,都会先调用onPause

如果太长时间没有调用onResume,就认为是退到后台

如果很快onResume就被调用了,那就认为这只是app内部的activity在变化。这就是设置延时的作用。