refactor(activity): 重构 BaseActivity 并优化相关页面

- 将 handler 改为 private,onDestroy 统一清理所有待执行任务
- 新增 protected postDelayed 方法供子类安全调度延时任务
- 将 DateTimeFormatter 提取为 companion object 常量,避免每秒重复创建
- setTitleBar 调用时自动显示标题栏
- 拆分 permissionCallback 为单权限和多权限独立回调,避免覆盖
- 将权限 launcher 改为 private,收敛访问入口
- 删除死代码 bgState/useBackground 及注释掉的旧代码
- InitActivity 倒计时改用私有 countdownHandler/countdownTask,解除对父类 handler 的依赖
- PrepareFoodActivity 替换 handler.postDelayed 为 BaseActivity.postDelayed
- FoodRecognizeActivity 持有弹窗引用防止重复打开
- BaseApp 使用 ScaleDeviceConfig.DEVICE_ID_2 替换硬编码设备 ID
This commit is contained in:
2026-04-29 14:23:22 +08:00
parent db839fd596
commit 8113c1d830
6 changed files with 87 additions and 71 deletions
@@ -9,8 +9,6 @@ import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
@@ -35,9 +33,14 @@ import java.time.format.DateTimeFormatter
import java.util.Locale
import kotlin.getValue
@Suppress("DEPRECATION")
open class BaseActivity : AppCompatActivity() {
companion object {
/** 日期时间格式化器,复用避免每秒重复创建 */
private val DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy年MM月dd日 EEEE***HH:mm:ss", Locale.CHINA)
}
private lateinit var binding: ActivityBaseBinding
override fun onCreate(savedInstanceState: Bundle?) {
@@ -56,13 +59,6 @@ open class BaseActivity : AppCompatActivity() {
}
setContentView(binding.root)
statusBarDarkFont(enable = false)
// window.setDecorFitsSystemWindows(false) // 启用 Edge-to-Edge
// window.insetsController?.apply {
// hide(WindowInsets.Type.statusBars()) // 隐藏状态栏
// systemBarsBehavior =
// WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE // 滑动时临时显示
// }
// 主设备垂直 padding 30dp,子设备 15dp
val verticalPadding = if (GlobalData.deviceRole == DeviceRole.MASTER) 30.dp else 10.dp
binding.tvLeftTime.setPadding(0, verticalPadding, 0, verticalPadding)
@@ -71,27 +67,18 @@ open class BaseActivity : AppCompatActivity() {
binding.ivBack.setOnClickListener { finish() }
}
private var bgState = true
fun useBackground(enable: Boolean) {
bgState = enable
}
override fun onDestroy() {
handler.removeCallbacksAndMessages(null)
dismissLoading()
super.onDestroy()
ActivityManager.removeActivity(this)
}
fun hideStatusBar() {
// enableEdgeToEdge()
//// val uiOptions = (View.SYSTEM_UI_FLAG_FULLSCREEN // 隐藏状态栏
//// or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) // 隐藏导航栏(可选)
// window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
hideSystemBars()
}
fun statusBarDarkFont(enable: Boolean) {
//window.decorView.systemUiVisibility = if (enable) View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR else 0
val color = ContextCompat.getColor(
this, if (enable) R.color.black else R.color.white
)
@@ -105,6 +92,7 @@ open class BaseActivity : AppCompatActivity() {
titleAction: ((TextView) -> Unit)? = null,
rightIconActon: ((ImageView) -> Unit)? = null
) {
binding.llTitleBar.visible()
titleBarAction?.invoke(binding.llTitleBar)
backAction?.invoke(binding.ivBack)
titleAction?.invoke(binding.tvTitle)
@@ -117,7 +105,7 @@ open class BaseActivity : AppCompatActivity() {
}
}
public fun setHeaderBackground(isHomePage: Boolean = false) {
fun setHeaderBackground(isHomePage: Boolean = false) {
binding.ivHeaderBg.setImageResource(
if (isHomePage) R.drawable.bg_home_page else R.drawable.bg_other_page
)
@@ -135,14 +123,13 @@ open class BaseActivity : AppCompatActivity() {
}
private fun updateDateTime() {
val formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 EEEE***HH:mm:ss", Locale.CHINA)
val dateTime = formatter.format(LocalDateTime.now(ZoneId.of("Asia/Shanghai")))
val dateTime = DATE_FORMATTER.format(LocalDateTime.now(ZoneId.of("Asia/Shanghai")))
val arr = dateTime.split("***")
binding.tvRightTime.text = arr[0]
binding.tvLeftTime.text = arr[1]
}
val handler = Handler(Looper.getMainLooper())
private val handler = Handler(Looper.getMainLooper())
private val updateTask = object : Runnable {
override fun run() {
updateDateTime()
@@ -182,27 +169,37 @@ open class BaseActivity : AppCompatActivity() {
}, 500)
}
/**
* 在主线程延迟执行任务,供子类使用
* @param delayMs 延迟毫秒数
* @param block 待执行的任务
*/
protected fun postDelayed(delayMs: Long, block: () -> Unit) {
handler.postDelayed(block, delayMs)
}
val netViewModel: NetViewModel by viewModels()
val appViewModel: DbViewModel by viewModels()
private var permissionCallback: ((isGranted: Boolean) -> Unit)? = null
private var singlePermissionCallback: ((isGranted: Boolean) -> Unit)? = null
private var multiplePermissionsCallback: ((isGranted: Boolean) -> Unit)? = null
private var activityCallback: ((intent: Intent?) -> Unit)? = null
fun requestMultiplePermissions(
permissions: Array<String>,
callback: (isGranted: Boolean) -> Unit
) {
this.permissionCallback = callback
this.multiplePermissionsCallback = callback
requestMultiplePermissionsLauncher.launch(permissions)
}
fun requestPermission(permission: String, callback: (isGranted: Boolean) -> Unit) {
this.permissionCallback = callback
this.singlePermissionCallback = callback
requestPermissionLauncher.launch(permission)
}
val requestMultiplePermissionsLauncher = registerForActivityResult(
private val requestMultiplePermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
var isGranted = true
@@ -211,14 +208,14 @@ open class BaseActivity : AppCompatActivity() {
isGranted = false
}
}
permissionCallback?.invoke(isGranted)
multiplePermissionsCallback?.invoke(isGranted)
}
// 权限请求回调
val requestPermissionLauncher = registerForActivityResult(
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
permissionCallback?.invoke(isGranted)
singlePermissionCallback?.invoke(isGranted)
}
fun startActivity(intent: Intent, callback: (Intent?) -> Unit) {
@@ -9,29 +9,38 @@ import android.util.Log
import com.shuwei.dish.match.R
import com.shuwei.dish.match.db.DatabaseProvider
import com.shuwei.dish.match.objbox.ObjectBox
import com.shuwei.dish.match.scale.ScaleDeviceConfig
import com.shuwei.dish.match.scale.ScaleServiceManager
import com.shuwei.dish.match.ui.InitActivity.Companion.TAG
import com.shuwei.dish.match.utils.AppUtil
import com.shuwei.dish.match.utils.BootReceiver
import com.shuwei.dish.match.utils.CrashHandler
import com.shuwei.dish.match.utils.Weigher2
/**
* BaseApp is the main application class that extends Android's Application class.
* It handles initialization of core components, device role determination,
* and service management for the weighing system.
*/
class BaseApp : Application() {
// Lazy initialization of the database instance
val database by lazy { DatabaseProvider(this).instance }
override fun onCreate() {
super.onCreate()
// Set the application instance
instance = this
// Initialize crash handler for error tracking
CrashHandler.init(this)
// Get and store the unique device ID
val deviceId = AppUtil.getUDID(this)
Log.d(TAG, "onCreate: deviceId=$deviceId")
GlobalData.deviceId = deviceId
// 根据设备 ID 判断角色:指定 ID 为主设备,其余为子设备
// 主设备是/dev/ttyS7,子设备是/dev/ttyS4
if (deviceId == "8fc2ab34-2137-3112-acca-f884ea8736d4") {
if (deviceId == ScaleDeviceConfig.DEVICE_ID_2) {
GlobalData.deviceRole = DeviceRole.MASTER
Weigher2.setDevicePort("/dev/ttyS7")
} else {
@@ -49,7 +58,7 @@ class BaseApp : Application() {
}
companion object {
private val TAG = "BaseApp"
// const val canteenId = "1678234139391512577"
var canteenId = "0"
// var configUrl = ""
@@ -99,6 +99,9 @@ class FoodRecognizeActivity : BaseActivity() {
/** 右侧自定义滚动指示条最小高度 */
private val scrollIndicatorMinHeightPx by lazy { (24 * resources.displayMetrics.density).toInt() }
/** 食材搜索弹窗,持有引用防止重复打开 */
private var foodSearchDialog: FoodSearchDialog? = null
private val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
updateScrollIndicator()
@@ -259,14 +262,15 @@ class FoodRecognizeActivity : BaseActivity() {
* 初始化按钮点击事件
*/
private fun initClickListeners() {
// 更换食材:打开食材搜索弹窗
// 更换食材:打开食材搜索弹窗,已打开时跳过
binding.btnChangeFood.clickWithDebounce {
// val currentName = list.getOrNull(selectedPosition)?.goodsName
FoodSearchDialog(
if (foodSearchDialog?.isShowing == true) return@clickWithDebounce
foodSearchDialog = FoodSearchDialog(
activity = this,
// defGoodsName = currentName,
onItemSelected = { item -> updateSelectedFood(item) }
).show()
).also { it.show() }
}
// 取消:关闭页面
@@ -3,6 +3,8 @@ package com.shuwei.dish.match.ui
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.view.animation.RotateAnimation
import androidx.activity.addCallback
@@ -33,6 +35,34 @@ class InitActivity : BaseActivity() {
private lateinit var binding: ActivityInitBinding
private var startTime = 0L // 倒计时开始时间
private var lastNetworkCheckTime = 0L // 上次检测网络的时间
private val countdownHandler = Handler(Looper.getMainLooper())
private val countdownTask = object : Runnable {
override fun run() {
val elapsedTime = SystemClock.elapsedRealtime() - startTime
val remainingTime = 60 - (elapsedTime / 1000).toInt()
val currentNetworkCheckTime = (elapsedTime / 1000).toInt()
// 每 10 秒检测一次网络
if (currentNetworkCheckTime > 0 && currentNetworkCheckTime % 10 == 0 && currentNetworkCheckTime != lastNetworkCheckTime.toInt()) {
lastNetworkCheckTime = currentNetworkCheckTime.toLong()
checkNetworkConnection()
}
// 倒计时未结束,继续更新 UI
if (remainingTime > 0) {
binding.tvCountdown.text = "${remainingTime}"
countdownHandler.postDelayed(this, 100)
} else {
// 倒计时结束,最后检测一次网络
if (NetworkUtils.isNetworkConnected(this@InitActivity)) {
startNextPage()
} else {
// 网络未连接,显示"连接网络"按钮
showNetworkButton()
}
}
}
}
@SuppressLint("HardwareIds")
override fun onCreate(savedInstanceState: Bundle?) {
@@ -108,6 +138,7 @@ class InitActivity : BaseActivity() {
}
override fun onDestroy() {
countdownHandler.removeCallbacksAndMessages(null)
WeightUtil.stopContinuousRead()
ScaleServiceManager.stop()
super.onDestroy()
@@ -155,7 +186,7 @@ class InitActivity : BaseActivity() {
override fun onPause() {
super.onPause()
// 页面不可见时停止倒计时
handler.removeCallbacksAndMessages(null)
countdownHandler.removeCallbacks(countdownTask)
}
/**
@@ -178,31 +209,8 @@ class InitActivity : BaseActivity() {
* 使用 SystemClock.elapsedRealtime() 确保精确计时
*/
private fun scheduleCountdown() {
handler.postDelayed({
val elapsedTime = SystemClock.elapsedRealtime() - startTime
val remainingTime = 60 - (elapsedTime / 1000).toInt()
val currentNetworkCheckTime = (elapsedTime / 1000).toInt()
// 每 10 秒检测一次网络
if (currentNetworkCheckTime > 0 && currentNetworkCheckTime % 10 == 0 && currentNetworkCheckTime != lastNetworkCheckTime.toInt()) {
lastNetworkCheckTime = currentNetworkCheckTime.toLong()
checkNetworkConnection()
}
// 倒计时未结束,继续更新 UI
if (remainingTime > 0) {
binding.tvCountdown.text = "${remainingTime}"
scheduleCountdown()
} else {
// 倒计时结束,最后检测一次网络
if (NetworkUtils.isNetworkConnected(this)) {
startNextPage()
} else {
// 网络未连接,显示"连接网络"按钮
showNetworkButton()
}
}
}, 100) // 每 100ms 检查一次,确保精确性
countdownHandler.removeCallbacks(countdownTask)
countdownHandler.postDelayed(countdownTask, 100)
}
/**
@@ -211,7 +219,7 @@ class InitActivity : BaseActivity() {
private fun checkNetworkConnection() {
if (NetworkUtils.isNetworkConnected(this)) {
// 网络连接成功,停止倒计时并跳转到 HomeActivity
handler.removeCallbacksAndMessages(null)
countdownHandler.removeCallbacks(countdownTask)
startNextPage()
}
}
@@ -390,9 +390,7 @@ class PrepareFoodActivity : BaseActivity() {
dishPartAdapter.notifyItemChanged(position)
return@SwipeCallback
}
list.removeAt(position)
dishPartAdapter.notifyItemRemoved(position)
dishPartAdapter.notifyItemRangeChanged(position, list.size - position)
dishPartAdapter.removeAt(position)
toast("已删除")
})
itemTouchHelper.attachToRecyclerView(this)
@@ -537,10 +535,10 @@ class PrepareFoodActivity : BaseActivity() {
)
}
Log.d(TAG, "takePhoto queryFood, 数据添加完成")
handler.postDelayed({
postDelayed(1000) {
dismissLoading()
loadRecognizeResultPage(foodList)
}, 1000)
}
}
/**
+1 -1
View File
@@ -13,7 +13,7 @@
android:scaleType="centerCrop"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"
tools:src="@drawable/bg_home_page" />
tools:src="@drawable/bg_other_page" />
<TextView
android:id="@+id/tvLeftTime"