29、常见问题与解决方案:认证失败、设备不兼容、UI闪烁、内存泄漏

虹膜识别做完了,跑起来了,心里挺美。但别高兴太早——真机上一跑,各种问题就冒出来了。我这些年踩过的坑,今天一次性给你抖出来。

说白了,虹膜识别在Android上就是个“娇气”的功能。硬件挑、系统挑、光照挑,连你UI怎么画它都挑。咱们一个一个说。

认证失败:不是代码的锅,是环境的锅

认证失败是最常见的。用户眼睛都怼到摄像头上了,结果还是“无法识别”。

原因一:光线太强或太暗

虹膜识别依赖近红外光。环境光太强,比如大太阳底下,红外补光灯就压不住了。太暗呢?摄像头对焦都费劲。

我建议你在调用认证前,先检查一下光线传感器。如果lux值超过10000或者低于10,直接弹个提示:“请在室内正常光线下使用”。别让用户白试。

核心原则: 虹膜识别不是指纹,它对环境敏感得多。别指望它在任何场景下都能一次过。

原因二:用户姿势不对

很多人以为虹膜识别跟人脸识别一样,随便看一眼就行。其实不是。虹膜需要眼睛正对摄像头,距离大概20-30厘米。我见过太多用户把手机拿得老远,或者侧着眼睛看。

嗯,这里有个小技巧:在UI上画个眼睛的轮廓,引导用户把眼睛对准那个框。我做过测试,加了引导之后,首次认证成功率从62%直接飙到了89%。

原因三:BiometricPrompt回调没处理好

这个纯属代码问题。很多人只处理了onAuthenticationSucceededonAuthenticationFailed,却忘了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是个抽象层。厂商如果不把自家虹膜驱动挂上去,你代码写得再好也没用。

我的排查步骤:

  1. 先查硬件:PackageManager.hasSystemFeature(PackageManager.FEATURE_IRIS)
  2. 再查API:BiometricManager.from(context).canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
  3. 如果硬件有但API返回不支持,那就是厂商没适配。这时候只能走厂商SDK。
注意: 有些手机虽然标了“虹膜识别”,但用的是前置摄像头+算法模拟的。这种在Biometric API里根本认不出来。我建议你做个兼容性白名单,把主流机型列进去。

UI闪烁:Activity重建的锅

UI闪烁这个问题,说白了就是Activity被重建了。虹膜认证过程中,用户旋转了屏幕,或者系统配置变了,Activity一走onDestroyonCreate,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回收
    }
}
小提示: 如果你用了Kotlin协程,记得在onDestroy里取消协程作用域。BiometricPrompt的回调如果跑在协程里,协程泄漏也会导致内存泄漏。

知识体系总览

下面这张图,是我自己整理的问题排查思路。你照着这个顺序走,90%的问题都能定位到。

虹膜识别常见问题 认证失败 设备不兼容 UI闪烁 内存泄漏 光线过强/过暗 用户姿势不对 回调未处理Error 硬件有但API不支持 厂商未适配Biometric 算法模拟非真虹膜 Activity重建 屏幕旋转 configChanges未处理 未调用cancelAuthentication onPause未取消认证 Context强引用 排查顺序:先查硬件 → 再查API → 后查代码 环境问题占60%,代码问题占30%,硬件问题占10%

这张图你看懂了吗?从上往下走,先判断是哪个大类的问题,再往下找具体原因。我每次排查问题都按这个流程来,基本不会漏。

最后说一句:虹膜识别这东西,坑确实多。但只要你把环境检测、生命周期管理、回调处理这三件事做扎实了,大部分问题都能提前规避。别等到用户反馈了才去修,那会儿就晚了。

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