22、人脸识别与MVVM架构:ViewModel管理认证状态、Repository模式封装、LiveData/StateFlow通知UI

说实话,很多新手做生物识别时,喜欢把代码全塞在Activity里。嗯,我早期也这么干过——结果项目一复杂,Activity直接变成“屎山”。后来我痛定思痛,全面拥抱MVVM架构。今天咱们就聊聊,怎么把人脸识别和MVVM结合起来,让代码既清爽又抗造。

为什么非得用MVVM?

你想想看,人脸识别涉及好几个环节:相机预览、检测回调、结果处理、UI更新。如果全写在一起,调试一个bug可能要翻几百行代码。我个人习惯把业务逻辑和UI彻底分离,这样好处很明显:

  • ViewModel:掌管认证状态,Activity重建时数据不丢
  • Repository:封装底层实现,切换人脸库时只改一个地方
  • LiveData/StateFlow:自动通知UI更新,不用手动刷新

我在项目中遇到过最典型的场景:用户旋转手机,Activity重建,结果人脸认证状态丢了,还得重新检测。用ViewModel后,状态稳稳地保留着,用户体验直接提升一个档次。

ViewModel:认证状态的“保险箱”

ViewModel的生命周期比Activity长,它会在配置变更后继续存活。说白了,它就是认证状态的“保险箱”。

class FaceAuthViewModel : ViewModel() {
    // 认证状态:空闲、检测中、成功、失败
    private val _authState = MutableLiveData<AuthState>(AuthState.Idle)
    val authState: LiveData<AuthState> = _authState

    // 人脸特征数据
    private val _faceFeatures = MutableLiveData<FaceFeatures?>(null)
    val faceFeatures: LiveData<FaceFeatures?> = _faceFeatures

    fun startAuth(bitmap: Bitmap) {
        _authState.value = AuthState.Detecting
        viewModelScope.launch(Dispatchers.Default) {
            // 模拟人脸检测
            val result = faceRepository.detectFace(bitmap)
            _authState.postValue(
                if (result.isSuccess) AuthState.Success 
                else AuthState.Failed(result.errorMsg)
            )
        }
    }
}

sealed class AuthState {
    object Idle : AuthState()
    object Detecting : AuthState()
    data class Success(val confidence: Float) : AuthState()
    data class Failed(val reason: String) : AuthState()
}
我的小技巧:用 sealed class 定义状态,比用 Int 常量安全得多。编译器会帮你检查所有分支,少写一个都不行。

Repository模式:封装底层实现

Repository就像个“中间人”,它把人脸检测的具体实现藏起来。你想想看,今天用Google的FaceDetector,明天换成华为的FaceKit,只要Repository接口不变,ViewModel完全不用改。

class FaceRepository(private val context: Context) {
    private val detector = FaceDetector.Builder(context)
        .setClassificationType(FaceDetector.ALL_CLASSIFICATIONS)
        .build()

    suspend fun detectFace(bitmap: Bitmap): Result<FaceFeatures> {
        return withContext(Dispatchers.IO) {
            try {
                val faces = detector.detect(bitmap)
                if (faces.isNotEmpty()) {
                    val feature = extractFeatures(faces[0])
                    Result.success(feature)
                } else {
                    Result.failure(Exception("未检测到人脸"))
                }
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }

    private fun extractFeatures(face: Face): FaceFeatures {
        // 提取特征点、置信度等
        return FaceFeatures(
            landmarks = face.landmarks,
            confidence = face.confidence,
            bounds = face.bounds
        )
    }
}
我曾经踩过的坑:人脸检测一定要放在IO线程,千万别在主线程跑。有一次我忘了切线程,结果UI卡了整整3秒,用户直接差评。用withContext(Dispatchers.IO)包一下,问题就解决了。

LiveData vs StateFlow:怎么选?

LiveData是Android原生方案,简单好用。StateFlow是Kotlin协程的产物,功能更强大。我个人建议:

场景 推荐方案 原因
简单UI更新 LiveData 开箱即用,无需额外依赖
复杂数据流 StateFlow 支持操作符,防抖、去重更方便
需要初始值 StateFlow 天生带初始值,LiveData需要额外处理

举个例子,如果用StateFlow实现人脸检测状态:

class FaceAuthViewModel : ViewModel() {
    private val _authState = MutableStateFlow<AuthState>(AuthState.Idle)
    val authState: StateFlow<AuthState> = _authState.asStateFlow()

    fun startAuth(bitmap: Bitmap) {
        _authState.value = AuthState.Detecting
        viewModelScope.launch {
            faceRepository.detectFace(bitmap)
                .onSuccess { _authState.value = AuthState.Success(it.confidence) }
                .onFailure { _authState.value = AuthState.Failed(it.message ?: "未知错误") }
        }
    }
}
核心要点:StateFlow的collect是挂起函数,需要在协程里调用。LiveData的observe是回调,在非协程环境更友好。选哪个,看你项目里协程用得多不多。

UI层如何订阅?

Activity或Fragment里,只需要观察ViewModel的状态,然后更新UI即可。代码非常干净:

class FaceAuthActivity : AppCompatActivity() {
    private val viewModel: FaceAuthViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_face_auth)

        // 观察认证状态
        viewModel.authState.observe(this) { state ->
            when (state) {
                is AuthState.Idle -> showIdle()
                is AuthState.Detecting -> showDetecting()
                is AuthState.Success -> showSuccess(state.confidence)
                is AuthState.Failed -> showFailed(state.reason)
            }
        }
    }
}

你看,Activity里没有任何业务逻辑,全是UI操作。这就是MVVM的魅力——职责清晰,测试也方便。

整体架构图

下面这张图,展示了人脸识别在MVVM架构中的完整数据流:

人脸识别MVVM架构数据流 UI层 (Activity/Fragment) - 显示相机预览 - 观察LiveData/StateFlow - 更新认证状态UI ViewModel - 管理AuthState - 调用Repository - 通过LiveData通知UI Repository - 封装人脸检测 - 切换底层实现 - 返回Result<T> 观察 调用 委托 底层实现 Google / 华为 / 商汤 返回结果 Result 状态 数据流方向:UI → ViewModel → Repository → 底层实现 结果流方向:底层实现 → Repository → ViewModel → UI
我的建议:刚开始用MVVM时,别追求一步到位。先把ViewModel和LiveData用起来,等熟悉了再引入Repository。我见过太多人一上来就搞全套,结果把自己绕晕了。

好了,关于人脸识别和MVVM架构的整合,今天就聊到这儿。记住:ViewModel管状态,Repository管实现,LiveData/StateFlow管通知。这三者配合好了,你的代码会变得异常清晰。下次维护时,你会感谢当初的自己。


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