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;
}
注意: 在API 28以下使用 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_WEAKBIOMETRIC_STRONG。如果不指定,某些设备会直接抛异常。我曾经因为这个被用户骂惨了。
  • 坑四:后台Activity被销毁。 如果认证过程中Activity被重建(比如屏幕旋转),BiometricPrompt 实例会失效。解决方案是在 onSaveInstanceState 中保存状态,并在 onCreate 中恢复。

知识体系结构图

下面这张图,帮你理清整个生物识别的调用流程和兼容策略:

Android生物识别知识体系 App发起认证 判断API版本 (Build.VERSION.SDK_INT) API 28+ BiometricPrompt API 23-27 FingerprintManager 检查硬件 & 已录入 检查硬件 & 已录入 认证成功 / 失败 / 错误

完整示例:带兼容性的封装类

下面是我在实际项目中用的一个封装类。它自动处理了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);
    }
}
小技巧:BiometricPromptsetAllowedAuthenticators() 中,如果你希望同时支持指纹和人脸,可以用 BIOMETRIC_WEAK。如果要求更高安全性(比如支付场景),用 BIOMETRIC_STRONG。但要注意,BIOMETRIC_STRONG 要求设备必须有硬件级别的安全模块(如TEE),不是所有设备都支持。

关于UI和用户体验

说实话,BiometricPrompt 自带的对话框已经够用了。但如果你想要自定义UI,比如在App内部显示一个指纹图标动画,那就得自己实现。

我的建议是:不要轻易自定义。因为系统对话框是经过各家厂商适配的,兼容性最好。你自定义的UI,可能在华为手机上没问题,到了小米上就显示不全。

如果你非要自定义,记得在 PromptInfo 中设置 setConfirmationRequired(false),这样认证成功后不会弹出系统确认对话框。但这样做会降低安全性,因为用户可能误触。

嗯,关于生物识别,核心内容就这些。记住一句话:能用系统的,就别自己造轮子。尤其是这种涉及安全和硬件兼容性的功能,老老实实用官方API,比什么都强。


公众号:蓝海资料掘金营,微信deep3321