From 8113c1d830ab49cac7b2e89d9904117b2e9e5377 Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Wed, 29 Apr 2026 14:23:22 +0800 Subject: [PATCH] =?UTF-8?q?refactor(activity):=20=E9=87=8D=E6=9E=84=20Base?= =?UTF-8?q?Activity=20=E5=B9=B6=E4=BC=98=E5=8C=96=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 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 --- .../shuwei/dish/match/base/BaseActivity.kt | 59 +++++++++--------- .../com/shuwei/dish/match/base/BaseApp.kt | 17 +++-- .../dish/match/ui/FoodRecognizeActivity.kt | 10 ++- .../com/shuwei/dish/match/ui/InitActivity.kt | 62 +++++++++++-------- .../dish/match/ui/PrepareFoodActivity.kt | 8 +-- app/src/main/res/layout/activity_base.xml | 2 +- 6 files changed, 87 insertions(+), 71 deletions(-) diff --git a/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt b/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt index 3db0ee7..624dec2 100644 --- a/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/base/BaseActivity.kt @@ -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, 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) { diff --git a/app/src/main/java/com/shuwei/dish/match/base/BaseApp.kt b/app/src/main/java/com/shuwei/dish/match/base/BaseApp.kt index 4cfeed1..0e208d0 100644 --- a/app/src/main/java/com/shuwei/dish/match/base/BaseApp.kt +++ b/app/src/main/java/com/shuwei/dish/match/base/BaseApp.kt @@ -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,8 +58,8 @@ class BaseApp : Application() { } companion object { - -// const val canteenId = "1678234139391512577" + private val TAG = "BaseApp" + // const val canteenId = "1678234139391512577" var canteenId = "0" // var configUrl = "" diff --git a/app/src/main/java/com/shuwei/dish/match/ui/FoodRecognizeActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/FoodRecognizeActivity.kt index de3b4f3..a64541f 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/FoodRecognizeActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/FoodRecognizeActivity.kt @@ -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() } } // 取消:关闭页面 diff --git a/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt index 63e93e5..187661b 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/InitActivity.kt @@ -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() } } diff --git a/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt index 5786c94..3681655 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/PrepareFoodActivity.kt @@ -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) + } } /** diff --git a/app/src/main/res/layout/activity_base.xml b/app/src/main/res/layout/activity_base.xml index cfb267c..3256697 100644 --- a/app/src/main/res/layout/activity_base.xml +++ b/app/src/main/res/layout/activity_base.xml @@ -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" />