在指纹识别手机没有大规模普及之前,许多app提供了使用手势密码进行快速登录或解锁的功能。但是在指纹识别大规模普及的今天,安卓的许多app还是仅支持收拾密码登陆功能,一方面是出于安全性的考量,另一方面是出于对适配兼容的担忧。一但用户进行了Root的操作,那么指纹识别的结果将变得不可信。
最近在寻找市面上常用的密码本软件的时候,发现大部分指纹密码软件支持通过生物识别解锁app。也许是指纹识别的兼容支持变好了?查阅资料发现,安卓对指纹识别的功能进行过封装,使用FingerprintManagerCompat
可以轻松对指纹识别进行对接操作。身边8台不同品牌和版本的测试机,都可以正常的使用指纹验证功能。
但是由于Android统一封装指纹功能是在Android6.0后才加入的,这里也只对6.0以上的版本进行指纹识别功能适配。
那么来看一下指纹识别该如何去实现和接入吧。这里为了方便接入,讲指纹识别的全部功能,封装到了一个Dialog中,当打开次dialog时,就可以进行指纹识别了。
首先在AndroidManifest.xml
中添加权限。Android在9.0时,开始不推荐使用FingerprintManager
,开放了新的API,BiometricPrompt
。
但是我们仍可以使用FingerprintManagerCompat
对指纹识别进行接入。
1 2
| <uses-permission android:name="android.permission.USE_FINGERPRINT"/> <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
接下来编写一个简单的指纹验证Dialog布局,代码如下:
1 2 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以下,就直接返回不支持。
1 2 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。
最后将参数传入方法,就可以实现指纹识别功能了。全部代码如下:
1 2 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; } }
|