如何用Kotlin实现Android的自动注销功能

275 阅读9分钟

在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)

它有三个参数。

  1. Runnable接口,它不能是空的。
  2. 一个Object实例,用于取消Runnable。它可以是空的。
  3. 一个长类型,用于存储在调用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类,最后用这两个类创建了一个自动注销应用程序。希望你有一个很好的阅读体验。