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)
    }
}
相关推荐
Lei活在当下7 小时前
【项目踩坑实录】并发环境下,Glide缓存引起的图片加载异常
android·debug·glide
my_power52010 小时前
检出git项目到android studio该如何配置
android·git·android studio
三少爷的鞋13 小时前
Repository 方法设计:suspend 与 Flow 的决选择指南(以朋友圈为例)
android
阿里云云原生13 小时前
Android App 崩溃排查指南:阿里云 RUM 如何让你快速从告警到定位根因?
android·java
cmdch201715 小时前
手持机安卓新增推送按钮功能
android
攻城狮201515 小时前
【rk3528/rk3518 android14 kernel-6.10 emcp sdk】
android
何妨呀~15 小时前
mysql 8服务器实验
android·mysql·adb
QuantumLeap丶16 小时前
《Flutter全栈开发实战指南:从零到高级》- 25 -性能优化
android·flutter·ios
木易 士心18 小时前
MVC、MVP 与 MVVM:Android 架构演进之路
android·架构·mvc
百锦再18 小时前
国产数据库的平替亮点——关系型数据库架构适配
android·java·前端·数据库·sql·算法·数据库架构