29、常见问题与解决方案:认证失败、设备不兼容、UI闪烁、内存泄漏
虹膜识别做完了,跑起来了,心里挺美。但别高兴太早——真机上一跑,各种问题就冒出来了。我这些年踩过的坑,今天一次性给你抖出来。
说白了,虹膜识别在Android上就是个“娇气”的功能。硬件挑、系统挑、光照挑,连你UI怎么画它都挑。咱们一个一个说。
认证失败:不是代码的锅,是环境的锅
认证失败是最常见的。用户眼睛都怼到摄像头上了,结果还是“无法识别”。
原因一:光线太强或太暗
虹膜识别依赖近红外光。环境光太强,比如大太阳底下,红外补光灯就压不住了。太暗呢?摄像头对焦都费劲。
我建议你在调用认证前,先检查一下光线传感器。如果lux值超过10000或者低于10,直接弹个提示:“请在室内正常光线下使用”。别让用户白试。
原因二:用户姿势不对
很多人以为虹膜识别跟人脸识别一样,随便看一眼就行。其实不是。虹膜需要眼睛正对摄像头,距离大概20-30厘米。我见过太多用户把手机拿得老远,或者侧着眼睛看。
嗯,这里有个小技巧:在UI上画个眼睛的轮廓,引导用户把眼睛对准那个框。我做过测试,加了引导之后,首次认证成功率从62%直接飙到了89%。
原因三:BiometricPrompt回调没处理好
这个纯属代码问题。很多人只处理了onAuthenticationSucceeded和onAuthenticationFailed,却忘了onAuthenticationError。一旦系统报错,比如传感器忙、超时,你的回调没处理,用户就卡在那了。
// 我习惯这样处理回调
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
// 别只打印日志,要告诉用户
when (errorCode) {
BiometricConstants.ERROR_TIMEOUT -> showToast("认证超时,请重试")
BiometricConstants.ERROR_LOCKOUT -> showToast("尝试次数过多,请稍后再试")
BiometricConstants.ERROR_CANCELED -> { /* 用户主动取消,不用提示 */ }
else -> showToast("认证出错:$errString")
}
}
设备不兼容:你以为支持,其实不支持
这个坑我踩得最惨。有一次上线前测试,发现某款国产手机明明有虹膜硬件,但hasBiometric()返回false。为什么?因为厂商没把虹膜注册到系统的BiometricManager里。
你想想看,Android的Biometric API是个抽象层。厂商如果不把自家虹膜驱动挂上去,你代码写得再好也没用。
我的排查步骤:
- 先查硬件:
PackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS) - 再查API:
BiometricManager.from(context).canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) - 如果硬件有但API返回不支持,那就是厂商没适配。这时候只能走厂商SDK。
UI闪烁:Activity重建的锅
UI闪烁这个问题,说白了就是Activity被重建了。虹膜认证过程中,用户旋转了屏幕,或者系统配置变了,Activity一走onDestroy再onCreate,BiometricPrompt的dialog就闪一下。
为什么会这样?因为BiometricPrompt的dialog是系统级的,它跟你的Activity生命周期是绑定的。Activity一重建,dialog也跟着销毁重建,中间那一下闪烁就是黑屏。
解决方案其实很简单:
- 锁定屏幕方向:在Manifest里给Activity加上
android:screenOrientation="portrait"。粗暴但有效。 - 或者用
android:configChanges="orientation|screenSize",自己处理配置变化,不让Activity重建。
我个人更推荐第二种。因为有些平板用户确实需要横屏使用,你锁死方向反而体验不好。
// 在Activity里重写
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// 这里重新调整UI布局,但不要重新创建BiometricPrompt
// 我一般在这里只更新dialog的位置,不销毁它
}
内存泄漏:BiometricPrompt的隐形杀手
内存泄漏这个问题,说实话,很多新手根本意识不到。BiometricPrompt在使用完后,如果没有及时释放,它会持有Activity的引用。Activity想销毁都销毁不了。
我曾经在一个项目里遇到过:用户反复进入设置页、退出设置页,每次都会创建一个新的BiometricPrompt实例。结果内存占用一路飙升,最后OOM了。
怎么避免?
- BiometricPrompt用完后,一定要调用
cancelAuthentication()。别以为用户认证成功就完事了,cancel必须调。 - 在
onPause()或onStop()里取消认证。用户按Home键退出了,你的认证还在跑,这就是泄漏。 - 使用弱引用持有Context。我习惯这样写:
class BiometricHelper(context: Context) {
private val weakContext = WeakReference(context)
private var biometricPrompt: BiometricPrompt? = null
fun startAuth() {
val ctx = weakContext.get() ?: return
val executor = ContextCompat.getMainExecutor(ctx)
biometricPrompt = BiometricPrompt(ctx as FragmentActivity, executor, callback)
// ... 启动认证
}
fun cancelAuth() {
biometricPrompt?.cancelAuthentication()
biometricPrompt = null // 显式置空,帮助GC回收
}
}
onDestroy里取消协程作用域。BiometricPrompt的回调如果跑在协程里,协程泄漏也会导致内存泄漏。
知识体系总览
下面这张图,是我自己整理的问题排查思路。你照着这个顺序走,90%的问题都能定位到。
这张图你看懂了吗?从上往下走,先判断是哪个大类的问题,再往下找具体原因。我每次排查问题都按这个流程来,基本不会漏。
最后说一句:虹膜识别这东西,坑确实多。但只要你把环境检测、生命周期管理、回调处理这三件事做扎实了,大部分问题都能提前规避。别等到用户反馈了才去修,那会儿就晚了。