Android实战进阶 - 用户闲置超时自动退出登录功能详解

在很多金融、银行类app中,当用户长时间未操作时会提示用户即将退出登录状态,我们这里就是为了解决该种场景的

关联篇

实践方案采用了双定时设计 + 广播组件

  1. 第一个定时器是保护时间,当用户xx分钟不操作app就会唤醒第二个定时器
  2. 第二个定时器会执行唤醒、退出等操作

思考点

  • 定时器绑定组件的生命周期
  • 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()
    }
    
}

登出逻辑

关于四大组件之一的广播,可静态注册,也可动态注册,但是从某一版本后好像基本都要求动态注册了!

故一般在 MainActivityonCreate 直接 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)
    }
}
相关推荐
阿巴斯甜18 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker19 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952720 小时前
Andorid Google 登录接入文档
android
黄林晴21 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
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
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android