Android实战进阶 - 用户闲置超时自动退出登录功能详解
在很多金融、银行类app中,当用户长时间未操作时会提示用户即将退出登录状态,我们这里就是为了解决该种场景的
关联篇
- Android进阶之路 - 长时间未操作屏幕唤出屏保(之前写的业务功能,于此篇有不少相似之处)
- Android进阶之路 - 前后台切换监听(可借鉴:有种保护会同步计算用户处于后台的时间,超时则会自动退出)
实践方案采用了双定时设计 + 广播组件
- 第一个定时器是保护时间,当用户xx分钟不操作app就会唤醒第二个定时器
- 第二个定时器会执行唤醒、退出等操作
思考点
- 定时器绑定组件的生命周期
MotionEvent.ACTION_UP
下开启定时器Timber
是一个Log
框架,不需要删除即可- 关于
ConstValue
下的各常量,可以直接直接整理为统一类,也可以直接声明,看自己习惯 - 定时器触发的退出登录弹框自己写一个就好,若有机会以后会补一个
软考还有好多题没刷...
- 超时保护
- 登出逻辑
- 绑定组件
超时保护
package cn.com.xximport 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? = nullinit {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 = nulltimer?.cancel()timer = nullcancel()
// lifecycleOwner.lifecycle.removeObserver(this)}override fun onTick(millisUntilFinished: Long) = Unitoverride 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 = nullstartTime() // 重新计时}.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 = nulltimer?.cancel()timer = null//这里才是触发倒计时结束后执行的核心逻辑入口AppContext.sendBroadcast(Intent(ConstValue.LONG_TIME_NO_OPERATED_LOGIN_RECEIVER))}}}timer?.start()appDialog?.show()}}
登出逻辑
关于四大组件之一的广播,可静态注册,也可动态注册,但是从某一版本后好像基本都要求动态注册了!
故一般在 MainActivity
的 onCreate
直接 LogoutBroadcastReceiver(this)
注册即可! (不再单独写代码记录了)
package cn.com.xximport 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.OnLifecycleEventclass 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 = trueLoginBean.getInstance().isKickoff = trueARouter.getInstance().build(RouterPath.USER_LOGIN_ACT).navigation()}}}@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)private fun onDestroy() {activity.unregisterReceiver(this)}
}
绑定组件
定时器需要关联对应的组件才能生效,不然自身可监听不到用户
action
!
注意:所有 Activity
需要继承 BaseActivity
才能实现全局监听,未继承类则会出现倒计时保护失效的情况!
package cn.com.xximport 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.AppCompatActivityopen class BaseActivity : AppCompatActivity() {private lateinit var longTimeNoOperated: LongTimeNoOperatedoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)longTimeNoOperated = LongTimeNoOperated(this)}override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {longTimeNoOperated.dispatchTouchEvent(ev)return super.dispatchTouchEvent(ev)}
}