15、指纹认证生命周期管理:Activity/Fragment生命周期绑定、取消认证、防止内存泄漏、屏幕旋转处理
指纹认证这东西,说白了就是个“一次性会话”。你调用 authenticate() 之后,系统会拉起一个系统级的认证对话框,然后等用户按指纹。但这里有个坑——这个会话的生命周期,跟你的 Activity 或 Fragment 是绑定的。如果不处理好,屏幕一转、页面一退,轻则回调不执行,重则内存泄漏、应用崩溃。
我个人习惯是把指纹认证的生命周期管理,当作一个“独立模块”来设计。别把它散落在 Activity 的各个生命周期回调里,那样后期维护起来真的很痛苦。
15.1 生命周期绑定的核心思路
先看一张图,理解一下整体流程:
嗯,这里要注意:指纹认证的 BiometricPrompt 实例,必须跟 Activity 或 Fragment 的生命周期对齐。你想想看,如果用户在认证过程中按了 Home 键,Activity 进入 onPause(),这时候认证对话框还在吗?答案是——系统会帮你关掉对话框,但你的 AuthenticationCallback 可能还在后台挂着。
15.2 标准绑定写法(Activity 示例)
我建议把 BiometricPrompt 的创建和销毁,放在 onResume() 和 onPause() 里。这样最安全。
public class FingerprintActivity extends AppCompatActivity {
private BiometricPrompt biometricPrompt;
private BiometricPrompt.PromptInfo promptInfo;
private Executor executor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fingerprint);
executor = ContextCompat.getMainExecutor(this);
setupBiometricPrompt();
}
private void setupBiometricPrompt() {
biometricPrompt = new BiometricPrompt(this, executor,
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(
BiometricPrompt.AuthenticationResult result) {
// 认证成功
handleAuthSuccess();
}
@Override
public void onAuthenticationError(int errorCode,
CharSequence errString) {
// 认证错误(包括用户取消、系统取消等)
handleAuthError(errorCode, errString);
}
@Override
public void onAuthenticationFailed() {
// 指纹不匹配
handleAuthFailed();
}
});
promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("指纹验证")
.setSubtitle("请按指纹")
.setNegativeButtonText("取消")
.build();
}
@Override
protected void onResume() {
super.onResume();
// 页面可见时,可以开始认证
// 但注意:不要在这里直接调用 authenticate()
// 应该由用户点击按钮触发
}
@Override
protected void onPause() {
super.onPause();
// 页面不可见时,立即取消认证
cancelAuthentication();
}
public void startAuthentication() {
if (biometricPrompt != null) {
biometricPrompt.authenticate(promptInfo);
}
}
private void cancelAuthentication() {
if (biometricPrompt != null) {
// 注意:cancel() 会触发 onAuthenticationError()
// 错误码为 BiometricPrompt.ERROR_CANCELED
biometricPrompt.cancelAuthentication();
}
}
}
核心原则:onPause() 里必须 cancel(),onResume() 里重新准备。别想着“用户只是按了 Home 键,回来还能继续用同一个会话”——系统不保证。
15.3 Fragment 里的生命周期绑定
Fragment 的情况稍微复杂一点。因为 Fragment 有 onPause() 和 onStop(),还有 onDetach()。我个人习惯在 onPause() 里取消认证,在 onResume() 里重新初始化。
但有一个坑:Fragment 的 onPause() 可能在 Activity 的 onPause() 之前调用。所以如果你在 Fragment 里管理认证,一定要确保 cancelAuthentication() 不会被重复调用。
public class FingerprintFragment extends Fragment {
private BiometricPrompt biometricPrompt;
private boolean isAuthenticating = false;
@Override
public void onResume() {
super.onResume();
// 重新创建 BiometricPrompt 实例
// 因为之前的实例可能已经被 cancel 了
setupBiometricPrompt();
}
@Override
public void onPause() {
super.onPause();
if (isAuthenticating) {
cancelAuthentication();
}
}
private void setupBiometricPrompt() {
biometricPrompt = new BiometricPrompt(this,
ContextCompat.getMainExecutor(requireContext()),
new BiometricPrompt.AuthenticationCallback() {
// ... 回调实现
});
}
private void cancelAuthentication() {
if (biometricPrompt != null) {
biometricPrompt.cancelAuthentication();
isAuthenticating = false;
}
}
}
注意:千万不要在 onDestroy() 里调用 cancelAuthentication()。因为 onDestroy() 调用时,Fragment 可能已经 detached,这时候调用 cancelAuthentication() 会抛出 IllegalStateException。
15.4 防止内存泄漏
内存泄漏是 Android 开发的老大难问题。指纹认证这块,泄漏点主要在 Callback 持有外部引用。
我曾经在一个项目里遇到过:AuthenticationCallback 里匿名内部类持有了 Activity 的引用,而 Activity 被销毁后,这个 Callback 还在后台等着回调。结果就是 Activity 泄漏了,OOM 警告天天弹。
解决办法其实很简单:
- 使用弱引用:在 Callback 里用
WeakReference<Activity>或WeakReference<Fragment> - 及时清理:在
onPause()或onStop()里把biometricPrompt置为 null - 避免匿名内部类:把 Callback 写成静态内部类,或者单独抽成一个类
private static class AuthCallback
extends BiometricPrompt.AuthenticationCallback {
private final WeakReference<FingerprintActivity> activityRef;
AuthCallback(FingerprintActivity activity) {
this.activityRef = new WeakReference<>(activity);
}
@Override
public void onAuthenticationSucceeded(
BiometricPrompt.AuthenticationResult result) {
FingerprintActivity activity = activityRef.get();
if (activity != null && !activity.isFinishing()) {
activity.handleAuthSuccess();
}
}
}
小技巧:在 onPause() 里调用 biometricPrompt = null; 之后,记得把 executor 也置空。虽然 ContextCompat.getMainExecutor() 返回的是单例,但显式置空是个好习惯。
15.5 屏幕旋转处理
屏幕旋转,说白了就是 Activity 重建。这时候指纹认证会话肯定断了。你想想看,系统对话框还在,但 Activity 已经重建了,回调往哪发?
处理方式有两种:
| 方案 | 做法 | 优缺点 |
|---|---|---|
| 方案一:禁止旋转 | 在 Manifest 里设置 android:screenOrientation="portrait" |
简单粗暴,但用户体验差 |
| 方案二:保存状态 | 在 onSaveInstanceState() 里保存认证状态,重建后恢复 |
灵活,但需要处理状态恢复逻辑 |
| 方案三:使用 Fragment | 把认证逻辑放在 setRetainInstance(true) 的 Fragment 里 |
Fragment 不随旋转重建,但已废弃,不推荐 |
我个人推荐方案二。虽然代码量多一点,但最可控。
private static final String KEY_AUTH_STATE = "auth_state";
private boolean isAuthInProgress = false;
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY_AUTH_STATE, isAuthInProgress);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null) {
boolean wasAuthInProgress =
savedInstanceState.getBoolean(KEY_AUTH_STATE, false);
if (wasAuthInProgress) {
// 旋转前正在认证,重新拉起对话框
// 但注意:不要自动弹,最好让用户手动触发
showAuthResumeDialog();
}
}
}
我的经验:屏幕旋转后不要自动重新弹认证对话框。用户可能只是不小心转了一下屏幕,你突然弹个指纹框,体验很糟糕。我一般会在界面上显示一个“继续验证”的按钮,让用户自己点。
15.6 完整生命周期管理清单
最后,我整理了一个检查清单。每次写指纹认证代码时,我都会对照着过一遍:
- onCreate():初始化 BiometricPrompt 实例和 PromptInfo
- onResume():重新创建 BiometricPrompt(如果之前被 cancel 了)
- onPause():调用 cancelAuthentication(),置空引用
- onSaveInstanceState():保存认证状态
- onRestoreInstanceState():恢复认证状态,提示用户重新验证
- Callback 实现:使用弱引用,避免泄漏
- 屏幕旋转:不要自动弹框,让用户手动触发
嗯,做到这几点,指纹认证的生命周期管理基本就稳了。记住一句话:页面不可见时,认证必须取消;页面重建时,认证重新开始。别想着复用旧的会话,系统不给你这个保证。