Android指纹识别,提升APP用户体验,从这里开始
本文由 左海龙 授权投稿
原文链接:https://blog.csdn.net/hailong0529/article/details/95406183
写在前面
指纹识别大家都不陌生,现在比较新的安卓手机大多都已经支持面部识别了,指纹识别更是主流安卓手机的标配功能。这两个功能可以说用过都说好,确实是方便快捷。
不过大家观察一下会发现,这些手机的指纹识别和面部识别也就是支持一下手机的锁屏解锁而已,数量巨大的APP对于这两个技术的应用可以说比较少。这何尝不是APP良好体验性的损失呢?
庆幸的是,基于 Google API 实现指纹识别的基础性功能并不复杂。
指纹识别的兼容性和安全性问题
除了实现指纹识别的基础性功能,我认为开发者还需要关注和选择性处理的问题有两个:兼容性和安全性。
为什么说选择性处理?
首先说兼容性,指纹识别的 API 是 Google 在 Android 6.0 开放出来的。
在 Android 6.0 以下的系统上,某些手机厂商自行支持了指纹识别,如果我们的 APP 要兼容这些设备,就还要集成厂商的指纹识别的SDK,这是最大的兼容性问题。不过,现在 Android 6.0 以下的设备已经很少了,其中支持指纹识别的设备就更少了,不对其进行兼容,我认为也是可以的。
在Android 6.0 以上的系统上,由于厂商对 Android 系统和指纹识别模块的定制化普遍,导致会出现一些兼容性问题。这个没有什么好的办法,就需要开发者见招拆招了。已经踩过坑的开发者很多,大家可以到网上搜索相关的文章看。
然后说下安全性,由于已添加的指纹是存储在手机上的,Google API 验证指纹后仅仅返回 true 或者 false,我们是很难无条件相信这个识别结果的。比如说用户的手机 root 了或者是自定制设备,指纹识别是有可能被劫持进而返回有误的识别结果的。
好在这种情况发生的概率比较低。如果指纹识别的应用场景非交易非支付,仅仅是类似于 “启动 APP 进行指纹验证” 这样的情况的话,Google API 提供的指纹识别就够用了。
关于兼容性和安全性的问题,本文不过多探讨了,给大家推荐一篇文章,同时也请大家关注文章中提到的支付宝和腾讯的处理方式,及其开源情况:
如何在复杂业务场景中优雅实现Android指纹验证?
指纹识别 API 的版本演进
在 Android 6.0(Android M Api23),Android 系统开放了指纹识别的api,存在于 android.hardware.fingerprint包下,核心类是FingerprintManager,提供了基础的指纹识别的功能。要注意的是,FingerprintManager在 Android 9.0(Android P Api28)做了 @Deprecated 标记,将被弃用。
后来,在android.support.v4.hardware.fingerprint包和 androidx.core.hardware.fingerprint包中,FingerprintManager升级为了 FingerprintManagerCompat,对功能进行了增强,也做了一些兼容性的处理,比如增加了系统版本号的判断,对指纹支持加密处理等。实际上阅读源码会发现,他的核心功能还是调用 FingerprintManager
实现的。
再之后,在 Android 9.0(Android P Api 28),Google 对生物识别进行了进一步增强,开放了以 BiometricPrompt
为核心的新 Api,存在于 androidx.biometric 包和android.hardware.biometrics包下,Google 在开发者文档中是这样解释的:
On devices running P and above, this will show a system-provided authentication prompt, using a device's supported biometric (fingerprint, iris, face, etc).
大意是,在 Android P 及以上版本的系统中,BiometricPrompt
将展现一个由系统提供的验证提示,用于支持设备提供的生物识别,包括指纹、虹膜、面部等。
目前来看,虹膜和面部等生物识别 Api 尚未开放,仅支持指纹识别,不过在指纹识别上进行了统一,比如要求使用统一的指纹识别 UI ,不允许开发者自定义了。
指纹识别关键方法 authenticate
这是指纹识别中最核心的方法,用于拉起指纹识别扫描器进行指纹识别。
以 FingerprintManagerCompat
中 authenticate() 方法为例,开发者文档中是这样定义的:
解释一下各个参数:
/** * A wrapper class for the crypto objects supported by FingerprintManager. Currently the * framework supports {@link Signature} and {@link Cipher} objects. */
大意是,这是一个密码对象的包装类,当前支持 Signature 形式和 Cipher 形式的密码对象加密。
作用是,指纹扫描器会使用这个对象判断指纹认证结果的合法性。Android 6.0 是 @Nullable
,但不建议传 null,且在 Android 9.0 之后就是 @NonNull
了。
我的代码中提供了一个 Cipher 的帮助类,可用来创建一个 Cipher 对象,可参考使用。
指纹识别的实践
在指纹识别功能的实践中,我将其做成了开源库发布在了 Github 上,可通过 gradle 进行依赖,使用方法比较简单,两三行代码,再传入一个验证结果监听即可。
下面对部分实现过程做一下介绍,详细的 Api 及源码请移步 Github。
Github 地址:https://github.com/ZuoHailong/BiometricPrompt
示例
Android 6.0 指纹识别框,开发者自定义:
Android 9.0 指纹识别框,系统提供:
其中指纹 icon 和取消按钮的文字颜色,由属性 colorPrimary 的颜色值决定。
指纹识别管理类
FingerprintVerifyManager
是指纹识别库的入口,对指纹识别进行管理,通过 FingerprintVerifyManager.Builder
对指纹识别进行初始化。
在这个类中,有根据手机系统版本调用不同的指纹识别 Api(FingerprintManagerCompat
或者 BiometricPrompt
),其中 BiometricPrompt
支持开启或者关闭,默认关闭。
public FingerprintVerifyManager(Builder builder) { IFingerprint fingerprint; // >= Android P if (AndrVersionUtil.isAboveAndrP()) { //在 Android P 上是否展示系统提供的识别框 if (builder.enableAndroidP) fingerprint = FingerprintAndrP.newInstance(); else fingerprint = FingerprintAndrM.newInstance(); } else if (AndrVersionUtil.isAboveAndrM()) {// Android 6.0 =< Version fingerprint = FingerprintAndrM.newInstance(); } else {// < Android 6.0 ,官方未开放指纹识别,某些机型自行支持的情况暂不做处理 builder.callback.onError(builder.context.getString(R.string.biometricprompt_verify_error_below_m)); return; } …… fingerprint.authenticate(builder.context, bean, builder.callback); } IFingerprint fingerprint; // >= Android P if (AndrVersionUtil.isAboveAndrP()) { //在 Android P 上是否展示系统提供的识别框 if (builder.enableAndroidP) fingerprint = FingerprintAndrP.newInstance(); else fingerprint = FingerprintAndrM.newInstance(); } else if (AndrVersionUtil.isAboveAndrM()) {// Android 6.0 =< Version fingerprint = FingerprintAndrM.newInstance(); } else {// < Android 6.0 ,官方未开放指纹识别,某些机型自行支持的情况暂不做处理 builder.callback.onError(builder.context.getString(R.string.biometricprompt_verify_error_below_m)); return; }
……
fingerprint.authenticate(builder.context, bean, builder.callback); }
其中 IFingerprint
是指纹识别的接口,兼容 Android 6.0 的 FingerprintAndrM
和兼容 Android 9.0 的 FingerprintAndrP
都实现了此接口。
public interface IFingerprint { /** * 初始化并调起指纹识别 * * @param context * @param verificationDialogStyleBean 指纹识别框样式 * @param callback 通知开发者指纹识别结果 */ void authenticate(Activity context, VerificationDialogStyleBean verificationDialogStyleBean, FingerprintCallback callback);}interface IFingerprint {
/** * 初始化并调起指纹识别 * * @param context * @param verificationDialogStyleBean 指纹识别框样式 * @param callback 通知开发者指纹识别结果 */ void authenticate(Activity context, VerificationDialogStyleBean verificationDialogStyleBean, FingerprintCallback callback);
}
基于Android 6.0 实现指纹识别
上文有提及,FingerprintAndrM
是基于 Android 6.0 的具体的指纹识别实现类:
@RequiresApi(api = Build.VERSION_CODES.M)public class FingerprintAndrM implements IFingerprint { private final String TAG = FingerprintAndrM.class.getName(); private Activity context; private static FingerprintAndrM fingerprintAndrM; //指纹验证框 private static FingerprintDialog fingerprintDialog; //指向调用者的指纹回调 private FingerprintCallback fingerprintCallback; //用于取消扫描器的扫描动作 private CancellationSignal cancellationSignal; //指纹加密 private static FingerprintManagerCompat.CryptoObject cryptoObject; //Android 6.0 指纹管理 private FingerprintManagerCompat fingerprintManagerCompat; @Override public void authenticate(Activity context, VerificationDialogStyleBean bean, FingerprintCallback callback) { //判断指纹识别是否可用 if (!canAuthenticate(context, callback)) return; this.context = context; this.fingerprintCallback = callback; //Android 6.0 指纹管理 实例化 fingerprintManagerCompat = FingerprintManagerCompat.from(context); //取消扫描,每次取消后需要重新创建新示例 cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener(() -> fingerprintDialog.dismiss()); //调起指纹验证 fingerprintManagerCompat.authenticate(cryptoObject, 0, cancellationSignal, authenticationCallback, null); //指纹验证框 fingerprintDialog = FingerprintDialog.newInstance(context).setActionListener(dialogActionListener).setDialogStyle(bean); fingerprintDialog.show(context.getFragmentManager(), TAG); } public static FingerprintAndrM newInstance() { if (fingerprintAndrM == null) { synchronized (FingerprintAndrM.class) { if (fingerprintAndrM == null) { fingerprintAndrM = new FingerprintAndrM(); } } } //指纹加密,提前进行Cipher初始化,防止指纹认证时还没有初始化完成 try { cryptoObject = new FingerprintManagerCompat.CryptoObject(new CipherHelper().createCipher()); } catch (Exception e) { e.printStackTrace(); } return fingerprintAndrM; } /** * 指纹验证框按键监听 */ private FingerprintDialog.OnDialogActionListener dialogActionListener = new FingerprintDialog.OnDialogActionListener() { @Override public void onUsepwd() { if (fingerprintCallback != null) fingerprintCallback.onUsepwd(); } @Override public void onCancle() {//取消指纹验证,通知调用者 if (fingerprintCallback != null) fingerprintCallback.onCancel(); } @Override public void onDismiss() {//验证框消失,取消指纹验证 if (cancellationSignal != null && !cancellationSignal.isCanceled()) cancellationSignal.cancel(); } }; /** * 指纹验证结果回调 */ private FingerprintManagerCompat.AuthenticationCallback authenticationCallback = new FingerprintManagerCompat.AuthenticationCallback() { @Override public void onAuthenticationError(int errMsgId, CharSequence errString) { super.onAuthenticationError(errMsgId, errString); fingerprintDialog.setTip(errString.toString(), R.color.biometricprompt_color_FF5555); } @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { super.onAuthenticationHelp(helpMsgId, helpString); fingerprintDialog.setTip(helpString.toString(), R.color.biometricprompt_color_FF5555); } @Override public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) { super.onAuthenticationSucceeded(result); fingerprintDialog.setTip(context.getString(R.string.biometricprompt_verify_success), R.color.biometricprompt_color_82C785); fingerprintCallback.onSucceeded(); fingerprintDialog.dismiss(); } @Override public void onAuthenticationFailed() { super.onAuthenticationFailed(); fingerprintDialog.setTip(context.getString(R.string.biometricprompt_verify_failed), R.color.biometricprompt_color_FF5555); fingerprintCallback.onFailed(); } }; /* * 在 Android Q,Google 提供了 Api BiometricManager.canAuthenticate() 用来检测指纹识别硬件是否可用及是否添加指纹 * 不过尚未开放,标记为"Stub"(存根) * 所以暂时还是需要使用 Andorid 6.0 的 Api 进行判断 * */ private boolean canAuthenticate(Context context, FingerprintCallback fingerprintCallback) { /* * 硬件是否支持指纹识别 * */ if (!FingerprintManagerCompat.from(context).isHardwareDetected()) { fingerprintCallback.onError(FingerprintManager.FINGERPRINT_ERROR_HW_NOT_PRESENT, context.getString(R.string.biometricprompt_verify_error_no_hardware)); return false; } //是否已添加指纹 if (!FingerprintManagerCompat.from(context).hasEnrolledFingerprints()) { fingerprintCallback.onNoneEnrolled(); return false; } return true; }}public class FingerprintAndrM implements IFingerprint {
private final String TAG = FingerprintAndrM.class.getName(); private Activity context;
private static FingerprintAndrM fingerprintAndrM; //指纹验证框 private static FingerprintDialog fingerprintDialog; //指向调用者的指纹回调 private FingerprintCallback fingerprintCallback;
//用于取消扫描器的扫描动作 private CancellationSignal cancellationSignal; //指纹加密 private static FingerprintManagerCompat.CryptoObject cryptoObject; //Android 6.0 指纹管理 private FingerprintManagerCompat fingerprintManagerCompat;
@Override public void authenticate(Activity context, VerificationDialogStyleBean bean, FingerprintCallback callback) {
//判断指纹识别是否可用 if (!canAuthenticate(context, callback)) return;
this.context = context; this.fingerprintCallback = callback; //Android 6.0 指纹管理 实例化 fingerprintManagerCompat = FingerprintManagerCompat.from(context);
//取消扫描,每次取消后需要重新创建新示例 cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener(() -> fingerprintDialog.dismiss());
//调起指纹验证 fingerprintManagerCompat.authenticate(cryptoObject, 0, cancellationSignal, authenticationCallback, null); //指纹验证框 fingerprintDialog = FingerprintDialog.newInstance(context).setActionListener(dialogActionListener).setDialogStyle(bean); fingerprintDialog.show(context.getFragmentManager(), TAG); }
public static FingerprintAndrM newInstance() { if (fingerprintAndrM == null) { synchronized (FingerprintAndrM.class) { if (fingerprintAndrM == null) { fingerprintAndrM = new FingerprintAndrM(); } } } //指纹加密,提前进行Cipher初始化,防止指纹认证时还没有初始化完成 try { cryptoObject = new FingerprintManagerCompat.CryptoObject(new CipherHelper().createCipher()); } catch (Exception e) { e.printStackTrace(); } return fingerprintAndrM; }
/** * 指纹验证框按键监听 */ private FingerprintDialog.OnDialogActionListener dialogActionListener = new FingerprintDialog.OnDialogActionListener() { @Override public void onUsepwd() { if (fingerprintCallback != null) fingerprintCallback.onUsepwd(); }
@Override public void onCancle() {//取消指纹验证,通知调用者 if (fingerprintCallback != null) fingerprintCallback.onCancel(); }
@Override public void onDismiss() {//验证框消失,取消指纹验证 if (cancellationSignal != null && !cancellationSignal.isCanceled()) cancellationSignal.cancel(); } };
/** * 指纹验证结果回调 */ private FingerprintManagerCompat.AuthenticationCallback authenticationCallback = new FingerprintManagerCompat.AuthenticationCallback() { @Override public void onAuthenticationError(int errMsgId, CharSequence errString) { super.onAuthenticationError(errMsgId, errString); fingerprintDialog.setTip(errString.toString(), R.color.biometricprompt_color_FF5555); }
@Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { super.onAuthenticationHelp(helpMsgId, helpString); fingerprintDialog.setTip(helpString.toString(), R.color.biometricprompt_color_FF5555); }
@Override public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) { super.onAuthenticationSucceeded(result); fingerprintDialog.setTip(context.getString(R.string.biometricprompt_verify_success), R.color.biometricprompt_color_82C785); fingerprintCallback.onSucceeded(); fingerprintDialog.dismiss(); }
@Override public void onAuthenticationFailed() { super.onAuthenticationFailed(); fingerprintDialog.setTip(context.getString(R.string.biometricprompt_verify_failed), R.color.biometricprompt_color_FF5555); fingerprintCallback.onFailed(); } };
/* * 在 Android Q,Google 提供了 Api BiometricManager.canAuthenticate() 用来检测指纹识别硬件是否可用及是否添加指纹 * 不过尚未开放,标记为"Stub"(存根) * 所以暂时还是需要使用 Andorid 6.0 的 Api 进行判断 * */ private boolean canAuthenticate(Context context, FingerprintCallback fingerprintCallback) { /* * 硬件是否支持指纹识别 * */ if (!FingerprintManagerCompat.from(context).isHardwareDetected()) { fingerprintCallback.onError(FingerprintManager.FINGERPRINT_ERROR_HW_NOT_PRESENT, context.getString(R.string.biometricprompt_verify_error_no_hardware)); return false; } //是否已添加指纹 if (!FingerprintManagerCompat.from(context).hasEnrolledFingerprints()) { fingerprintCallback.onNoneEnrolled(); return false; } return true; }
}
这里面要重点关注 CancellationSignal
与指纹识别框的关联,也就是识别框消失,就一定要取消指纹扫描器的扫描操作,否则在超时时间内,用户将无法再次拉起指纹识别(尽管可以弹出指纹识别框)。
基于Android 9.0 实现指纹识别
上文有提及,FingerprintAndrP
是基于 Android 9.0 的具体的指纹识别实现类:
@RequiresApi(api = Build.VERSION_CODES.P)public class FingerprintAndrP implements IFingerprint { private static FingerprintAndrP fingerprintAndrP; //指向调用者的指纹回调 private FingerprintCallback fingerprintCallback; //用于取消扫描器的扫描动作 private CancellationSignal cancellationSignal; //指纹加密 private static BiometricPrompt.CryptoObject cryptoObject; @Override public void authenticate(Activity context, VerificationDialogStyleBean verificationDialogStyleBean, FingerprintCallback callback) { //判断指纹识别是否可用 if (!canAuthenticate(context, callback)) return; this.fingerprintCallback = callback; /* * 初始化 BiometricPrompt.Builder */ …… //构建 BiometricPrompt BiometricPrompt biometricPrompt = builder.build(); //取消扫描,每次取消后需要重新创建新示例 cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener(() -> { }); /* * 拉起指纹验证模块,等待验证 * Executor: * context.getMainExecutor() */ biometricPrompt.authenticate(cryptoObject, cancellationSignal, context.getMainExecutor(), authenticationCallback); } public static FingerprintAndrP newInstance() { if (fingerprintAndrP == null) { synchronized (FingerprintAndrM.class) { if (fingerprintAndrP == null) { fingerprintAndrP = new FingerprintAndrP(); } } } //指纹加密,提前进行Cipher初始化,防止指纹认证时还没有初始化完成 try { cryptoObject = new BiometricPrompt.CryptoObject(new CipherHelper().createCipher()); } catch (Exception e) { e.printStackTrace(); } return fingerprintAndrP; } /** * 认证结果回调 */ private BiometricPrompt.AuthenticationCallback authenticationCallback = new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationError(int errorCode, CharSequence errString) { super.onAuthenticationError(errorCode, errString); if (fingerprintCallback != null) { if (errorCode == 5) {//用户取消指纹验证,不必向用户抛提示信息 fingerprintCallback.onCancel(); return; } fingerprintCallback.onError(errorCode, errString.toString()); } } @Override public void onAuthenticationHelp(int helpCode, CharSequence helpString) { super.onAuthenticationHelp(helpCode, helpString); if (fingerprintCallback != null) fingerprintCallback.onError(helpCode, helpString.toString()); } @Override public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { super.onAuthenticationSucceeded(result); if (fingerprintCallback != null) fingerprintCallback.onSucceeded(); } @Override public void onAuthenticationFailed() { super.onAuthenticationFailed(); if (fingerprintCallback != null) fingerprintCallback.onFailed(); } }; /* * 在 Android Q,Google 提供了 Api BiometricManager.canAuthenticate() 用来检测指纹识别硬件是否可用及是否添加指纹 * 不过尚未开放,标记为"Stub"(存根) * 所以暂时还是需要使用 Andorid 6.0 的 Api 进行判断 * */ private boolean canAuthenticate(Context context, FingerprintCallback fingerprintCallback) { /* * 硬件是否支持指纹识别 * */ if (!FingerprintManagerCompat.from(context).isHardwareDetected()) { fingerprintCallback.onError(FingerprintManager.FINGERPRINT_ERROR_HW_NOT_PRESENT, context.getString(R.string.biometricprompt_verify_error_no_hardware)); return false; } //是否已添加指纹 if (!FingerprintManagerCompat.from(context).hasEnrolledFingerprints()) { fingerprintCallback.onNoneEnrolled(); return false; } return true; }}public class FingerprintAndrP implements IFingerprint {
private static FingerprintAndrP fingerprintAndrP; //指向调用者的指纹回调 private FingerprintCallback fingerprintCallback;
//用于取消扫描器的扫描动作 private CancellationSignal cancellationSignal; //指纹加密 private static BiometricPrompt.CryptoObject cryptoObject;
@Override public void authenticate(Activity context, VerificationDialogStyleBean verificationDialogStyleBean, FingerprintCallback callback) {
//判断指纹识别是否可用 if (!canAuthenticate(context, callback)) return;
this.fingerprintCallback = callback;
/* * 初始化 BiometricPrompt.Builder */ ……
//构建 BiometricPrompt BiometricPrompt biometricPrompt = builder.build();
//取消扫描,每次取消后需要重新创建新示例 cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener(() -> { });
/* * 拉起指纹验证模块,等待验证 * Executor: * context.getMainExecutor() */ biometricPrompt.authenticate(cryptoObject, cancellationSignal, context.getMainExecutor(), authenticationCallback); }
public static FingerprintAndrP newInstance() { if (fingerprintAndrP == null) { synchronized (FingerprintAndrM.class) { if (fingerprintAndrP == null) { fingerprintAndrP = new FingerprintAndrP(); } } } //指纹加密,提前进行Cipher初始化,防止指纹认证时还没有初始化完成 try { cryptoObject = new BiometricPrompt.CryptoObject(new CipherHelper().createCipher()); } catch (Exception e) { e.printStackTrace(); }
return fingerprintAndrP; }
/** * 认证结果回调 */ private BiometricPrompt.AuthenticationCallback authenticationCallback = new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationError(int errorCode, CharSequence errString) { super.onAuthenticationError(errorCode, errString); if (fingerprintCallback != null) { if (errorCode == 5) {//用户取消指纹验证,不必向用户抛提示信息 fingerprintCallback.onCancel(); return; } fingerprintCallback.onError(errorCode, errString.toString()); } }
@Override public void onAuthenticationHelp(int helpCode, CharSequence helpString) { super.onAuthenticationHelp(helpCode, helpString); if (fingerprintCallback != null) fingerprintCallback.onError(helpCode, helpString.toString()); }
@Override public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { super.onAuthenticationSucceeded(result); if (fingerprintCallback != null) fingerprintCallback.onSucceeded(); }
@Override public void onAuthenticationFailed() { super.onAuthenticationFailed(); if (fingerprintCallback != null) fingerprintCallback.onFailed(); } };
/* * 在 Android Q,Google 提供了 Api BiometricManager.canAuthenticate() 用来检测指纹识别硬件是否可用及是否添加指纹 * 不过尚未开放,标记为"Stub"(存根) * 所以暂时还是需要使用 Andorid 6.0 的 Api 进行判断 * */ private boolean canAuthenticate(Context context, FingerprintCallback fingerprintCallback) {
/* * 硬件是否支持指纹识别 * */ if (!FingerprintManagerCompat.from(context).isHardwareDetected()) { fingerprintCallback.onError(FingerprintManager.FINGERPRINT_ERROR_HW_NOT_PRESENT, context.getString(R.string.biometricprompt_verify_error_no_hardware)); return false; } //是否已添加指纹 if (!FingerprintManagerCompat.from(context).hasEnrolledFingerprints()) { fingerprintCallback.onNoneEnrolled(); return false; } return true; }
}
这里需要开发者关注的有两点:
(1) Android 9.0 不允许开发者自定义指纹识别框,但系统提供的指纹识别框的灵活性堪忧。比如说,目前来看,系统只允许在识别框出现一个按钮,放了 “取消” 就不能放 “密码验证” ,放了 “密码验证” 就不能放 “取消”。(尴尬脸……)
(2) 系统提供的指纹识别框只能在界面底部,不可以上下居中。但在某些手机上(如OPPO reno),指纹传感器也是在界面底部,当拉起指纹识别时,会在指纹传感器的位置显示一个指纹图标,以提示用户在哪下指。然而,系统提供的指纹识别框上也有一个指纹图标,这两个指纹图标就发生了重合或者离的很近。(尴尬脸……)
示例:
鉴于以上问题,指纹开源库提供了一个方法 builder.enableAndroidP(boolean enableAndroidP)
,允许调用者开启或者关闭 Android 9.0 系统提供的指纹识别框。当关闭时,将使用 Android 6.0 的指纹识别 Api,并使用自定义的指纹识别框。
指纹识别库 Github 地址:https://github.com/ZuoHailong/BiometricPrompt
感谢
https://developer.android.google.cn/reference
https://www.jianshu.com/p/1eae12582a31
https://www.jianshu.com/p/ed880f35f97f
推荐阅读
谈谈我最近的一些想法
Android多线程误区
ConstraintLayout,看完一篇就够了吗?
编程·思维·职场
欢迎扫码关注
在看也是一种认可
Android指纹识别,提升APP用户体验,从这里开始相关推荐
- android指纹识别真机,vivo屏下指纹真机试玩:支持湿手解锁,最快月底发布
文:文俊 在去年 12 月,Synaptics(新思国际)正式发布全球首款量产的屏下指纹识别传感器 Clear ID FS9500.就在同一时间,全球首款配备屏下指纹识别功能的智能手机也被外媒曝光,并 ...
- 【转】人脸识别功能的用户体验设计优化
2016-08-09 人脸识别技术是生物特征识别技术的一种,因其相对其它生物特征识别技术的优点而在市场上得到广泛应用.在人脸识别功能优化设计的项目中,主要以"三步走"的流程完成了设 ...
- App用户体验的一点思考
App用户体验的一点思考 最近我在团队中负责TImers4Me这款Android软件的开发.维护和更新,软件每次在市场上的发布都能得到用户一些有价值的反馈,通过收集整理用户们的使用反馈,我们常能看到一 ...
- android指纹识别源码
随着科技的进步,不仅是软件方面,安卓系统在硬件发展上也有了日新月异的发展.2015年下半年出的安卓手机基本上都带指纹识别功能,不管是炒得很热的360奇酷手机.魅族手机,还是"中华酷联&quo ...
- Android指纹识别
Android指纹识别 原文:Android指纹识别 上一篇讲了通过FingerprintManager验证手机是否支持指纹识别,以及是否录入了指纹,这里进行指纹的验证. //获取Fingerprin ...
- 提升App用户活跃度的5个小技巧
一款APP成功的关键在于App用户量和活跃度,打造一款大众喜欢的APP,是APP存活的关键!然而现实却是很多APP没有运营几天,就被市场所淘汰了,那么如何保障APP下载量和用户活跃度呢? 对于APP运 ...
- 易观分析发布:证券类APP用户体验指数评测框架
易观分析:易观分析发布证券类APP用户体验指数评测框架.易观分析长期监测证券类APP市场动向,建立多个APP评测模型,基于用户对证券类APP体验需求,我们将定期发布APP评测分析,定期监测证券类APP ...
- android app报告,知乎APP用户体验报告
一.设备信息 App版本:3.3.0 更新时间:2016年4月26日 手机信息:魅族MX5 Android版本:5.1 系统版本:Flyme OS 5.6.4.19beta 二.产品概述 知乎属于通讯 ...
- 提升汽车APP用户体验,火山引擎APMPlus的“独家秘笈”
随着数字经济时代的到来,以大数据.云计算.人工智能为代表的新技术正在引领各行各业实现数字化转型.对于汽车行业而言,如何帮助企业从容应对发展新趋势,赢得市场机遇,已成为重要课题.在汽车行业数字化转型的进 ...
最新文章
- RabbitMQ 入门系列(6)— 如何保证 RabbitMQ 消息不丢失
- php如何通过变量销毁unset的过程讲解
- 最受欢迎中文机器学习课程,台大李宏毅老师公开课2019版上线!
- linux和android学习,android学习笔记
- 【定时器/中断/PWM】利用一个定时器实现一路PWM波的输出---点亮LED
- java定时数据同步_java实现定时同步数据同步
- stm32—光敏电阻传感器的初步使用
- 熊猫的python小课账号_校长,我要上车——python模拟登录熊猫TV
- 一文将 DCDC 的 Layout 讲的明明白白,收藏这篇就够了
- docker部署案例
- mysql 增加分区_MySql数据分区操作之新增分区操作
- bib config_配置config.bib的注意事项以及错误分析——自己都要顶!
- python tutorial json_Python Tutorial - Parse JSON Objects with Python
- clickhouse连接Tableau
- poj2976 01分数规划
- CLA not signed yet
- CAD保存高版本的dwg(网页版)
- 学习DNS路上之CloudXNS
- Oracle中同义词的研究
- Java6-7章总结复习
热门文章
- android+布局倾斜,Android中的倾斜或倾斜UI设计
- zkCli.sh命令使用
- 洛谷·bzoj·伟大的奶牛聚集Great Cow Gather
- 第 n 小的质数 与 7 无关的数 计算多项式的值 三道水题(深学思维)
- 查看Elasticsearch磁盘使用率
- java实战_java实战练习
- u-boot-1.3.4移植到mini2440+128M nand boot(2)
- adb命令(这篇文章就够了)
- linux var log目录作用,Linux系统/var/log/journal/垃圾日志清理 - 米扑博客
- js 滚动监听,核心方法