Просмотр исходного кода

fix: thread-safety for cachedActionMenuItems with Mutex, move refreshActionMenuItems to onRefsReady for lifecycle safety

HapeLee 4 месяцев назад
Родитель
Сommit
c94740d79b
1 измененных файлов с 67 добавлено и 64 удалено
  1. 67 64
      app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt

+ 67 - 64
app/src/main/java/io/legado/app/ui/book/read/ReadBookController.kt

@@ -72,6 +72,8 @@ import kotlinx.coroutines.Dispatchers.Main
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 import kotlinx.coroutines.withContext
 
 
@@ -93,10 +95,6 @@ class ReadBookController(
     var onUnhandledEffect: (ReadBookEffect) -> Unit = {}
     var onClose: (() -> Unit)? = null
 
-    init {
-        refreshActionMenuItems()
-    }
-
     // Page state — moved from Activity
     var pageChanged: Boolean = false
         private set
@@ -123,6 +121,7 @@ class ReadBookController(
     private val _textMenuState = MutableStateFlow<TextMenuState?>(null)
     val textMenuState = _textMenuState.asStateFlow()
     private var textMenuRequestVersion = 0L
+    private val menuMutex = Mutex()
     @Volatile
     private var cachedActionMenuItems: List<ActionMenuItem>? = null
 
@@ -191,6 +190,7 @@ class ReadBookController(
         }
         newRefs.readView.upTime()
         newRefs.readView.upBattery(activity.sysBattery)
+        refreshActionMenuItems()
     }
 
     fun onMenuVisibilityChanged(visible: Boolean) {
@@ -655,75 +655,78 @@ class ReadBookController(
     }
 
     suspend fun getActionMenuItems(): List<ActionMenuItem> = withContext(IO) {
-        val cached = cachedActionMenuItems
-        if (cached != null) return@withContext cached
-
-        val items = mutableListOf<ActionMenuItem>()
-        items.add(ActionMenuItem(R.id.menu_copy, activity.getString(android.R.string.copy)))
-        items.add(ActionMenuItem(R.id.menu_share_str, activity.getString(R.string.share)))
-        items.add(ActionMenuItem(R.id.menu_browser, activity.getString(R.string.browser)))
-        items.add(ActionMenuItem(R.id.menu_aloud, activity.getString(R.string.read_aloud)))
-        items.add(ActionMenuItem(R.id.menu_bookmark, activity.getString(R.string.bookmark)))
-        items.add(ActionMenuItem(R.id.menu_dict, activity.getString(R.string.dict)))
-        items.add(ActionMenuItem(R.id.menu_replace, activity.getString(R.string.replace)))
-        items.add(ActionMenuItem(R.id.menu_edit, activity.getString(R.string.edit)))
-        items.add(ActionMenuItem(R.id.menu_ai_clean, activity.getString(R.string.ai_text_clean)))
-        items.add(ActionMenuItem(R.id.menu_ai_rewrite, activity.getString(R.string.ai_text_rewrite)))
-        items.add(ActionMenuItem(R.id.menu_search_content, activity.getString(R.string.search_content)))
-
-        val thirdPartyItems = mutableListOf<ActionMenuItem>()
-        runCatching {
-            val pm = activity.packageManager
-            val intent = Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/plain")
-            val resolveInfos = pm.queryIntentActivities(intent, 0)
-            for (resolveInfo in resolveInfos) {
-                val processIntent = Intent()
-                    .setAction(Intent.ACTION_PROCESS_TEXT)
-                    .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false)
-                    .setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)
-                
-                val title = resolveInfo.loadLabel(pm).toString()
-                val icon = if (ReadConfig.showSelectMenuIcon) {
-                    runCatching { resolveInfo.loadIcon(pm) }.getOrNull()
-                } else null
-
-                thirdPartyItems.add(ActionMenuItem(id = -1, title = title, iconDrawable = icon, intent = processIntent))
+        menuMutex.withLock {
+            cachedActionMenuItems?.let { return@withContext it }
+
+            val items = mutableListOf<ActionMenuItem>()
+            items.add(ActionMenuItem(R.id.menu_copy, activity.getString(android.R.string.copy)))
+            items.add(ActionMenuItem(R.id.menu_share_str, activity.getString(R.string.share)))
+            items.add(ActionMenuItem(R.id.menu_browser, activity.getString(R.string.browser)))
+            items.add(ActionMenuItem(R.id.menu_aloud, activity.getString(R.string.read_aloud)))
+            items.add(ActionMenuItem(R.id.menu_bookmark, activity.getString(R.string.bookmark)))
+            items.add(ActionMenuItem(R.id.menu_dict, activity.getString(R.string.dict)))
+            items.add(ActionMenuItem(R.id.menu_replace, activity.getString(R.string.replace)))
+            items.add(ActionMenuItem(R.id.menu_edit, activity.getString(R.string.edit)))
+            items.add(ActionMenuItem(R.id.menu_ai_clean, activity.getString(R.string.ai_text_clean)))
+            items.add(ActionMenuItem(R.id.menu_ai_rewrite, activity.getString(R.string.ai_text_rewrite)))
+            items.add(ActionMenuItem(R.id.menu_search_content, activity.getString(R.string.search_content)))
+
+            val thirdPartyItems = mutableListOf<ActionMenuItem>()
+            runCatching {
+                val pm = activity.packageManager
+                val intent = Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/plain")
+                val resolveInfos = pm.queryIntentActivities(intent, 0)
+                for (resolveInfo in resolveInfos) {
+                    val processIntent = Intent()
+                        .setAction(Intent.ACTION_PROCESS_TEXT)
+                        .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false)
+                        .setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)
+                    
+                    val title = resolveInfo.loadLabel(pm).toString()
+                    val icon = if (ReadConfig.showSelectMenuIcon) {
+                        runCatching { resolveInfo.loadIcon(pm) }.getOrNull()
+                    } else null
+
+                    thirdPartyItems.add(ActionMenuItem(id = -1, title = title, iconDrawable = icon, intent = processIntent))
+                }
             }
-        }
 
-        val allItems = items + thirdPartyItems
-        val configStr = ReadConfig.textSelectMenuConfig
-        val result = if (configStr.isEmpty()) {
-            allItems
-        } else {
-            runCatching {
-                val savedConfigs = GSON.fromJsonObject<List<SelectionMenuConfigItem>>(configStr).getOrNull() ?: emptyList()
-                val savedMap = savedConfigs.associateBy { it.id }
-
-                val sortedItems = mutableListOf<ActionMenuItem>()
-                for (saved in savedConfigs) {
-                    val found = allItems.find { it.uniqueId == saved.id }
-                    if (found != null) {
-                        val resolvedShowState = saved.showState ?: if (saved.enabled == false) 1 else 0
-                        sortedItems.add(found.copy(showState = resolvedShowState))
+            val allItems = items + thirdPartyItems
+            val configStr = ReadConfig.textSelectMenuConfig
+            val result = if (configStr.isEmpty()) {
+                allItems
+            } else {
+                runCatching {
+                    val savedConfigs = GSON.fromJsonObject<List<SelectionMenuConfigItem>>(configStr).getOrNull() ?: emptyList()
+                    val savedMap = savedConfigs.associateBy { it.id }
+
+                    val sortedItems = mutableListOf<ActionMenuItem>()
+                    for (saved in savedConfigs) {
+                        val found = allItems.find { it.uniqueId == saved.id }
+                        if (found != null) {
+                            val resolvedShowState = saved.showState ?: if (saved.enabled == false) 1 else 0
+                            sortedItems.add(found.copy(showState = resolvedShowState))
+                        }
                     }
-                }
-                for (item in allItems) {
-                    val uniqueId = item.uniqueId
-                    if (!savedMap.containsKey(uniqueId)) {
-                        sortedItems.add(item)
+                    for (item in allItems) {
+                        val uniqueId = item.uniqueId
+                        if (!savedMap.containsKey(uniqueId)) {
+                            sortedItems.add(item)
+                        }
                     }
-                }
-                sortedItems
-            }.getOrDefault(allItems)
+                    sortedItems
+                }.getOrDefault(allItems)
+            }
+            cachedActionMenuItems = result
+            result
         }
-        cachedActionMenuItems = result
-        result
     }
 
     fun refreshActionMenuItems() {
-        cachedActionMenuItems = null
         activity.lifecycleScope.launch {
+            menuMutex.withLock {
+                cachedActionMenuItems = null
+            }
             val menuItems = getActionMenuItems()
             _textMenuState.value?.let { currentState ->
                 _textMenuState.value = currentState.copy(items = menuItems)