在Kotlin中为Android实现自动注销功能
在应用程序中处理敏感数据可能需要进行一些检查,以了解用户是否仍在活动,并决定是否让他/她继续登录。我们将使用一种简单明了的方法:手势检测器和处理程序类来设置超时时间间隔。
这里使用的是同样的逻辑,但我们将为Android做这个。让我们直接进入概念。
前提条件
要贯彻执行,你需要具备以下条件。
- 对Kotlin和一般的编程(OOP)有基本了解。
- 安卓/IntellijIDEA(为安卓开发而配置)在你的机器上安装并工作。
- 有构建Android应用程序的知识。
我们将做什么
我们将创建一个应用程序,显示一个对话框,询问用户他/她是否仍在活动。显示对话框的方法将在用户不活动的15秒后被触发。我们通过检查用户是否在点击屏幕来检查他的活跃程度。
我们将手势检测器嵌入到整个屏幕上,而不是单个子类的视图上,使我们能够检测到将成为最终输出的手势。
在我们的应用程序中自动注销的理论
对于检测到的任何手势,都会进行以下操作。
我们首先计算在屏幕上检测到的每个手势所做的点击次数。例如,如果用户滚动,点击次数会增加1。如果他再次滚动,计数会再次增加,使点击次数达到2。
其次,用户的活动状态将被设置为真。
然后,我们开始跟踪用户的活动状态。这里有一个问题。为了避免对话框被多次显示,我们将只调用对话框显示方法的处理程序。我们通过在点击量为1时调用这个方法来实现。
我们先前说过,我们将在15秒后显示对话框。我们把它分成10秒和5秒。一旦我们开始检测到用户与应用程序互动后的不活动状态,就等待10秒。之后,故意将活动状态设置为假,再次等待5秒,并重新检查活动状态。
如果用户没有与屏幕进行互动,肯定状态还是假的。现在它是假的,我们就显示对话框。否则,当他/她与屏幕进行交互时,活动状态将为真,所以不显示对话框。就这么简单。
在我们开始创建应用程序之前,我们将简要地看一下我们将使用的两个 "工具"。
GestureDetector类包含检测手势的辅助方法,如滚动、甩动、轻扫等。Handler类使我们能够在一个特定的时间内采取行动。
GestureDetector类
为了检测所有常见的手势,我们实现了GestureDetector.OnGestureListener 接口。为了检测一个子集的手势,我们扩展GestureDetector.SimpleOnGestureListener 。 我们将在我们的应用程序中使用第一个选项。
让我们看一下这个代码片段。
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.GestureDetector
import android.view.MotionEvent
import androidx.core.view.GestureDetectorCompat
class MainActivity :
AppCompatActivity(),
GestureDetector.OnGestureListener {
private lateinit var ourDetector: GestureDetectorCompat
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Instantiating the gesture detector
/*We pass in the application context and an implementation of
GestureDetector.OnGestureListener as the arguments*/
ourDetector = GestureDetectorCompat(this, this)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
return if (ourDetector.onTouchEvent(event)) {
true
} else {
super.onTouchEvent(event)
}
}
override fun onDown(event: MotionEvent): Boolean {
//do something
return true
}
override fun onFling(
event1: MotionEvent,
event2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
//do something
return true
}
override fun onLongPress(event: MotionEvent) {
//do something
}
override fun onScroll(
event1: MotionEvent,
event2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
//do something
return true
}
override fun onShowPress(event: MotionEvent) {
//do something
}
override fun onSingleTapUp(event: MotionEvent): Boolean {
//do something
return true
}
我们注意到的第一件事是,我们的活动类扩展了接口(GestureDetector.OnGestureListener)。由于它是一个接口,我们必须使用它的所有方法,你可以看到。我们重写它们以放入我们的自定义代码。在onCreate() 方法中,我们将手势检测器实例化,然后将其传入当前的应用程序上下文和GestureDetector.OnGestureListener的实现作为参数。
在所有的重载方法中,你会注意到两个相似之处--它们接收一个MotionEvent对象并返回true。MotionEvent用于报告活动中发生的运动事件。我们返回true ,以显示对该事件的检测。对于onFling() 和onScroll() 方法,我们传入额外的参数,用于检测两个手势执行的位置。
在创建游戏和其他产品时,它们可能是必要的。在我们的文章中,我们将不使用它们。
处理程序类
当我们想执行一个特定的动作时,如调用一个方法、打开一个活动、在特定的时间过后发送一个短信时,就会用到这个类。
以这个片段为例。
Handler(Looper.getMainLooper()).postDelayed({
//the method(callback method) to be called
displayDialog()
}, 5000)
在Handler 类的构造函数中,我们从Looper的getMainLooper() 方法中传入一个postDelayed() 方法。Looper是用来运行一个线程的消息循环的。getMainLooper() ,顾名思义,让循环器在应用程序的主线程中运行。
postDelayed() 方法使一个Runnable在指定时间后运行。在这种情况下,Runnable可能是一个方法。看看这段代码。
public final boolean postDelayed (Runnable outRunnable, Object ourObject, long delayTime)
它有三个参数。
- Runnable接口,它不能是空的。
- 一个Object实例,用于取消Runnable。它可以是空的。
- 一个长类型,用于存储在调用runnable之前要等待的时间,单位是毫秒。
创建应用程序
现在我们已经了解了理论和我们将在文章中使用的东西,我们将继续创建我们的应用程序。选择Empty Activity选项,设置你喜欢的名字(我叫它AutoLogout),选择语言为Kotlin,然后完成。
修改MainActivity.kt文件
在你的包名声明之后,复制并粘贴以下代码到你的MainActivity.kt文件。
import android.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.GestureDetector
import android.view.MotionEvent
import androidx.core.view.GestureDetectorCompat
class MainActivity :
AppCompatActivity(),
GestureDetector.OnGestureListener {
private lateinit var ourDetector: GestureDetectorCompat
private var timeset: Long = 10000
private var noOfClicks: Int = 0
private var isActive: Boolean = false
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Instantiating the gesture detector
/*We pass in the application context and an implementation of
GestureDetector.OnGestureListener as the arguments*/
ourDetector = GestureDetectorCompat(this, this)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
return if (ourDetector.onTouchEvent(event)) {
true
} else {
super.onTouchEvent(event)
}
}
override fun onDown(event: MotionEvent): Boolean {
startDetection()
return true
}
override fun onFling(
event1: MotionEvent,
event2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
startDetection()
return true
}
override fun onLongPress(event: MotionEvent) {
startDetection()
}
override fun onScroll(
event1: MotionEvent,
event2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
startDetection()
return true
}
override fun onShowPress(event: MotionEvent) {
startDetection()
}
override fun onSingleTapUp(event: MotionEvent): Boolean {
startDetection()
return true
}
//a method to start detection
fun startDetection() {
//set the user's active status to true then increase the number of clicks by 1
isActive = true
noOfClicks++
//dont listen more than once
//simply do nothing :)
if (noOfClicks > 1) {
} else if (noOfClicks == 1) {
startListener()
}
}
fun startListener() {
//check the user's activeness after a specified time in milliseconds
Handler(Looper.getMainLooper()).postDelayed({
//displayDialog()
checkActiveness()
}, timeset)
}
fun checkActiveness() {
/*set the active status to false deliberately
and wait for 5 seconds to see if it will change to true
*/
isActive = false
Handler(Looper.getMainLooper()).postDelayed({
displayDialog()
}, 5000)
}
fun displayDialog() {
//if the user is still inactive, show the dialog
/*reset the number of clicks to zero to start
the detection incase the dialog is dismissed*/
if (isActive == false) {
noOfClicks = 0
val displayDialog = AlertDialog.Builder(this)
displayDialog.apply {
setTitle("Autologout")
setMessage("It seems you have you have been away for a while. Would you like us to sign you out?")
setPositiveButton("Yes, Sign out") { _, _ ->
}
setNegativeButton("Keep me in") { _, _ ->
}
}.create().show()
} else {
/*if active, set the clicks to zero also
to allow the detection to start as the user clicks/taps the screen
*/
noOfClicks = 0
}
}
}
在导入所需的包后,我们再声明变量来保存我们的检测器对象,实例化值来保存点击次数、延迟时间和用户的活动状态。重载方法的实现与我们之前讨论的一样,只是我们给每个方法传入一个startDetection() 。让我们看看startDetection() 方法做什么。
//a method to start detection
fun startDetection() {
//set the user's active status to true then increase the number of clicks by 1
isActive = true
noOfClicks++
//dont listen more than once
//simply do nothing :)
if (noOfClicks > 1) {
} else if (noOfClicks == 1) {
startListener()
}
}
这里是不活动检测开始的地方。我们首先将用户的活动状态设置为true ,并将点击次数增加1。一个条件检查允许在第一个点击被传递后只听一次点击。
转到startListener() 方法。
fun startListener() {
//check the user's activeness after a specified time in milliseconds
Handler(Looper.getMainLooper()).postDelayed({
checkActiveness()
}, timeset)
}
在存储在timeset 变量(10秒)中的时间过后,我们调用该方法来检查用户的活跃度。该方法被称为checkActiveness() ,接下来讨论。
fun checkActiveness() {
/*set the active status to false deliberately
and wait for 5 seconds to see if it will change to true
*/
isActive = false
Handler(Looper.getMainLooper()).postDelayed({
displayDialog()
}, 5000)
}
在10秒之后,故意将活跃状态设置为false,并等待5秒钟,看它是否会变为true 。如果仍然是false ,显示对话框,正如我们在最后一个方法displayDialog() 。
fun displayDialog() {
//if the user is still inactive, show the dialog
/*reset the number of clicks to zero to start
the detection incase the dialog is dismissed*/
if (isActive == false) {
noOfClicks = 0
val displayDialog = AlertDialog.Builder(this)
displayDialog.apply {
setTitle("Autologout")
setMessage("It seems you have you have been away for a while. Would you like us to sign you out?")
setPositiveButton("Yes, Sign out") { _, _ ->
}
setNegativeButton("Keep me in") { _, _ ->
}
}.create().show()
} else {
/*if active, set the clicks to zero also
to allow the detection to start as the user clicks/taps the screen
*/
noOfClicks = 0
}
}
如果用户仍然不活动,显示对话框并将点击次数重置为0,开始检测。如果用户和手势解散,则检测对话框。如果激活,再次将点击数设置为0,以便在用户点击/触摸屏幕时开始检测。
请注意,我们必须在屏幕上做一些触摸,才能让这些方法开始启动。
如果你不想等到有人触碰屏幕,你可以在实例化手势检测器后,在Activity的onCreate() 方法中也调用startDetection() 方法。
布局XML( activity_main.xml )文件
这是XML代码。它包含一个TextView,显示 "Hello there.你将在15秒后被注销"。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello there. You will be logged out after 15 seconds"
android:id="@+id/sampleTxt"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" android:paddingLeft="15dp" android:paddingRight="15dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
运行该应用程序,做一些触摸、滚动,并等待15秒来检查该功能。同时,在几秒钟过去之前做同样的事情,并进行检查。改变数字并再次检查。
进一步练习
现在,这只是针对一个屏幕。如果你想在许多屏幕上实现它呢?你会在每个活动中重写代码吗?当然,不会。所以,喝些咖啡,写一个可继承(可扩展)的类,你可以在任何屏幕上调用。
你也可以让这个函数打开另一个活动而不是对话框。你可以为过去的5秒实现一个计数器,然后在以后做一个特定的动作,比如打开一个登录界面。
总结
我们看了工作原理,简单地谈了一下GestureDetector和Handler类,最后用这两个类创建了一个自动注销应用程序。希望你有一个很好的阅读体验。