本资源库已上传至github,可直接用build.gradle添加依赖,链接如下:
https://github.com/JasonPearySamuel/BiometricUtil
前言
在网上搜索了不少关于Android指纹识别的开发教程,由于安卓的碎片化严重,大多文章都比较单一,有的只介绍了6.0~9.0的案例,有的只有9.0+的案例;虽然也有大牛自己封装了指纹识别类出来,我试着接入开源库放在自己的Android X项目中,但使用还是会报出异常,所以整理了以下代码。
这是大牛封装的开源库,但是我用起来会有异常,有兴趣的同学可以尝试一下。
https://github.com/ZuoHailong/BiometricPrompt
以下的代码均在Android X下进行开发,使用Kotlin编写,兼容了Android 6.0以上所有安卓版本,并都尽量整合在了同一个Util工具类中。但由于6.0与9.0的差异性,即9.0以下的安卓系统不提供专门的页面,所以9.0以下设备必须自定义指纹识别页,FingerprintDialogFragment来专门供9.0以下的机型显示指纹识别页面,Android 9.0的指纹识别开启代码较为简单,所以直接整理在了Util类里面。
关于9.0+的人脸解锁
Android系统提供的BiometricPrompt并没有支持2D/3D人脸解锁,市面上所见的具备人脸解锁的手机都是厂家对Android ROM做一定的适配而形成的,并非谷歌提供的人脸解锁接口。
BiometricPromptUtil
这是指纹识别的工具类,具备了打开9.0+设备的指纹识别监听与回调实现、9.0以下指纹识别秘钥生成、9.0以下指纹Dialog开启和判断当前设备是否支持指纹识别的功能。使用观察者模式设置监听器,将识别数据传递给外层的Activity或Fragment:
/**
* Android的生物识别
*/
class BiometricPromptUtil(val mContext: Context) {
private lateinit var keyStore: KeyStore // 6.0~9.0的指纹识别
private lateinit var mOnFingerResultListener: OnFingerResultListener
/**
* 判断是否支持指纹
* @return
*/
fun supportFingerprint(): Boolean {
if (Build.VERSION.SDK_INT < 23) {
//ToastUtil.showToast(mContext, "您的系统版本过低,不支持指纹功能")
return false
} else {
//键盘锁管理者
val keyguardManager = mContext.getSystemService(KeyguardManager::class.java)
//指纹管理者
val fingerprintManager = mContext.getSystemService(FingerprintManager::class.java)
if (!fingerprintManager.isHardwareDetected()) { //判断硬件支不支持指纹
//ToastUtil.showToast(mContext, "您的手机不支持指纹功能")
return false
} else if (!keyguardManager.isKeyguardSecure()) { //还未设置锁屏
ToastUtil.showToast(mContext, "您还未设置锁屏,请先设置锁屏并添加一个指纹")
return false
} else if (!fingerprintManager.hasEnrolledFingerprints()) { //指纹未登记
ToastUtil.showToast(mContext, "您至少需要在系统设置中添加一个指纹")
return false
}
}
return true
}
/**
* 判断是否支持人脸
* 注:目前安卓源生BiometricPrompt暂不支持调用系统的人脸识别,
* 现阶段人脸识别均为厂家在系统中自己定制实现。
* @return
*/
fun supportFace(): Boolean {
return false
}
fun addFingerResultListener(onFingerResult: OnFingerResultListener) {
mOnFingerResultListener = onFingerResult
}
/**
* 安卓9.0及以上的指纹识别
*/
@RequiresApi(Build.VERSION_CODES.P)
fun startBiometricPromptIn28() {
val mBiometricPrompt = BiometricPrompt.Builder(mContext)
.setTitle("指纹验证")
.setDescription("扫描指纹,登录系统")
.setNegativeButton("取消", mContext.getMainExecutor(), DialogInterface.OnClickListener { dialogInterface, I ->
// ToastUtil.showToast(mContext, "Cancel")
})
.build()
val mCancellationSignal = CancellationSignal()
mCancellationSignal.setOnCancelListener {
ToastUtil.showToast(mContext, "cancel")
}
val mAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
ToastUtil.showToast(mContext, "$errString")
if (::mOnFingerResultListener.isInitialized) {
mOnFingerResultListener.fingerResult(false)
}
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
// ToastUtil.showToast(mContext, "Succeeded")
if (::mOnFingerResultListener.isInitialized) {
mOnFingerResultListener.fingerResult(true)
}
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
ToastUtil.showToast(mContext, "Failed")
if (::mOnFingerResultListener.isInitialized) {
mOnFingerResultListener.fingerResult(false)
}
}
}
mBiometricPrompt.authenticate(mCancellationSignal, mContext.getMainExecutor(), mAuthenticationCallback)
}
/**
* 安卓6.0以上,9.0以下的指纹识别
*/
@RequiresApi(Build.VERSION_CODES.M)
fun startBiometricPromptIn23() {
initKey()
initCipher()
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initKey() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
//秘钥生成器
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val builder = KeyGenParameterSpec.Builder("DEFAULT_KEY_NAME",
KeyProperties.PURPOSE_ENCRYPT or
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(false)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
keyGenerator.init(builder.build())
keyGenerator.generateKey()
} catch (e: java.lang.Exception) {
throw java.lang.RuntimeException(e)
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun initCipher() {
try {
val key = keyStore.getKey("DEFAULT_KEY_NAME", null)
val cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7)
cipher.init(Cipher.ENCRYPT_MODE, key)
val fragment23 = FingerprintDialogFragment()
fragment23.setCipher(cipher)
fragment23.show((mContext as AppCompatActivity).supportFragmentManager, "fingerprint")
fragment23.setOnFingerResultListener { result ->
mOnFingerResultListener.fingerResult(result)
}
} catch (e: Exception) {
throw RuntimeException(e)
}
}
interface OnFingerResultListener {
fun fingerResult(result: Boolean)
}
}
FingerprintDialogFragment
这是9.0以下设备所使用的自定义Dialog类,其中包含了通过工具类穿来的秘钥启动指纹识别监听和指纹识别监听回调实现的功能。使用观察者模式设置监听器,将识别数据传递给BiometricPromptUtil:
@RequiresApi(Build.VERSION_CODES.M)
public class FingerprintDialogFragment extends DialogFragment {
private FingerprintManager fingerprintManager;
private CancellationSignal mCancellationSignal;
private Cipher mCipher;
private Activity mActivity;
private TextView errorMsg;
private OnFingerResultListener mOnFingerResultListener;
private boolean isSelfCancelled; // 标识是否是用户主动取消的认证。
public void setCipher(Cipher cipher) {
mCipher = cipher;
}
public void setOnFingerResultListener(OnFingerResultListener onFingerResultListener) {
mOnFingerResultListener = onFingerResultListener;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mActivity = getActivity();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fingerprintManager = getContext().getSystemService(FingerprintManager.class);
setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.dialog_fingerprint, container, false);
errorMsg = v.findViewById(R.id.error_msg);
TextView cancel = v.findViewById(R.id.cancel);
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
stopListening();
}
});
return v;
}
@Override
public void onResume() {
super.onResume();
// 开始指纹认证监听
startListening(mCipher);
}
@Override
public void onPause() {
super.onPause();
// 停止指纹认证监听
stopListening();
}
private void startListening(Cipher cipher) {
isSelfCancelled = false;
mCancellationSignal = new CancellationSignal();
fingerprintManager.authenticate(new FingerprintManager.CryptoObject(cipher), mCancellationSignal, 0, new FingerprintManager.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
if (!isSelfCancelled) {
errorMsg.setText(errString);
if (errorCode == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
//Toast.makeText(mActivity, errString, Toast.LENGTH_SHORT).show();
ToastUtil.showToast(mActivity, errString + "");
if (mOnFingerResultListener != null) {
mOnFingerResultListener.fingerResult(false);
}
dismiss();
}
}
}
@Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
errorMsg.setText(helpString);
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
// ToastUtil.showToast(mActivity, "指纹认证成功");
if (mOnFingerResultListener != null) {
mOnFingerResultListener.fingerResult(true);
}
dismiss();
//mActivity.onAuthenticated();
}
@Override
public void onAuthenticationFailed() {
errorMsg.setText("指纹认证失败,请再试一次");
if (mOnFingerResultListener != null) {
mOnFingerResultListener.fingerResult(false);
}
}
}, null);
}
private void stopListening() {
if (mCancellationSignal != null) {
mCancellationSignal.cancel();
mCancellationSignal = null;
isSelfCancelled = true;
}
}
public interface OnFingerResultListener {
void fingerResult(Boolean result);
}
}
dialog_fingerprint.xml
这是与FingerprintDialogFragment相应的布局文件,其中ic_fingerprint是指纹的一个图标,请自行添加:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@mipmap/ic_fingerprint" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:text="请验证指纹解锁"
android:textColor="#000"
android:textSize="16sp" />
<TextView
android:id="@+id/error_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="5dp"
android:maxLines="1"
android:textColor="#f45"
android:textSize="12sp" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="10dp"
android:background="#ccc" />
<TextView
android:id="@+id/cancel"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:text="取消"
android:textColor="#5d7883"
android:textSize="16sp" />
</LinearLayout>
如何使用
以上三个文件均复制到项目中后,在需要开启指纹识别的Activity或Fragment中使用如下代码:
/**
* 启动指纹识别
*/
private fun startFinger() {
val biometricPromptUtil = BiometricPromptUtil(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
biometricPromptUtil.startBiometricPromptIn28()
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
biometricPromptUtil.startBiometricPromptIn23()
}
}
最后
本着面向百度开发的精神就直接贴出了源码,复制到项目中即可食用,如果稍有遗漏请多指正,谢谢。
本文引用了关于安卓6.0~9.0指纹识别的代码进行参考
https://blog.csdn.net/qq_34983989/article/details/82798043