14、人脸识别与Fragment集成:在Fragment中使用BiometricPrompt、Fragment生命周期管理

说实话,很多开发者第一次接触生物识别时,都是在Activity里直接调用BiometricPrompt。这当然没问题,但现实项目里,我们更多是用Fragment来组织UI。毕竟现在单Activity架构已经是主流了,你想想看,Google官方都推了多少年了。

我个人习惯是把生物识别逻辑封装在Fragment里,这样复用性更好。但这里有个坑——Fragment的生命周期比Activity复杂得多。搞不好就会出现内存泄漏、回调丢失、甚至崩溃。今天我们就来聊聊怎么在Fragment里优雅地集成人脸识别。

14.1 Fragment中使用BiometricPrompt的两种方式

嗯,这里要分两种情况讨论:

  • 方式一:Fragment直接创建BiometricPrompt实例
  • 方式二:通过宿主Activity代理调用

我个人更推荐方式一,因为逻辑内聚,Fragment自己就能搞定一切。但要注意,BiometricPrompt的构造函数需要传入一个LifecycleOwner,而Fragment本身就是LifecycleOwner,所以直接传this就行。

核心要点:BiometricPrompt必须绑定到LifecycleOwner,否则在Fragment销毁后回调可能被触发,导致IllegalStateException。

14.2 代码示例:在Fragment中集成人脸识别

下面是我在实际项目中用过的模板,你直接拿去改改就能用:

public class FaceRecognitionFragment extends Fragment {

    private BiometricPrompt biometricPrompt;
    private BiometricPrompt.PromptInfo promptInfo;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initBiometricPrompt();
    }

    private void initBiometricPrompt() {
        // 注意:这里使用Fragment作为LifecycleOwner
        biometricPrompt = new BiometricPrompt(
            this,  // Fragment implements LifecycleOwner
            new BiometricPrompt.AuthenticationCallback() {
                @Override
                public void onAuthenticationSucceeded(
                        @NonNull BiometricPrompt.AuthenticationResult result) {
                    // 识别成功
                    handleSuccess(result);
                }

                @Override
                public void onAuthenticationError(int errorCode,
                        @NonNull CharSequence errString) {
                    // 识别错误
                    handleError(errorCode, errString);
                }

                @Override
                public void onAuthenticationFailed() {
                    // 识别失败(非错误,只是没匹配上)
                    handleFailed();
                }
            }
        );

        promptInfo = new BiometricPrompt.PromptInfo.Builder()
                .setTitle("人脸验证")
                .setSubtitle("请面向手机")
                .setNegativeButtonText("取消")
                .setAllowedAuthenticators(
                    BiometricManager.Authenticators.BIOMETRIC_STRONG
                    | BiometricManager.Authenticators.BIOMETRIC_WEAK)
                .build();
    }

    public void startAuthentication() {
        if (biometricPrompt != null) {
            biometricPrompt.authenticate(promptInfo);
        }
    }

    private void handleSuccess(BiometricPrompt.AuthenticationResult result) {
        // 处理成功逻辑
    }

    private void handleError(int errorCode, CharSequence errString) {
        // 处理错误逻辑
    }

    private void handleFailed() {
        // 处理失败逻辑
    }
}

小技巧:我在项目中遇到过一个问题——Fragment被重建时,BiometricPrompt的callback会丢失。解决办法是在onSaveInstanceState里保存一个状态标记,重建后根据标记决定是否重新弹出认证。

14.3 Fragment生命周期管理:别让认证跑偏了

Fragment的生命周期比Activity复杂,主要体现在:

  • onCreateViewonDestroyView 可能多次调用
  • Fragment可以 detached 再 reattached
  • 后台Fragment可能被系统回收

我曾经踩过一个坑:在onCreateView里初始化BiometricPrompt,结果Fragment被添加到回退栈后又返回,导致重复初始化。后来我改成在onCreate里初始化,问题就解决了。

14.4 生命周期与BiometricPrompt的绑定关系

这里我画了一张图,帮你理清关系:

Fragment生命周期与BiometricPrompt绑定关系 onAttach() onCreate() onCreateView() onStart() onResume() BiometricPrompt 初始化 (推荐在onCreate中) authenticate() 调用 (通常在按钮点击后) 回调触发 (onAuthenticationXxx) onDestroy() onDestroyView() 绿色箭头:正常流程 | 红色虚线:销毁时需取消认证

14.5 避坑指南:Fragment中的人脸识别常见问题

我总结了几条血泪教训,你记一下:

警告:千万不要在onActivityCreated里调用authenticate()。我曾经这么干过,结果Fragment还没完全可见,BiometricPrompt就弹出来了,用户一脸懵。

问题场景 原因 解决方案
Fragment回退后认证回调丢失 Fragment被detach,LifecycleOwner失效 使用requireActivity()作为LifecycleOwner
多次弹出认证对话框 Fragment重建导致重复初始化 onCreate中初始化,用标志位控制
认证成功后UI未更新 回调在后台线程,未切回主线程 使用requireActivity().runOnUiThread()
内存泄漏 BiometricPrompt持有Fragment引用 onDestroy中置空

14.6 最佳实践:封装一个可复用的BiometricFragment

嗯,这里我分享一个我自己项目里用的封装思路。说白了就是做一个基类,所有需要人脸识别的Fragment都继承它:

public abstract class BaseBiometricFragment extends Fragment {

    private BiometricPrompt biometricPrompt;
    private boolean isAuthenticating = false;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initBiometricPrompt();
    }

    private void initBiometricPrompt() {
        biometricPrompt = new BiometricPrompt(
            this,
            new BiometricPrompt.AuthenticationCallback() {
                @Override
                public void onAuthenticationSucceeded(
                        @NonNull BiometricPrompt.AuthenticationResult result) {
                    isAuthenticating = false;
                    onBiometricSuccess(result);
                }

                @Override
                public void onAuthenticationError(int errorCode,
                        @NonNull CharSequence errString) {
                    isAuthenticating = false;
                    onBiometricError(errorCode, errString);
                }

                @Override
                public void onAuthenticationFailed() {
                    onBiometricFailed();
                }
            }
        );
    }

    protected void startBiometricAuth(BiometricPrompt.PromptInfo info) {
        if (!isAuthenticating && biometricPrompt != null) {
            isAuthenticating = true;
            biometricPrompt.authenticate(info);
        }
    }

    // 子类实现具体业务逻辑
    protected abstract void onBiometricSuccess(
            BiometricPrompt.AuthenticationResult result);
    protected abstract void onBiometricError(
            int errorCode, CharSequence errString);
    protected abstract void onBiometricFailed();

    @Override
    public void onDestroy() {
        super.onDestroy();
        biometricPrompt = null;  // 防止内存泄漏
    }
}

个人经验:我建议你在onStop里也做一次清理。因为Fragment可能只是不可见,但还没销毁。这时候如果认证还在进行,用户切到其他应用再回来,状态就乱了。

14.7 总结一下

Fragment里集成人脸识别,核心就三点:

  • 生命周期绑定:BiometricPrompt必须和Fragment的LifecycleOwner绑定
  • 初始化时机:onCreate里初始化,不要在onCreateView
  • 状态管理:用标志位控制认证流程,避免重复触发

说白了,只要把生命周期管好了,人脸识别在Fragment里跑起来跟Activity里一样稳。我刚开始做的时候也踩过不少坑,但后来总结出这套模式,项目里用了两年多没出过问题。


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