在指纹识别手机没有大规模普及之前,许多app提供了使用手势密码进行快速登录或解锁的功能。但是在指纹识别大规模普及的今天,安卓的许多app还是仅支持收拾密码登陆功能,一方面是出于安全性的考量,另一方面是出于对适配兼容的担忧。一但用户进行了Root的操作,那么指纹识别的结果将变得不可信。
最近在寻找市面上常用的密码本软件的时候,发现大部分指纹密码软件支持通过生物识别解锁app。也许是指纹识别的兼容支持变好了?查阅资料发现,安卓对指纹识别的功能进行过封装,使用FingerprintManagerCompat可以轻松对指纹识别进行对接操作。身边8台不同品牌和版本的测试机,都可以正常的使用指纹验证功能。
但是由于Android统一封装指纹功能是在Android6.0后才加入的,这里也只对6.0以上的版本进行指纹识别功能适配。
那么来看一下指纹识别该如何去实现和接入吧。这里为了方便接入,讲指纹识别的全部功能,封装到了一个Dialog中,当打开次dialog时,就可以进行指纹识别了。
首先在AndroidManifest.xml中添加权限。Android在9.0时,开始不推荐使用FingerprintManager,开放了新的API,BiometricPrompt。
但是我们仍可以使用FingerprintManagerCompat对指纹识别进行接入。
| 12
 
 | <uses-permission android:name="android.permission.USE_FINGERPRINT"/><uses-permission android:name="android.permission.USE_BIOMETRIC"/>
 
 | 
接下来编写一个简单的指纹验证Dialog布局,代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | <?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="wrap_content"
 android:background="#FFFFFF"
 android:orientation="vertical">
 
 <ImageView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_gravity="center"
 android:layout_marginTop="20dp"
 android:src="@drawable/fingerprint"/>
 
 <TextView
 android:layout_width="match_parent"
 android:layout_height="50dp"
 android:gravity="center"
 android:textSize="16sp"
 android:textColor="#000000"
 android:text="请验证指纹解锁" />
 
 <View
 android:layout_width="match_parent"
 android:layout_height="1dp"
 android:background="#339CA6B1" />
 
 <TextView
 android:id="@+id/tv_cancel"
 android:layout_width="match_parent"
 android:layout_height="50dp"
 android:gravity="center"
 android:text="取消"
 android:textColor="#177FFF"
 android:textSize="16sp" />
 </LinearLayout>
 
 | 
接下来可以编写Dialog了,全部的代码放在最后,这里只截取关键的部分来说明。
这里继承使用DialogFragment,因为DialogFragment拥有和Fragment一样的生命周期管理能力,便于资源的处理和释放。
首先获取实例对象,并判断设备是否支持指纹识别功能和是否开启了(录入了)指纹。如果系统版本在6.0以下,就直接返回不支持。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | FingerprintManagerCompat mCompat = FingerprintManagerCompat.from(getContext());
 
 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
 return false;
 } else {
 return mCompat != null && mCompat.isHardwareDetected();
 }
 
 
 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
 return false;
 } else {
 return mCompat != null && mCompat.hasEnrolledFingerprints();
 }
 
 | 
之后就可以开始验证了,看一下验证方法authenticate需要的参数:
- crypto: 需要传入一个加密对象,用于对指纹识别结果进行加密,如果传null,表示无条件相信返回的结果。推荐使用加密,避免指纹识别结果被篡改
- flags: 标志位,默认传0即可
- cancel: 用于处理取消指纹识别后的行为
- callback: 提供了指纹识别完毕后的回调方法,包括成功、失败、异常错误
- handler: 默认会使用主线程的Handler,传null即可
其中特殊强调一下第一个参数,crypto的作用是,验证系统返回的指纹识别结果是否被修改过,这里使用了非对称加密,具体的内容可以参考之前的“Android AES加解密”那篇文章,里面对加密相关的类型和参数有详细的讲解。
具体的逻辑也是大同小异,先生成KeyStore,然后通过KeyStore生成Cipher对象,最后通过FingerprintManagerCompat.CryptoObject生成CryptoObject。
最后将参数传入方法,就可以实现指纹识别功能了。全部代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 
 | public class FingerprintDialog extends DialogFragment {
 private static final String DEFAULT_KEY_NAME = "default_key";
 private FingerprintManagerCompat mCompat;
 private CancellationSignal mCancellationSignal;
 private FingerprintManagerCompat.CryptoObject cryptoObject = null;
 private FingerPrintResult mResult;
 
 @Nullable
 @Override
 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
 View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_finger_test, container, false);
 mCompat = FingerprintManagerCompat.from(getContext());
 
 TextView tvCancel = view.findViewById(R.id.tv_cancel);
 tvCancel.setOnClickListener(v -> dismiss());
 
 init();
 return view;
 }
 
 private void init() {
 if (!hasDeviceAllow()) {
 Toast.makeText(getContext(), "暂不支持此设备", Toast.LENGTH_SHORT).show();
 dismiss();
 return;
 }
 if (!hasOpenFingerprints()) {
 Toast.makeText(getContext(), "此设备未设置指纹", Toast.LENGTH_SHORT).show();
 dismiss();
 return;
 }
 initKey();
 authenticate();
 }
 
 public boolean hasDeviceAllow() {
 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
 return false;
 } else {
 return mCompat != null && mCompat.isHardwareDetected();
 }
 }
 
 public boolean hasOpenFingerprints() {
 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
 return false;
 } else {
 return mCompat != null && mCompat.hasEnrolledFingerprints();
 }
 }
 
 private void initKey() {
 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
 return;
 }
 try {
 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
 keyStore.load(null);
 KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
 KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(DEFAULT_KEY_NAME,
 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
 .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
 .setUserAuthenticationRequired(true)
 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
 keyGenerator.init(builder.build());
 keyGenerator.generateKey();
 initCipher(keyStore);
 } catch (Exception e) {
 throw new RuntimeException(e);
 }
 }
 
 @TargetApi(23)
 private void initCipher(KeyStore keyStore) {
 try {
 SecretKey key = (SecretKey) keyStore.getKey(DEFAULT_KEY_NAME, null);
 Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
 + KeyProperties.BLOCK_MODE_CBC + "/"
 + KeyProperties.ENCRYPTION_PADDING_PKCS7);
 cipher.init(Cipher.ENCRYPT_MODE, key);
 cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
 } catch (Exception e) {
 throw new RuntimeException(e);
 }
 }
 
 public void authenticate() {
 mCancellationSignal = new CancellationSignal();
 mCompat.authenticate(cryptoObject, 0, mCancellationSignal, new FingerprintManagerCompat.AuthenticationCallback() {
 @Override
 public void onAuthenticationError(int errMsgId, CharSequence errString) {
 super.onAuthenticationError(errMsgId, errString);
 Toast.makeText(getContext(), "指纹识别出现错误", Toast.LENGTH_SHORT).show();
 dismiss();
 }
 
 @Override
 public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
 super.onAuthenticationSucceeded(result);
 Toast.makeText(getContext(), "指纹识别成功", Toast.LENGTH_SHORT).show();
 
 mResult.onSuccess();
 dismiss();
 }
 
 @Override
 public void onAuthenticationFailed() {
 super.onAuthenticationFailed();
 Toast.makeText(getContext(), "指纹识别失败", Toast.LENGTH_SHORT).show();
 }
 }, null);
 }
 
 public interface FingerPrintResult{
 void onSuccess();
 }
 
 public void setResult(FingerPrintResult mResult) {
 this.mResult = mResult;
 }
 }
 
 |