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

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", "进入后台")
            }
        }))
    }
}

效果

ForegroundCallbacks.gif

方案二:使用 ProcessLifecycleOwner

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

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

[图片上传失败...(image-17d168-1731395595528)]

使用方法

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")

            })
        }
    }
                                                              }

效果

onProcessLifecycle.gif

源码解读

核心代码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在变化。这就是设置延时的作用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 226,913评论 6 527
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,710评论 3 412
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 174,561评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,278评论 1 306
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,080评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,604评论 1 320
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,698评论 3 434
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,847评论 0 285
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,361评论 1 329
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,330评论 3 353
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,487评论 1 365
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,044评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,738评论 3 342
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,134评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,378评论 1 281
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,053评论 3 385
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,471评论 2 370

推荐阅读更多精彩内容