3

我使用 androidx.biometric:biometric:1.0.1一切正常,但是当我有一个没有生物识别传感器的设备(或者当用户没有设置他的指纹等)并且我在进行身份验证后尝试使用DeviceCredentials 时,我的函数输入数据无效。

class MainActivity : AppCompatActivity() {

    private val TAG = MainActivity::class.java.name

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<View>(R.id.first).setOnClickListener {
            authenticate(MyData(1, "first"))
        }

        findViewById<View>(R.id.second).setOnClickListener {
            authenticate(MyData(2, "second"))
        }
    }

    private fun authenticate(data: MyData) {
        Log.e(TAG, "starting auth with $data")
        val biometricPrompt = BiometricPrompt(
            this,
            ContextCompat.getMainExecutor(this),
            object : BiometricPrompt.AuthenticationCallback() {
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    Log.e(TAG, "auth done : $data")
                }
            })

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setDeviceCredentialAllowed(true)
            .setTitle("title")
            .build()
        biometricPrompt.authenticate(promptInfo)
    }
}

data class MyData(
    val id: Int,
    val text: String
)

首先我点击我的first按钮,进行身份验证,然后我点击我的second按钮并进行身份验证,然后android logcat是这样的:

E/com.test.biometrictest.MainActivity: starting auth with MyData(id=1, text=first)
E/com.test.biometrictest.MainActivity: auth done : MyData(id=1, text=first)
E/com.test.biometrictest.MainActivity: starting auth with MyData(id=2, text=second)
E/com.test.biometrictest.MainActivity: auth done : MyData(id=1, text=first)

正如您在最后一行中看到的 MyData id 和 text 无效!autneticate调用 onAuthenticationSucceeded 时函数 input(data) 不一样!

(如果您尝试对其进行测试,请务必使用 DeviceCredentials 而不是生物特征,我的意思是模式或密码,请取消设置您的指纹) 为什么数据在回调中无效?

它可以在 android 10 或指纹上正常工作

我不想使用 onSaveInstanceState。

4

3 回答 3

6

当您创建一个新的BiometricPrompt类实例时,它会LifecycleObserver向活动添加一个,并且我发现它永远不会删除它。因此,当您BiometricPrompt在一个活动中有多个实例时,同时有多个实例LifecycleObserver会导致此问题。

对于 Android Q 之前的设备,有一个名为的透明活动DeviceCredentialHandlerActivity和一个名为的桥接类DeviceCredentialHandlerBridge,它们支持设备凭据身份验证。BiometricPrompt管理不同状态的网桥,如果需要,最终调用该onResume状态的回调方法(离开凭证窗口后返回活动时)。当有多个LifecycleObserver时,第一个将处理结果并重置桥,因此其他观察者无事可做。这就是第一个回调实现在您的代码中调用两次的原因。

解决方案: 您应该LifecycleObserver在创建BiometricPrompt类的新实例时从活动中移除。由于无法直接访问观察者,因此您需要在此处使用反射。我根据此解决方案修改了您的代码,如下所示:

class MainActivity : AppCompatActivity() {

    private val TAG = MainActivity::class.java.name
    private var lastLifecycleObserver: LifecycleObserver? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<View>(R.id.first).setOnClickListener {
            authenticate(MyData(1, "first"))
        }

        findViewById<View>(R.id.second).setOnClickListener {
            authenticate(MyData(2, "second"))
        }
    }

    private fun authenticate(data: MyData) {
        Log.e(TAG, "starting auth with $data")
        lastLifecycleObserver?.let {
            lifecycle.removeObserver(it)
            lastLifecycleObserver = null
        }
        val biometricPrompt = BiometricPrompt(
                this,
                ContextCompat.getMainExecutor(this),
                object : BiometricPrompt.AuthenticationCallback() {
                    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                        Log.e(TAG, "auth done : $data")
                    }
                })

        var field = BiometricPrompt::class.java.getDeclaredField("mLifecycleObserver")
        field.isAccessible = true
        lastLifecycleObserver = field.get(biometricPrompt) as LifecycleObserver

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
                .setDeviceCredentialAllowed(true)
                .setTitle("title")
                .build()
        biometricPrompt.authenticate(promptInfo)
    }
}

data class MyData(
        val id: Int,
        val text: String
)
于 2020-01-07T23:48:29.597 回答
0

所以这看起来很奇怪,但我设法通过引入一个参数来让它工作MainActivity

这是工作代码:

class MainActivity : AppCompatActivity() {

    var dataParam : MyData? = null

    companion object {
        private val TAG = MainActivity::class.java.name

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<View>(R.id.firstBtn).setOnClickListener {
            authenticate(MyData(1, "first"))
        }

        findViewById<View>(R.id.secondBtn).setOnClickListener {
            authenticate(MyData(2, "second"))
        }
    }


    private fun authenticate(data: MyData) {
        Log.e(TAG, "starting auth with $data")
        dataParam = data
        val biometricPrompt = BiometricPrompt(
                this,
                ContextCompat.getMainExecutor(this),
                object : BiometricPrompt.AuthenticationCallback() {
                    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                        Log.e(TAG, "auth done : $dataParam")
                    }
                })

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
                .setDeviceCredentialAllowed(true)
                .setTitle("title")
                .build()
        biometricPrompt.authenticate(promptInfo)
    }
}

data class MyData(
        val id: Int,
        val text: String
)

现在的输出是:

E/com.worldsnas.bioissuesample.MainActivity: starting auth with MyData(id=1, text=first)
E/com.worldsnas.bioissuesample.MainActivity: auth done : MyData(id=1, text=first)
E/com.worldsnas.bioissuesample.MainActivity: starting auth with MyData(id=2, text=second)
E/com.worldsnas.bioissuesample.MainActivity: auth done : MyData(id=2, text=second)
于 2020-01-07T20:43:13.670 回答
0

由于您在询问,因此可以肯定setDeviceCredentialAllowed(true)假设您没有遵循使用. (另请查看此博客文章。)CryptoObject

setDeviceCredentialAllowed(true)功能仅适用于 API 21+,但根据您的 minSdkVersion,您可以在应用程序中使用多种选项来处理它。

API 23+

如果您的应用程序的目标是 API 23+,那么您可以这样做

 if (keyguardManager.isDeviceSecure()){
     biometricPrompt.authenticate(promptInfo)
 }

API 16 到 API 23 之前

如果您的应用必须在 API 23 之前进行检查,您可以使用

if (keyguardManager.isKeyguardSecure) {
   biometricPrompt.authenticate(promptInfo)
}

KeyguardManager.isKeyguardSecure()相当于isDeviceSecure()除非设备被 SIM 卡锁定。

API 14 至 API 16

如果您的目标低于 API 16 或 SIM 锁定是一个问题,那么您应该简单地依赖回调中的错误代码onAuthenticationError()

PS您应该替换private val TAG = MainActivity::class.java.nameprivate val TAG = "MainActivity".

于 2020-01-07T21:22:20.413 回答