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)
    }
}
相关推荐
石山岭13 小时前
自己动手写了一个 Android 虚拟定位 App:GPSSimulate 技术实
android·前端
杉氧15 小时前
副作用 (Side Effects) 全攻略:如何像大师一样掌控 Composable 的生命周期?
android·架构·android jetpack
Kapaseker19 小时前
Kotlin Toolchain 0.11 发布:主要是把 Amper 干没了
android·kotlin
三少爷的鞋20 小时前
Android 现代架构不需要事件总线进阶篇
android
杉氧1 天前
深入理解 Compose 重组机制:快照系统如何驱动 UI 精准刷新?
android·架构·android jetpack
召钱熏1 天前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
杉氧1 天前
深度解析:Jetpack Compose 核心架构与底层原理 —— 十年安卓老兵的“破茧重生”
android·架构·android jetpack
通玄1 天前
Jetpack Compose 入门系列(七):ViewModel 与界面状态管理
android
落魄Android在线炒饭1 天前
Android Framework 开发技巧:android.jar 生成与系统快速编译验证
android
如此风景2 天前
Kotlin Flow操作符学习
android·kotlin