Android 指纹验证
本篇记录一下在Android上做指纹校验的过程。在某些敏感场景,可以通过指纹验证操作者是否是设备主人。
本篇使用的androidx
下的Biometric
来实现的,FingerprintManagerCompat
已经被官方标记过时,就不过多描述了。
加入依赖
implementation 'androidx.biometric:biometric:1.1.0'
检查是否支持指纹验证
检查设备硬件是否支持或者是否设置了指纹(至少一个或更多)
BiometricManager.BIOMETRIC_SUCCESS == BiometricManager.from(context).canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
进行验证
核心类BiometricPrompt
,通过authenticate
方法启动验证。
- 创建一个
BiometricPrompt
mBiometricPrompt = BiometricPrompt(`activity or fragment`, `BiometricPrompt.AuthenticationCallback`)
- 进行验证
mBiometricPrompt?.authenticate(`BiometricPrompt.PromptInfo`)
上面伪代码中BiometricPrompt
的第一个参数,为Activity或Fragment,第二个参数为识别验证的回调,代码如下:
/**
* A collection of methods that may be invoked by {@link BiometricPrompt} during authentication.
*/
public abstract static class AuthenticationCallback {
/**
* Called when an unrecoverable error has been encountered and authentication has stopped.
*
* <p>After this method is called, no further events will be sent for the current
* authentication session.
*
* @param errorCode An integer ID associated with the error.
* @param errString A human-readable string that describes the error.
*/
public void onAuthenticationError(@AuthenticationError int errorCode, @NonNull CharSequence errString) {}
/**
* Called when a biometric (e.g. fingerprint, face, etc.) is recognized, indicating that the
* user has successfully authenticated.
*
* <p>After this method is called, no further events will be sent for the current
* authentication session.
*
* @param result An object containing authentication-related data.
*/
public void onAuthenticationSucceeded(@NonNull AuthenticationResult result) {}
/**
* Called when a biometric (e.g. fingerprint, face, etc.) is presented but not recognized as
* belonging to the user.
*/
public void onAuthenticationFailed() {}
}
- onAuthenticationError:指纹识别异常回调,包含几个常用错误码,详见:
- onAuthenticationSucceeded:指纹识别通过回调
- onAuthenticationFailed:指纹识别不通过回调
onAuthenticationError 错误码
/**
* An error code that may be returned during authentication.
*/
@IntDef({
ERROR_HW_UNAVAILABLE,
ERROR_UNABLE_TO_PROCESS,
ERROR_TIMEOUT,
ERROR_NO_SPACE,
ERROR_CANCELED,
ERROR_LOCKOUT,
ERROR_VENDOR,
ERROR_LOCKOUT_PERMANENT,
ERROR_USER_CANCELED,
ERROR_NO_BIOMETRICS,
ERROR_HW_NOT_PRESENT,
ERROR_NEGATIVE_BUTTON,
ERROR_NO_DEVICE_CREDENTIAL
})
@Retention(RetentionPolicy.SOURCE)
@interface AuthenticationError {}
上面伪代码中authenticate
方法的参数,为设置验证指纹弹窗的基础参数,代码大概长这样:
val promptInfo: BiometricPrompt.PromptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("这里设置Title")
.setSubtitle("这里设置Subtitle")
.setDescription("这里设置Description")
// .setDeviceCredentialAllowed(false)
.setNegativeButtonText("这里设置关闭按钮文案")
// .setAllowedAuthenticators()
.setConfirmationRequired(true)
.build()
流程很简单,下面提供一个工具类,可以直接拿去用:
BiometricUtils.kt
package com.kongqw.fingerprintdemo
import android.content.Context
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
class BiometricUtils {
private val mBuilder: BiometricPrompt.PromptInfo.Builder = BiometricPrompt.PromptInfo.Builder()
private var mBiometricPrompt: BiometricPrompt? = null
private var mAuthenticationErrorListener: IAuthenticationErrorListener? = null
private var mAuthenticationSucceededListener: IAuthenticationSucceededListener? = null
private var mAuthenticationFailedListener: IAuthenticationFailedListener? = null
/**
* 是否支持指纹识别
*/
fun isSupportBiometric(context: Context): Boolean {
return try {
BiometricManager.BIOMETRIC_SUCCESS == BiometricManager.from(context).canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
} catch (e: Exception) {
e.printStackTrace()
false
}
}
fun setTitle(title: String): BiometricUtils {
mBuilder.setTitle(title)
return this
}
fun setSubtitle(subtitle: String): BiometricUtils {
mBuilder.setSubtitle(subtitle)
return this
}
fun setDescription(description: String): BiometricUtils {
mBuilder.setDescription(description)
return this
}
fun setNegativeButtonText(negativeButtonText: String): BiometricUtils {
mBuilder.setNegativeButtonText(negativeButtonText)
return this
}
fun setAuthenticationErrorListener(listener: IAuthenticationErrorListener): BiometricUtils {
mAuthenticationErrorListener = listener
return this
}
fun setAuthenticationSucceededListener(listener: IAuthenticationSucceededListener): BiometricUtils {
mAuthenticationSucceededListener = listener
return this
}
fun setAuthenticationFailedListener(listener: IAuthenticationFailedListener): BiometricUtils {
mAuthenticationFailedListener = listener
return this
}
fun authenticate(fragmentActivity: FragmentActivity, succeededListener: IAuthenticationSucceededListener? = null, errorListener: IAuthenticationErrorListener? = null) {
succeededListener?.apply { mAuthenticationSucceededListener = succeededListener }
errorListener?.apply { mAuthenticationErrorListener = errorListener }
mBiometricPrompt = BiometricPrompt(fragmentActivity, /*{ command -> command?.run() },*/ FingerCallBack())
mBiometricPrompt?.authenticate(mBuilder.build())
}
fun authenticate(fragment: Fragment, succeededListener: IAuthenticationSucceededListener? = null, errorListener: IAuthenticationErrorListener? = null) {
succeededListener?.apply { mAuthenticationSucceededListener = succeededListener }
errorListener?.apply { mAuthenticationErrorListener = errorListener }
mBiometricPrompt = BiometricPrompt(fragment, /*{ command -> command?.run() },*/ FingerCallBack())
mBiometricPrompt?.authenticate(mBuilder.build())
}
inner class FingerCallBack : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
mAuthenticationErrorListener?.onAuthenticationError(errorCode, errString)
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
mBiometricPrompt?.cancelAuthentication()
mAuthenticationSucceededListener?.onAuthenticationSucceeded(result)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
mAuthenticationFailedListener?.onAuthenticationFailed()
}
}
interface IAuthenticationErrorListener {
fun onAuthenticationError(errorCode: Int, errString: CharSequence)
}
interface IAuthenticationSucceededListener {
fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult)
}
interface IAuthenticationFailedListener {
fun onAuthenticationFailed()
}
}
使用
初始化
private val mBiometricUtils = BiometricUtils()
检查是否支持指纹验证
val isSupportBiometric = mBiometricUtils.isSupportBiometric(applicationContext)
开始验证
mBiometricUtils.setTitle("指纹验证")
.setSubtitle("需要身份验证")
.setDescription("Description")
.setNegativeButtonText("关闭吧")
.setAuthenticationSucceededListener(object : BiometricUtils.IAuthenticationSucceededListener {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
Toast.makeText(applicationContext, "指纹通过", Toast.LENGTH_SHORT).show()
}
})
.setAuthenticationFailedListener(object : BiometricUtils.IAuthenticationFailedListener {
override fun onAuthenticationFailed() {
Toast.makeText(applicationContext, "指纹不通过", Toast.LENGTH_SHORT).show()
}
})
.setAuthenticationErrorListener(object : BiometricUtils.IAuthenticationErrorListener {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
// when(errorCode){
// BiometricPrompt.ERROR_HW_UNAVAILABLE -> ""
// BiometricPrompt.ERROR_UNABLE_TO_PROCESS -> ""
// BiometricPrompt.ERROR_TIMEOUT -> ""
// BiometricPrompt.ERROR_NO_SPACE -> ""
// BiometricPrompt.ERROR_CANCELED -> ""
// BiometricPrompt.ERROR_LOCKOUT -> ""
// BiometricPrompt.ERROR_VENDOR -> ""
// BiometricPrompt.ERROR_LOCKOUT_PERMANENT -> ""
// BiometricPrompt.ERROR_USER_CANCELED -> ""
// BiometricPrompt.ERROR_NO_BIOMETRICS -> "未注册任何指纹"
// BiometricPrompt.ERROR_HW_NOT_PRESENT -> ""
// BiometricPrompt.ERROR_NEGATIVE_BUTTON -> ""
// BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> ""
// }
Toast.makeText(applicationContext, "onAuthenticationError($errorCode, $errString)", Toast.LENGTH_SHORT).show()
}
})
mBiometricUtils.authenticate(this)