4. BiometricPrompt基础用法:创建实例、设置回调、启动认证

好,我们直接进入正题。

上一章我们聊了前置准备,权限、Intent、还有那个坑爹的ActivityResultLauncher。现在,假设你已经把那些都搞定了,该有的权限都给了,该弹的弹窗也弹了。接下来,就是真正的核心——BiometricPrompt 本身。

说白了,BiometricPrompt 就是 Android 官方给我们封装好的一个“生物识别对话框”。你不用自己去画指纹动画,也不用自己去调人脸摄像头。你只需要告诉它:“嘿,我要开始验证了,结果你通知我。” 剩下的,系统帮你搞定。

我个人习惯把 BiometricPrompt 的用法拆成三步:创建实例 → 设置回调 → 启动认证。嗯,就这么简单。但每一步里都有细节,不注意就会翻车。

核心流程速览

创建 BiometricPrompt 对象 → 绑定 AuthenticationCallback → 调用 authenticate() 方法。

系统会弹出官方对话框,用户完成人脸/指纹验证后,回调告诉你结果。

① 创建实例 new BiometricPrompt() ② 设置回调 AuthenticationCallback ③ 启动认证 authenticate() 回调结果分支 ✅ onAuthenticationSucceeded() ❌ onAuthenticationError() ⚠️ onAuthenticationFailed() 系统自动处理对话框显示与动画

4.1 创建 BiometricPrompt 实例

先看代码。这是最基础的一个创建方式,我一般写在 Activity 或者 Fragment 的初始化阶段。

// 记得在成员变量区声明
private BiometricPrompt biometricPrompt;
private BiometricPrompt.PromptInfo promptInfo;

// 在 onCreate 或 onViewCreated 中初始化
private void initBiometricPrompt() {
    // 1. 创建 PromptInfo —— 这是对话框的配置
    promptInfo = new BiometricPrompt.PromptInfo.Builder()
            .setTitle("人脸验证")
            .setSubtitle("请面向手机完成验证")
            .setDescription("用于登录您的账户")
            .setNegativeButtonText("取消")
            .build();

    // 2. 创建 BiometricPrompt 实例 —— 需要传入一个回调
    biometricPrompt = new BiometricPrompt(
            this,  // Activity 或 Fragment
            Executors.newSingleThreadExecutor(), // 回调执行的线程
            new BiometricPrompt.AuthenticationCallback() {
                // 回调内容我们下一节细讲
            }
    );
}

这里有个点我想强调一下。第二个参数是 Executor,它决定了你的回调方法在哪个线程执行。我建议用 Executors.newSingleThreadExecutor() 或者主线程的 ContextCompat.getMainExecutor(this)

我的习惯: 如果回调里只是更新 UI,直接用主线程 Executor。如果要做一些耗时操作(比如加密、网络请求),就用后台线程,然后在回调里切到主线程更新 UI。别在回调里直接做耗时操作,会卡对话框。

4.2 设置 AuthenticationCallback

这个回调是 BiometricPrompt 的灵魂。系统验证完人脸或指纹后,结果全在这里告诉你。

我直接贴一个完整的回调实现,然后逐一说每个方法。

new BiometricPrompt.AuthenticationCallback() {
    @Override
    public void onAuthenticationError(int errorCode, CharSequence errString) {
        // 认证出错 —— 比如用户取消、系统不支持、被锁定了
        // errorCode 可以帮你判断具体原因
        Log.e("Biometric", "认证错误: " + errString);
        // 我曾经遇到过 errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON
        // 其实就是用户点了“取消”按钮,别当成真正的错误去弹 Toast
    }

    @Override
    public void onAuthenticationSucceeded(
            BiometricPrompt.AuthenticationResult result) {
        // 认证成功!
        // result 里可以拿到 BiometricType(人脸还是指纹)
        Log.d("Biometric", "认证成功");
        // 这里做你的业务逻辑,比如登录、支付
    }

    @Override
    public void onAuthenticationFailed() {
        // 认证失败 —— 人脸没匹配上,或者指纹不对
        // 注意:这个回调不会关闭对话框,用户可以继续尝试
        Log.w("Biometric", "认证失败,请重试");
        // 我一般在这里更新 UI 提示“请再试一次”
    }
}

嗯,这里要注意几个容易混淆的点。

  • onAuthenticationError:一旦触发,对话框会关闭,认证流程结束。你需要重新调用 authenticate() 才能再次启动。
  • onAuthenticationFailed:对话框不会关闭,用户可以继续尝试。说白了就是“这次没对上,再来一次”。
  • onAuthenticationSucceeded:成功,对话框自动关闭。你可以拿到 AuthenticationResult,里面有个 getBiometricType() 方法,可以判断用户用的是人脸还是指纹。
我曾经踩过的坑:onAuthenticationError 里直接弹 Toast 提示“系统错误”。结果用户点了“取消”按钮,也弹了个“系统错误”,用户一脸懵。后来我才发现,ERROR_NEGATIVE_BUTTON 就是用户主动取消,不应该算错误。所以一定要判断 errorCode。

4.3 启动认证

实例有了,回调有了,最后一步就是启动。代码就一行:

// 启动人脸认证
biometricPrompt.authenticate(promptInfo);

就这么简单?对,就这么简单。系统会立刻弹出官方的人脸验证对话框,开始扫描用户的脸。

但这里有一个非常重要的前提:你必须先检查设备是否支持人脸识别,以及用户是否已经录入人脸。否则你直接调用 authenticate(),系统会直接回调 onAuthenticationError,告诉你“没有可用的生物识别方式”。

我建议在调用 authenticate() 之前,先做一次检查:

BiometricManager biometricManager = BiometricManager.from(this);
int canAuthenticate = biometricManager.canAuthenticate(
        BiometricManager.Authenticators.BIOMETRIC_STRONG
);

if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) {
    // 设备支持,且已录入生物信息
    biometricPrompt.authenticate(promptInfo);
} else {
    // 处理不支持或未录入的情况
    // 比如跳转到系统设置页面
}

这个检查逻辑,我在项目里是放在一个工具类里的。每次调用认证前都跑一遍,避免无意义的弹窗。

4.4 完整代码示例

把上面几段拼起来,就是一个完整的 BiometricPrompt 基础用法。我贴一个我常用的模板:

public class FaceAuthActivity extends AppCompatActivity {

    private BiometricPrompt biometricPrompt;
    private BiometricPrompt.PromptInfo promptInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_face_auth);

        // 1. 配置对话框
        promptInfo = new BiometricPrompt.PromptInfo.Builder()
                .setTitle("人脸验证")
                .setSubtitle("请面向手机")
                .setDescription("用于登录")
                .setNegativeButtonText("取消")
                .build();

        // 2. 创建实例
        biometricPrompt = new BiometricPrompt(this,
                ContextCompat.getMainExecutor(this),
                new BiometricPrompt.AuthenticationCallback() {
                    @Override
                    public void onAuthenticationSucceeded(
                            BiometricPrompt.AuthenticationResult result) {
                        // 成功:跳转主页
                        startActivity(new Intent(FaceAuthActivity.this, MainActivity.class));
                        finish();
                    }

                    @Override
                    public void onAuthenticationError(int errorCode, CharSequence errString) {
                        // 错误:根据 errorCode 做不同处理
                        if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
                            // 用户取消,什么都不做
                        } else {
                            Toast.makeText(FaceAuthActivity.this, "错误: " + errString, Toast.LENGTH_SHORT).show();
                        }
                    }

                    @Override
                    public void onAuthenticationFailed() {
                        // 失败:提示用户重试
                        Toast.makeText(FaceAuthActivity.this, "人脸不匹配,请重试", Toast.LENGTH_SHORT).show();
                    }
                });

        // 3. 检查并启动
        findViewById(R.id.btn_start_auth).setOnClickListener(v -> {
            BiometricManager manager = BiometricManager.from(this);
            if (manager.canAuthenticate(
                    BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS) {
                biometricPrompt.authenticate(promptInfo);
            } else {
                Toast.makeText(this, "设备不支持或未录入人脸", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

4.5 几个容易忽略的细节

要点 说明
对话框不可自定义 UI BiometricPrompt 的对话框是系统级的,你不能改它的布局。想换样式?没门。但好处是统一、安全。
回调线程 第二个参数 Executor 决定了回调线程。主线程更新 UI 方便,但别做耗时操作。
取消按钮 必须设置 setNegativeButtonText(),否则会抛异常。这是强制要求。
多次调用 如果用户连续点击“启动认证”,不要重复调用 authenticate()。我一般加一个布尔锁,防止重复弹窗。
一个小技巧: 如果你希望用户验证失败后能自动重试,可以在 onAuthenticationFailed() 里延迟 1 秒再次调用 authenticate()。但别无限循环,我一般限制最多重试 3 次。

好了,BiometricPrompt 的基础用法就这些。创建实例、设置回调、启动认证,三步走。你想想看,是不是比自己去调摄像头简单多了?

下一章我们会深入 PromptInfo 的配置细节,比如怎么隐藏“取消”按钮、怎么支持设备凭据(PIN/图案)作为备选。嗯,那些才是真正让用户体验变好的地方。


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