refactor(base): 优化协程作用域和空安全处理

- 将自定义CoroutineScope替换为lifecycleScope以避免内存泄漏
- 添加runCatching包装器处理潜在异常并记录错误日志
- 使用安全调用操作符替代强制调用防止空指针异常
- 在CountDownTimer中正确管理生命周期以避免内存泄漏
- 修复设备信息获取中的数组越界风险
- 统一异步操作的线程切换方式
- 保护Fragment中的_binding访问权限
- 改进相机助手对象引用的安全性检查
This commit is contained in:
mazengfei
2026-03-31 17:12:14 +08:00
parent df9212d2dd
commit 0a193ed84e
6 changed files with 48 additions and 30 deletions
@@ -30,10 +30,10 @@ import com.sw.platecabinet.utils.IntervalExecutor
import com.sw.platecabinet.utils.SpTool import com.sw.platecabinet.utils.SpTool
import com.sw.platecabinet.view.CustomLoadingDialog import com.sw.platecabinet.view.CustomLoadingDialog
import com.sw.platecabinet.viewmodel.UserViewModel import com.sw.platecabinet.viewmodel.UserViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
/** /**
@@ -142,8 +142,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
onRightDoubleClick() onRightDoubleClick()
} }
) )
val scope = CoroutineScope(Dispatchers.Main) timeJob = lifecycleScope.launch {
timeJob = scope.launch {
DateTimeUtils.realTimeChineseDateFlow() DateTimeUtils.realTimeChineseDateFlow()
.collect { date -> .collect { date ->
it.tvRightTime.text = date it.tvRightTime.text = date
@@ -347,7 +346,9 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
val alreadyExists = featureMatches.any { it } val alreadyExists = featureMatches.any { it }
if (!alreadyExists) { if (!alreadyExists) {
//当前特征不存在,保存 //当前特征不存在,保存
faceList.add(getNewFaceEntity(model)) runCatching { getNewFaceEntity(model) }
.onSuccess { faceList.add(it) }
.onFailure { Timber.e(it, "人脸特征解码失败: userId=${model.userId}") }
} }
} }
} }
@@ -378,13 +379,13 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
} }
fun clearAllFace(block: () -> Unit) { fun clearAllFace(block: () -> Unit) {
Thread { lifecycleScope.launch(Dispatchers.IO) {
val faceDao = FaceDatabase.getInstance(App.getContext()).faceDao() val faceDao = FaceDatabase.getInstance(App.getContext()).faceDao()
faceDao.deleteAll() faceDao.deleteAll()
faceDao.resetId() faceDao.resetId()
recognizeViewModel.refreshFaceList() recognizeViewModel.refreshFaceList()
runOnUiThread { block() } withContext(Dispatchers.Main) { block() }
}.start() }
} }
private var permissionCallback: ((isGranted: Boolean) -> Unit)? = null private var permissionCallback: ((isGranted: Boolean) -> Unit)? = null
@@ -238,7 +238,10 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
return@setCallback return@setCallback
} }
if (this.userFaceInfo == null && isFitData(userFaceInfo)) { if (this.userFaceInfo == null && isFitData(userFaceInfo)) {
val headBmp = userFaceInfo.headBmp val headBmp = userFaceInfo.headBmp ?: run {
loadLogInfo("collectFace,isFitData:headBmp 为 null,跳过")
return@setCallback
}
if (headBmp.width <= 200 || headBmp.height <= 200) { if (headBmp.width <= 200 || headBmp.height <= 200) {
loadLogInfo("collectFace,isFitData:拿到人脸提取特征数据照片过小w:${headBmp.width},h:${headBmp.height}-----------") loadLogInfo("collectFace,isFitData:拿到人脸提取特征数据照片过小w:${headBmp.width},h:${headBmp.height}-----------")
binding.llCollectButton.visible() binding.llCollectButton.visible()
@@ -253,30 +256,30 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
} }
} }
recognizeViewModel.ftInitCode.observe(this) { ftInitCode: Int? -> recognizeViewModel.ftInitCode.observe(this) { ftInitCode: Int? ->
if (ftInitCode != ErrorInfo.MOK) { if (ftInitCode != null && ftInitCode != ErrorInfo.MOK) {
val error: String? = getString( val error: String? = getString(
R.string.specific_engine_init_failed, "ftEngine", R.string.specific_engine_init_failed, "ftEngine",
ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!) ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode)
) )
Timber.e("ftInitCode observe = $error") Timber.e("ftInitCode observe = $error")
ToastUtils.showToast(error) ToastUtils.showToast(error)
} }
} }
recognizeViewModel.frInitCode.observe(this) { frInitCode: Int? -> recognizeViewModel.frInitCode.observe(this) { frInitCode: Int? ->
if (frInitCode != ErrorInfo.MOK) { if (frInitCode != null && frInitCode != ErrorInfo.MOK) {
val error: String? = getString( val error: String? = getString(
R.string.specific_engine_init_failed, "frEngine", R.string.specific_engine_init_failed, "frEngine",
frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!) frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode)
) )
Timber.e("frInitCode observe = $error") Timber.e("frInitCode observe = $error")
ToastUtils.showToast(error) ToastUtils.showToast(error)
} }
} }
recognizeViewModel.flInitCode.observe(this) { flInitCode: Int? -> recognizeViewModel.flInitCode.observe(this) { flInitCode: Int? ->
if (flInitCode != ErrorInfo.MOK) { if (flInitCode != null && flInitCode != ErrorInfo.MOK) {
val error: String? = getString( val error: String? = getString(
R.string.specific_engine_init_failed, "flEngine", R.string.specific_engine_init_failed, "flEngine",
flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!) flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode)
) )
Timber.e("flInitCode observe = $error") Timber.e("flInitCode observe = $error")
ToastUtils.showToast(error) ToastUtils.showToast(error)
@@ -615,7 +618,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) { override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) {
Timber.i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") Timber.i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}")
if (rgbFaceRectTransformer != null) { if (rgbFaceRectTransformer != null) {
rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation rgbFaceRectTransformer?.cameraDisplayOrientation = displayOrientation
} }
Timber.i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation") Timber.i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation")
} }
@@ -672,10 +675,11 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
private fun resumeCamera() { private fun resumeCamera() {
// isRecognition = true // isRecognition = true
if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) { val helper = rgbCameraHelper
if (helper != null && helper.isStopped) {
loadLogInfo("collectFace,resumeCamera: ") loadLogInfo("collectFace,resumeCamera: ")
isRecognition = true isRecognition = true
rgbCameraHelper!!.start() helper.start()
} else { } else {
//recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) //recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
@@ -15,7 +15,7 @@ import com.sw.platecabinet.view.CustomLoadingDialog
abstract class BaseFragment<VB : ViewBinding>( abstract class BaseFragment<VB : ViewBinding>(
private val bindingInflater: (inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean) -> VB private val bindingInflater: (inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean) -> VB
) : Fragment() { ) : Fragment() {
private var _binding: VB? = null protected var _binding: VB? = null
protected val binding get() = _binding!! protected val binding get() = _binding!!
private var mDialogWaiting: CustomLoadingDialog? = null private var mDialogWaiting: CustomLoadingDialog? = null
@@ -11,6 +11,7 @@ import timber.log.Timber
class PlateCabinetFullFragment : class PlateCabinetFullFragment :
BaseFragment<FragmentPlateCabinetFullBinding>(FragmentPlateCabinetFullBinding::inflate) { BaseFragment<FragmentPlateCabinetFullBinding>(FragmentPlateCabinetFullBinding::inflate) {
private var totalTimeInMillis: Long = Constants.AUTO_CLOSE_TIME * 1000 private var totalTimeInMillis: Long = Constants.AUTO_CLOSE_TIME * 1000
private var countDownTimer: CountDownTimer? = null
override fun initialize() { override fun initialize() {
Timber.d("initialize") Timber.d("initialize")
@@ -18,15 +19,22 @@ class PlateCabinetFullFragment :
} }
fun initCountTime() { fun initCountTime() {
object : CountDownTimer(totalTimeInMillis, 1000) { countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) {
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
binding.tvAutoClose.text = "${(millisUntilFinished / 1000).toInt() + 1}秒后返回主屏" _binding?.tvAutoClose?.text = "${(millisUntilFinished / 1000).toInt() + 1}秒后返回主屏"
} }
override fun onFinish() { override fun onFinish() {
activity?.finish() activity?.finish()
} }
}.start() }
countDownTimer?.start()
}
override fun onDestroyView() {
countDownTimer?.cancel()
countDownTimer = null
super.onDestroyView()
} }
} }
@@ -20,6 +20,7 @@ class PlateOpenFragment :
// private var isAdmin: Boolean = false // private var isAdmin: Boolean = false
private var isScanCode = false private var isScanCode = false
private var countDownTimer: CountDownTimer? = null
companion object { companion object {
const val IS_SCAN_CODE = "isScanCode" const val IS_SCAN_CODE = "isScanCode"
// @JvmStatic // @JvmStatic
@@ -75,19 +76,23 @@ class PlateOpenFragment :
} }
fun initCountTime() { fun initCountTime() {
object : CountDownTimer(totalTimeInMillis, 1000) { countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) {
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
binding.tvAutoClose.text = ((millisUntilFinished / 1000).toInt() + 1).toString() _binding?.tvAutoClose?.text = ((millisUntilFinished / 1000).toInt() + 1).toString()
} }
override fun onFinish() { override fun onFinish() {
val ctx = context ?: return
activity?.finish() activity?.finish()
// if (!isAdmin) { ctx.startActivity(Intent(ctx, InitActivity::class.java))
val intent = Intent(context, InitActivity::class.java)
startActivity(intent)
// }
} }
}.start() }
countDownTimer?.start()
}
override fun onDestroyView() {
countDownTimer?.cancel()
countDownTimer = null
super.onDestroyView()
} }
} }
@@ -154,7 +154,7 @@ class CrashHandler private constructor(private val context: Context) :
// 设备信息 // 设备信息
sb.append("Vendor: ${Build.MANUFACTURER}\n") sb.append("Vendor: ${Build.MANUFACTURER}\n")
sb.append("Model: ${Build.MODEL}\n") sb.append("Model: ${Build.MODEL}\n")
sb.append("CPU ABI: ${Build.SUPPORTED_ABIS[0]}\n") sb.append("CPU ABI: ${Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"}\n")
// 其他信息 // 其他信息
sb.append("Locale: ${Locale.getDefault()}\n") sb.append("Locale: ${Locale.getDefault()}\n")