我们经常会在银行、金融或者其他行业的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
原理,例如 Looper
、Therad
等
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>