在很多金融、银行类app中,当用户长时间未操作时会提示用户即将退出登录状态,我们这里就是为了解决该种场景的
关联篇
- Android进阶之路 - 长时间未操作屏幕唤出屏保(之前写的业务功能,于此篇有不少相似之处)
- Android进阶之路 - 前后台切换监听(可借鉴:有种保护会同步计算用户处于后台的时间,超时则会自动退出)
实践方案采用了双定时设计 + 广播组件
- 第一个定时器是保护时间,当用户xx分钟不操作app就会唤醒第二个定时器
- 第二个定时器会执行唤醒、退出等操作
思考点
- 定时器绑定组件的生命周期
MotionEvent.ACTION_UP下开启定时器Timber是一个Log框架,不需要删除即可- 关于
ConstValue下的各常量,可以直接直接整理为统一类,也可以直接声明,看自己习惯 - 定时器触发的退出登录弹框自己写一个就好,若有机会以后会补一个
软考还有好多题没刷...
超时保护
kotlin
package cn.com.xx
import android.content.Intent
import android.os.CountDownTimer
import android.os.Handler
import android.os.Looper
import android.view.MotionEvent
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
/**
* 长时间未操作唤醒、退出保护
* */
class LongTimeNoOperated(val lifecycleOwner: LifecycleOwner): CountDownTimer(MILLIS_IN_FUTURE, 1000), LifecycleObserver, Runnable {
companion object {
//该处定义为10分钟保护期
private const val MILLIS_IN_FUTURE = 10 * 60 * 1000L + 20 * 1000
}
private var timer: CountDownTimer? = null
//提醒弹框
private var appDialog: AppDialog? = null
init {
lifecycleOwner.lifecycle.addObserver(this)
}
fun dispatchTouchEvent(ev: MotionEvent?) {
//获取触摸动作,如果ACTION_UP,计时开始
if (ev?.action == MotionEvent.ACTION_UP) {
startTime()
} else { //否则其他动作计时取消
cancel()
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun startTime() {
//判断用户是否处于登录状态(结合自己项目的判别方法)
if (LoginInfo.isLogin()) {
cancel()
start()
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
cancel()
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
//防止内存泄露
if (appDialog?.isShowing == true) {
appDialog?.dismiss()
}
appDialog = null
timer?.cancel()
timer = null
cancel()
// lifecycleOwner.lifecycle.removeObserver(this)
}
override fun onTick(millisUntilFinished: Long) = Unit
override fun onFinish() {
Handler(Looper.getMainLooper()).post(this)
}
override fun run() {
//当用户长时间未操作时交互方面会出现提示弹框,其实多久之后会自行退出账户
appDialog = AppDialog.Builder()
.setTitle("提示")
.setDelayMillis(200)
.setMessage("您长时间未操作,10秒后将自动为您退出账户")
.setNeutralButton("继续使用") {
Timber.e("LongTimeNoOperated--->继续使用")
timer?.cancel()
timer = null
startTime() // 重新计时
}.create()
//第二个定时弹框,主要用户用户可见的显性倒计时(当下为10秒倒计时)
timer = object : CountDownTimer(10 * 1000, 1000) {
override fun onTick(millisUntilFinished: Long) {
Handler(Looper.getMainLooper()).post {
appDialog?.setMessage("您长时间未操作,${millisUntilFinished / 1000 + 1}秒后将自动为您退出账户")
}
}
override fun onFinish() {
Timber.e("超时 LongTimeNoOperated --> onFinish()")
Handler(Looper.getMainLooper()).post {
if (appDialog?.isShowing == true) {
appDialog?.dismiss()
}
appDialog = null
timer?.cancel()
timer = null
//这里才是触发倒计时结束后执行的核心逻辑入口
AppContext.sendBroadcast(Intent(ConstValue.LONG_TIME_NO_OPERATED_LOGIN_RECEIVER))
}
}
}
timer?.start()
appDialog?.show()
}
}
登出逻辑
关于四大组件之一的广播,可静态注册,也可动态注册,但是从某一版本后好像基本都要求动态注册了!
故一般在 MainActivity 的 onCreate 直接 LogoutBroadcastReceiver(this) 注册即可! (不再单独写代码记录了)
kotlin
package cn.com.xx
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
class LogoutBroadcastReceiver(val activity: AppCompatActivity) : BroadcastReceiver(), LifecycleObserver {
init {
activity.lifecycle.addObserver(this)
val intentFilter = IntentFilter()
//在项目中其实有很多场景都会执行退出操作,例如账户被顶,系统维护、自行退出等场景,所以可以自行过滤多种广播
//其他几种场景,后续有机会一起总结,关注顶部关联篇即可
intentFilter.addAction(ConstValue.RE_LOGIN_RECEIVIR)
intentFilter.addAction(ConstValue.SYS_MAINTAIN)
intentFilter.addAction(ConstValue.LONG_TIME_NO_OPERATED_LOGIN_RECEIVER)
activity.registerReceiver(this, intentFilter)
}
override fun onReceive(context: Context, intent: Intent) {
//根据不同业务场景执行不同逻辑就可以,以下仅是伪代码,用于提供开发思路(如有误导,可自行删除不同action下的执行逻辑)
when (intent.action) {
ConstValue.SYS_MAINTAIN -> { // 系统维护
Timber.e("系统维护的广播 LogoutBroadcastReceiver --> 用户被踢出去")
activity.startActivity(Intent(activity, MaintainDlgActivity::class.java).apply {
//intent可以传外部发送广播时传入的值
putExtra("notice", intent.getStringExtra("notice"))
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
})
}
ConstValue.RE_LOGIN_RECEIVIR -> {// 弹窗提示重新登录
Timber.e("弹窗提示 LogoutBroadcastReceiver --> 用户被踢出去")
activity.startActivity(Intent(activity, LogoutActivity::class.java).apply {
//intent可以传固定值
putExtra("reLogin", true)
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
})
}
ConstValue.LONG_TIME_NO_OPERATED_LOGIN_RECEIVER -> {// 退出
Timber.e("超时 LogoutBroadcastReceiver --> 用户被踢出去")
//可以直接在当下执行逻辑,也可以跳转到新类后执行,具体看业务场景
LoginInfo.setToken("")
LoginBean.getInstance().isGoHome = true
LoginBean.getInstance().isKickoff = true
ARouter.getInstance().build(RouterPath.USER_LOGIN_ACT).navigation()
}
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
private fun onDestroy() {
activity.unregisterReceiver(this)
}
}
绑定组件
定时器需要关联对应的组件才能生效,不然自身可监听不到用户
action!
注意:所有 Activity 需要继承 BaseActivity 才能实现全局监听,未继承类则会出现倒计时保护失效的情况!
kotlin
package cn.com.xx
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Bundle
import android.view.MotionEvent
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity
open class BaseActivity : AppCompatActivity() {
private lateinit var longTimeNoOperated: LongTimeNoOperated
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
longTimeNoOperated = LongTimeNoOperated(this)
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
longTimeNoOperated.dispatchTouchEvent(ev)
return super.dispatchTouchEvent(ev)
}
}