Android进阶之路 - app后台切回前台触发超时保护退出登录

我们经常会在银行、金融或者其他行业的app中看到用户长时间将app放置于后台,当再次唤醒app时就会提示用户已退出登录,需要重新登录,那么该篇主要就是用于处理这种场景的

针对于放置后台的超时保护属于进程级别,所以我们需要监听进程的生命周期,主要用到了 Lifecycle 组件,有兴趣的可以去 组件化之路 - Lifecycle一知半解 了解一下如何监听进程的生命周期?

以前写过一篇 前后台切换监听 ,也是用于监听组件生命周期的,可以参考参考

    • [创建观察者 - 监听生命周期](#创建观察者 - 监听生命周期)
    • [关于 Handler 、Thread 扩展函数](#关于 Handler 、Thread 扩展函数)
    • [观察者 绑定 进程生命周期](#观察者 绑定 进程生命周期)
    • 初始化配置
      • [Application 初始化监听](#Application 初始化监听)
      • [绑定 Application](#绑定 Application)

实现思路:通过监听进程的生命周期从而判断app处于前后台的状态,在不同状态下进行计时操作,当状态切换后判断是否超过所设时间,从而执行相关逻辑

这里并不涉及什么原理,最多就是有兴趣看看 Lifecycle 对于 Android 常用组件(Activity、Service、Process)在生命周期方面如何绑定、监听等

废话不多说,直接向目标进发!

创建观察者 - 监听生命周期

主要用于监听app处于前后台的一个状态,以及前后台切换后的时差是否超过保护时间,如果超过则可以将用户踢出去,让其重新登录

kotlin 复制代码
package com.example.lifestartupdemo

import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent

// 超时重新登录提示
internal class LoginStateObserver : LifecycleObserver {

    companion object {
        private const val interval = 10 * 1000L //保护时间10s,可自行设置
    }

    private var timestamp = 0L  //onPause 时间点

    /**
     * 应用程序出现到前台时调用
     */
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {
        Log.e("tag", "应用 onResume() - 前台")
        val currentTime = System.currentTimeMillis()
        if (timestamp != 0L && currentTime - timestamp > interval) {     // 后台超过保护时间,需要执行的逻辑
            timestamp = 0
            mainHandler.postDelayed(300) {
                //常见于清空用户信息,请用户重新登录
                Log.e("tag", "应用由后台到了前台,进入了超时逻辑")
            }
        }
    }

    /**
     * 应用程序退出到后台时调用
     */
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() {
        Log.e("tag", "应用 onPause() 已被切换至后台")
        timestamp = System.currentTimeMillis()
    }

}

关于 Handler 、Thread 扩展函数

这里主要涉及到了一些Handler原理,例如 LooperTherad

kotlin 复制代码
package com.example.lifestartupdemo

import android.os.Build
import android.os.Handler
import android.os.Looper


fun Handler.postDelayed(
    delayMillis: Long,
    runnable: Runnable
) = this.postDelayed(runnable, delayMillis)

@JvmField
val mainHandler: Handler = if (Build.VERSION.SDK_INT >= 28) Handler.createAsync(mainLooper) else try {
    Handler::class.java.getDeclaredConstructor(
        Looper::class.java,
        Handler.Callback::class.java,
        Boolean::class.javaPrimitiveType // async
    ).newInstance(mainLooper, null, true)
} catch (ignored: NoSuchMethodException) {
    Handler(mainLooper) // Hidden constructor absent. Fall back to non-async constructor.
}

MainThread(kt文件)

kotlin 复制代码
@file:Suppress("UNUSED")

package com.example.lifestartupdemo

import android.os.Looper

/** This main looper cache avoids synchronization overhead when accessed repeatedly. */
@JvmField
val mainLooper: Looper = Looper.getMainLooper()!!

@JvmField
val mainThread: Thread = mainLooper.thread

val isMainThread: Boolean inline get() = mainThread === Thread.currentThread()

@PublishedApi
internal val currentThread: Any?
    inline get() = Thread.currentThread()

观察者 绑定 进程生命周期

从架构而言有很多东西需要初始化,可以写一个接口便于解耦

kotlin 复制代码
package com.example.lifestartupdemo

import android.app.Application


interface ApplicationStartup {
    fun onCreate(application: Application)
}

具体绑定组件生命周期的实现类

kotlin 复制代码
package com.example.lifestartupdemo

import android.app.Application
import androidx.lifecycle.ProcessLifecycleOwner

internal class MineApplicationStartup : ApplicationStartup {

    override fun onCreate(application: Application) {
        ProcessLifecycleOwner.get().lifecycle.addObserver(LoginStateObserver())
    }
}

初始化配置

Application 初始化监听

kotlin 复制代码
package com.example.lifestartupdemo

import android.app.Application

class OurApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        //应用启动则初始化该配置
        val mineApplicationStartup = MineApplicationStartup()
        mineApplicationStartup.onCreate(this)
    }
}

绑定 Application

kotlin 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:name=".OurApplication"
        android:theme="@style/Theme.LifeStartupDemo"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
相关推荐
黄林晴14 分钟前
Android17 为什么重写 MessageQueue
android
阿巴斯甜21 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android