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()
    }
    
}

登出逻辑

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

故一般在 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)
    }
}
相关推荐
mmsx2 分钟前
Android 测绘数值基础:测量单位系统与七四参数、高程拟合
android
郑州光合科技余经理11 分钟前
出海外卖平台:支付对接为什么要和下单收款分开
android·java·开发语言·前端·后端·php·ai编程
夜雪一千12 分钟前
MySQL 如何提高查询速度:从索引、SQL、表结构到实战调优
android·sql·mysql
终端安全笔记30 分钟前
配置锁状态那一栏查的是什么:报告里几项各自指向什么风险
android·前端·安全·ios
恋猫de小郭40 分钟前
Kotlin Toolchain 0.13,JetBrains 推荐 KMP 都切换到到全新工具链
android·前端·flutter
安卓机器1 小时前
rom定制系列------小米8 安卓16线刷原生固件 体验新版本
android·小米8安卓16
cyy2981 小时前
小米电视安装tts引擎
android·tv
小锋学长生活大爆炸1 小时前
【工具】在 Android 手机上运行 Docker
android·docker·智能手机
Carson带你学Android1 小时前
OpenAI Dots 登陆手机端:手机上派任务,AI 在云端写代码
android·ios
事圆则缓2 小时前
Android 依赖注入库到底应该怎么选
android