62、指纹识别与生物识别:BiometricPrompt、指纹验证、兼容性处理
生物识别,说白了就是让手机认人。
指纹、人脸、虹膜,都属于这个范畴。在Android上,官方推荐的方式就是 BiometricPrompt。我最早做指纹识别那会儿,还是用 FingerprintManager,那叫一个痛苦。各家厂商的适配,简直能让人崩溃。后来Google推出了 BiometricPrompt,总算把这事儿统一了。
为什么不用老的FingerprintManager?
嗯,这里要讲清楚。
FingerprintManager 是Android 6.0推出的。它只支持指纹。但现在的手机,有人脸、有虹膜,甚至还有屏下指纹。你想想看,如果每个厂商都自己搞一套API,开发者得疯掉。
BiometricPrompt 是Android 9.0(API 28)引入的。它把指纹、人脸、虹膜统一成了一个接口。你只需要调用它,系统会自动选择当前设备支持的、且用户已录入的生物识别方式。
我在项目中遇到过一个问题:某款手机支持人脸识别,但用 FingerprintManager 死活调不起来。换成 BiometricPrompt 后,一切正常。所以,我的建议是:新项目直接上 BiometricPrompt,别犹豫。
核心API:BiometricPrompt
使用起来其实很简单。核心就三个东西:
- BiometricPrompt:主类,负责发起认证。
- BiometricPrompt.PromptInfo:配置对话框的标题、描述、取消按钮文字等。
- BiometricPrompt.AuthenticationCallback:认证结果回调。
来看一段最基础的代码:
// 1. 创建PromptInfo
PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("验证指纹")
.setSubtitle("请按下指纹")
.setDescription("使用已录入的指纹进行验证")
.setNegativeButtonText("取消")
.build();
// 2. 创建BiometricPrompt实例
BiometricPrompt biometricPrompt = new BiometricPrompt(
this, // Activity或Fragment
Executors.newSingleThreadExecutor(),
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(
@NonNull BiometricPrompt.AuthenticationResult result) {
// 认证成功
runOnUiThread(() -> Toast.makeText(MainActivity.this,
"认证成功", Toast.LENGTH_SHORT).show());
}
@Override
public void onAuthenticationFailed() {
// 认证失败(指纹不匹配)
runOnUiThread(() -> Toast.makeText(MainActivity.this,
"指纹不匹配", Toast.LENGTH_SHORT).show());
}
@Override
public void onAuthenticationError(int errorCode,
@NonNull CharSequence errString) {
// 发生错误(如取消、重试次数过多等)
runOnUiThread(() -> Toast.makeText(MainActivity.this,
"错误: " + errString, Toast.LENGTH_SHORT).show());
}
});
// 3. 开始认证
biometricPrompt.authenticate(promptInfo);
你看,就这么几行。但实际项目中,坑都在细节里。
兼容性处理:Android 6.0到13
为什么会需要兼容性处理?因为 BiometricPrompt 是API 28才有的。如果你的App还要支持Android 6.0到8.1(API 23-27),就得做降级方案。
我个人的习惯是:
- API 28+:直接用
BiometricPrompt。 - API 23-27:回退到
FingerprintManager。 - API 22以下:不支持生物识别,直接提示用户升级系统或使用密码。
这里有一个关键点:判断设备是否支持生物识别。不能直接调API,否则会崩溃。
判断代码如下:
public boolean isBiometricSupported(Context context) {
PackageManager pm = context.getPackageManager();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// API 23+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// API 28+ 使用BiometricManager
BiometricManager manager = BiometricManager.from(context);
int result = manager.canAuthenticate(
BiometricManager.Authenticators.BIOMETRIC_WEAK);
return result == BiometricManager.BIOMETRIC_SUCCESS;
} else {
// API 23-27 使用FingerprintManager
FingerprintManager fpm = (FingerprintManager)
context.getSystemService(Context.FINGERPRINT_SERVICE);
return fpm != null && fpm.isHardwareDetected()
&& fpm.hasEnrolledFingerprints();
}
}
return false;
}
FingerprintManager 时,记得在AndroidManifest.xml中添加权限:<uses-permission android:name="android.permission.USE_FINGERPRINT" />。API 28+则不需要这个权限,改用 USE_BIOMETRIC。
避坑指南:我曾经踩过的雷
做生物识别,有几个坑是绕不开的。我一个个说。
- 坑一:模拟器上无法测试。 大部分模拟器不支持指纹硬件。你可以在模拟器上通过adb命令模拟指纹事件:
adb -e emu finger touch 1。但说实话,效果有限。我建议还是用真机测。 - 坑二:用户未录入生物信息。 如果用户没录指纹或人脸,直接调
authenticate()会返回错误。所以一定要先检查canAuthenticate()的结果。如果返回BIOMETRIC_ERROR_NONE_ENROLLED,就引导用户去设置里录入。 - 坑三:Android 11的认证器类型。 从API 30开始,
BiometricPrompt要求你明确指定认证器类型。比如BIOMETRIC_WEAK或BIOMETRIC_STRONG。如果不指定,某些设备会直接抛异常。我曾经因为这个被用户骂惨了。 - 坑四:后台Activity被销毁。 如果认证过程中Activity被重建(比如屏幕旋转),
BiometricPrompt实例会失效。解决方案是在onSaveInstanceState中保存状态,并在onCreate中恢复。
知识体系结构图
下面这张图,帮你理清整个生物识别的调用流程和兼容策略:
完整示例:带兼容性的封装类
下面是我在实际项目中用的一个封装类。它自动处理了API版本判断和降级逻辑:
public class BiometricHelper {
private Context context;
private BiometricPrompt biometricPrompt;
private FingerprintManager fingerprintManager;
public BiometricHelper(Context context) {
this.context = context;
}
public void authenticate(Activity activity,
BiometricCallback callback) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// API 28+ 使用BiometricPrompt
PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("验证身份")
.setSubtitle("请使用指纹或人脸")
.setNegativeButtonText("取消")
.setAllowedAuthenticators(
BiometricManager.Authenticators.BIOMETRIC_WEAK)
.build();
biometricPrompt = new BiometricPrompt(activity,
Executors.newSingleThreadExecutor(),
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(
BiometricPrompt.AuthenticationResult result) {
callback.onSuccess();
}
@Override
public void onAuthenticationFailed() {
callback.onFailed();
}
@Override
public void onAuthenticationError(int errorCode,
CharSequence errString) {
callback.onError(errorCode, errString.toString());
}
});
biometricPrompt.authenticate(promptInfo);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// API 23-27 使用FingerprintManager
fingerprintManager = (FingerprintManager)
context.getSystemService(Context.FINGERPRINT_SERVICE);
if (fingerprintManager != null &&
fingerprintManager.isHardwareDetected() &&
fingerprintManager.hasEnrolledFingerprints()) {
fingerprintManager.authenticate(null,
new FingerprintManager.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(
FingerprintManager.AuthenticationResult result) {
callback.onSuccess();
}
@Override
public void onAuthenticationFailed() {
callback.onFailed();
}
@Override
public void onAuthenticationError(int errorCode,
CharSequence errString) {
callback.onError(errorCode, errString.toString());
}
}, null);
} else {
callback.onError(-1, "设备不支持或未录入指纹");
}
} else {
callback.onError(-2, "系统版本过低,不支持生物识别");
}
}
public interface BiometricCallback {
void onSuccess();
void onFailed();
void onError(int errorCode, String errorMessage);
}
}
BiometricPrompt 的 setAllowedAuthenticators() 中,如果你希望同时支持指纹和人脸,可以用 BIOMETRIC_WEAK。如果要求更高安全性(比如支付场景),用 BIOMETRIC_STRONG。但要注意,BIOMETRIC_STRONG 要求设备必须有硬件级别的安全模块(如TEE),不是所有设备都支持。
关于UI和用户体验
说实话,BiometricPrompt 自带的对话框已经够用了。但如果你想要自定义UI,比如在App内部显示一个指纹图标动画,那就得自己实现。
我的建议是:不要轻易自定义。因为系统对话框是经过各家厂商适配的,兼容性最好。你自定义的UI,可能在华为手机上没问题,到了小米上就显示不全。
如果你非要自定义,记得在 PromptInfo 中设置 setConfirmationRequired(false),这样认证成功后不会弹出系统确认对话框。但这样做会降低安全性,因为用户可能误触。
嗯,关于生物识别,核心内容就这些。记住一句话:能用系统的,就别自己造轮子。尤其是这种涉及安全和硬件兼容性的功能,老老实实用官方API,比什么都强。
公众号:蓝海资料掘金营,微信deep3321