当前位置: 首页 > news >正文

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

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

关联篇

  • Android进阶之路 - 长时间未操作屏幕唤出屏保(之前写的业务功能,于此篇有不少相似之处)
  • Android进阶之路 - 前后台切换监听(可借鉴:有种保护会同步计算用户处于后台的时间,超时则会自动退出)

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

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

思考点

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

登出逻辑

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

故一般在 MainActivityonCreate 直接 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)}
}
http://www.dtcms.com/a/494845.html

相关文章:

  • 2二、u-boot移植
  • 淄博网站建设哪家好常德网站建设技术
  • Java Spring日志
  • OpenAI Agent Kit 全网首发深度解读与上手指南
  • 网络:2.Socket编程UDP
  • Linux服务器编程实践45-UDP数据读写:recvfrom与sendto函数的使用实例
  • 基于SpringBoot+Vue的数码交流管理系统(AI问答、协同过滤算法、websocket实时聊天、Echarts图形化分析)
  • 设计模式篇之 状态模式 State
  • linux系统编程(十)RK3568 socket之 UDP的实现
  • MySQL事务隔离
  • 甜点的网站建设规划书长春市城乡建设局网站
  • C++ 多线程实战 11|如何系统性避免死锁
  • WAPR断网攻击天阶大法根基法之wifi爆破
  • 集群冗余:高可用的核心设计
  • Vue 3 完全指南:响应式原理、组合式 API 与实战优化
  • Netscape 浏览器
  • 笔记:TFT_eSPI不支持ESP32C6;ESP8266运行LVGL注意事项
  • 会网站开发没学历seo网络营销
  • 简述深度学习中的四种数据并行方法(DP,DDP,TP,PP)
  • YOLO-World 全面解析:实时开放词汇目标检测的新范式(附实践指南)
  • 西瓜网络深圳网站建设 东莞网站建设电商型网站
  • AI+大数据时代:时序数据库的生态重构与价值跃迁——从技术整合到行业落地
  • 设计素材网站图案免费建设银行社保卡网站在哪
  • 预告!星火社吕诚将推 “星星之火” 线上课堂,哲思 + 投资赋能公益新生态
  • 孟德尔随机化 哪个计算最消耗时间 在肠道菌群、代谢物和疾病三类数据中,**肠道菌群数据的处理通常最消耗时间**
  • 【Redis学习】持久化机制(RDB/AOF)
  • 栈式自编码器(Stacked Auto-Encoder)
  • 像wordpress一样的网站建设银行网站转账必须u盾吗
  • 让低端机也能飞:Canvas/WebGL/Viz 分层、降级渲染与数据抽样策略
  • 【grafana查询超时问题】