perf(image): 优化图片加载性能

- 使用 BitmapDecodeHelper 替换 BitmapFactory 进行采样解码
- 添加单线程执行器进行异步图片解码处理
- 实现图片视图布局完成后再进行尺寸计算和加载
- 防止因 Activity 销毁导致的内存泄漏问题
- 在 SprayingFinishActivity、SprayingOCRActivity 和 SprayingResultActivity 中统一图片加载逻辑
这个提交包含在:
徐勤民 2026-04-14 22:59:25 +08:00
父节点 ff6a0c3a33
当前提交 7310127d56
共有 3 个文件被更改,包括 58 次插入7 次删除

查看文件

@ -2,10 +2,11 @@ package com.nova.brain.glass.ui
import android.app.Activity
import android.content.Intent
import android.graphics.BitmapFactory
import androidx.core.view.doOnLayout
import androidx.recyclerview.widget.RecyclerView
import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivitySprayingFinishBinding
import com.nova.brain.glass.helper.BitmapDecodeHelper
import com.nova.brain.glass.helper.OfflineCmdListener
import com.nova.brain.glass.helper.OfflineCmdServiceHelper
import com.nova.brain.glass.helper.SprayingPhotoManager
@ -15,6 +16,7 @@ import com.xuqm.base.adapter.BasePagedAdapter
import com.xuqm.base.adapter.CommonPagedAdapter
import com.xuqm.base.adapter.ViewHolder
import com.xuqm.base.ui.BaseListFormLayoutNormalActivity
import java.util.concurrent.Executors
class SprayingFinishActivity :
BaseListFormLayoutNormalActivity<ItemItem, SprayingFinishVM, ActivitySprayingFinishBinding>() {
@ -22,6 +24,7 @@ class SprayingFinishActivity :
override fun fullscreen(): Boolean = true
override fun getRecyclerOrientation(): Int = RecyclerView.VERTICAL
private val imageDecodeExecutor = Executors.newSingleThreadExecutor()
private val listener = object : OfflineCmdListener {
override fun onOfflineCmd(cmd: String) {
@ -52,7 +55,21 @@ class SprayingFinishActivity :
val imageViews = listOf(binding.photo1, binding.photo2, binding.photo3)
imageViews.forEach { it.setImageDrawable(null) }
photos.forEachIndexed { index, path ->
imageViews.getOrNull(index)?.setImageBitmap(BitmapFactory.decodeFile(path))
imageViews.getOrNull(index)?.let { imageView ->
imageView.doOnLayout {
val targetWidth = imageView.width.coerceAtLeast(1)
val targetHeight = imageView.height.coerceAtLeast(1)
imageDecodeExecutor.execute {
val bitmap = BitmapDecodeHelper.decodeSampledBitmap(path, targetWidth, targetHeight)
runOnUiThread {
if (isFinishing || isDestroyed) {
return@runOnUiThread
}
imageView.setImageBitmap(bitmap)
}
}
}
}
}
}

查看文件

@ -1,13 +1,14 @@
package com.nova.brain.glass.ui
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.CountDownTimer
import android.os.Environment
import android.view.WindowManager
import androidx.core.view.doOnLayout
import androidx.recyclerview.widget.RecyclerView
import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivitySprayingOcrBinding
import com.nova.brain.glass.helper.BitmapDecodeHelper
import com.nova.brain.glass.helper.OfflineCmdListener
import com.nova.brain.glass.helper.OfflineCmdServiceHelper
import com.nova.brain.glass.helper.SprayingPhotoManager
@ -24,6 +25,7 @@ import com.xuqm.base.common.LogHelper
import com.xuqm.base.extensions.showMessage
import com.xuqm.base.ui.BaseListFormLayoutNormalActivity
import java.io.File
import java.util.concurrent.Executors
import java.util.UUID
class SprayingOCRActivity :
@ -63,6 +65,7 @@ class SprayingOCRActivity :
}
private val photoCallbackId = UUID.randomUUID().toString()
private var resultCountdown: CountDownTimer? = null
private val imageDecodeExecutor = Executors.newSingleThreadExecutor()
private val mPhotoFileCallback = object : PhotoFileCallback.Stub() {
override fun onTakePhoto(path: String) {
@ -139,9 +142,21 @@ class SprayingOCRActivity :
override fun adapter(): BasePagedAdapter<ItemItem> = adapter
private fun showPhoto(path: String) {
binding.content.setImageBitmap(BitmapFactory.decodeFile(path))
binding.content.doOnLayout {
val targetWidth = it.width.coerceAtLeast(1)
val targetHeight = it.height.coerceAtLeast(1)
imageDecodeExecutor.execute {
val bitmap = BitmapDecodeHelper.decodeSampledBitmap(path, targetWidth, targetHeight)
runOnUiThread {
if (isFinishing || isDestroyed) {
return@runOnUiThread
}
binding.content.setImageBitmap(bitmap)
restartResultCountdown(path)
}
}
}
}
private fun restartResultCountdown(path: String) {
resultCountdown?.cancel()

查看文件

@ -2,14 +2,15 @@ package com.nova.brain.glass.ui
import android.app.Activity
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.Paint
import android.os.Environment
import android.view.WindowManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.doOnLayout
import androidx.recyclerview.widget.RecyclerView
import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivitySprayingResultBinding
import com.nova.brain.glass.helper.BitmapDecodeHelper
import com.nova.brain.glass.helper.OfflineCmdListener
import com.nova.brain.glass.helper.OfflineCmdServiceHelper
import com.nova.brain.glass.helper.SprayingPhotoManager
@ -25,6 +26,7 @@ import com.xuqm.base.common.LogHelper
import com.xuqm.base.extensions.showMessage
import com.xuqm.base.ui.BaseListFormLayoutNormalActivity
import java.io.File
import java.util.concurrent.Executors
import java.util.UUID
class SprayingResultActivity :
@ -33,6 +35,7 @@ class SprayingResultActivity :
override fun fullscreen(): Boolean = true
override fun getRecyclerOrientation(): Int = RecyclerView.HORIZONTAL
private val imageDecodeExecutor = Executors.newSingleThreadExecutor()
private var status = true
private val manualResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
@ -157,7 +160,7 @@ class SprayingResultActivity :
super.initData()
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
intent.getStringExtra("path")?.apply {
binding.iv.setImageBitmap(BitmapFactory.decodeFile(this))
showResultImage(this)
setStatusImage()
}
}
@ -231,4 +234,20 @@ class SprayingResultActivity :
}
override fun adapter(): BasePagedAdapter<ItemItem> = adapter
private fun showResultImage(path: String) {
binding.iv.doOnLayout {
val targetWidth = it.width.coerceAtLeast(1)
val targetHeight = it.height.coerceAtLeast(1)
imageDecodeExecutor.execute {
val bitmap = BitmapDecodeHelper.decodeSampledBitmap(path, targetWidth, targetHeight)
runOnUiThread {
if (isFinishing || isDestroyed) {
return@runOnUiThread
}
binding.iv.setImageBitmap(bitmap)
}
}
}
}
}