android - Slow screen value detection using root access and uiautomator in Kotlin – Optimization help needed - Stack Overflow

admin2025-04-30  0

I'm developing an automation service in Kotlin that uses root access and uiautomator to detect values on the screen of an Android device. However, the value search takes around 3 seconds, which is too slow for the required functionality. The target value is on the screen, but it's not detected as quickly as expected.

Additionally, I’m using a test app that alternates values on the screen every 5 seconds, so I need to detect values as fast as possible.

I’ve been using root access and uiautomator to fetch values from the screen, but I’m encountering performance issues, especially with speed. The detection is slower than I need. I’ve tried optimizing the execution flow but haven’t found a solution that meets the required speed.

I’m looking for ways to speed up this detection process, preferably without using accessibility services. If there are any optimizations or different approaches I can try, I’d appreciate your suggestions.

Logs

2025-01-03 23:30:19.234 32522-893 SearchService com.lr D ===== STARTING VALUE SEARCH ===== 2025-01-03 23:30:19.234 32522-893 SearchService com.lr D Target value set: 160 2025-01-03 23:30:21.560 32522-893 SearchService com.lr D ===== FINISHING VALUE SEARCH ===== 2025-01-03 23:30:21.560 32522-893 SearchService com.lr D Total values analyzed: 4 2025-01-03 23:30:21.560 32522-893 SearchService com.lr D Total values found: 0 2025-01-03 23:30:21.760 32522-893 SearchService com.lr D ===== STARTING VALUE SEARCH ===== 2025-01-03 23:30:21.760 32522-893 SearchService com.lr D Target value set: 160 2025-01-03 23:30:24.058 32522-893 SearchService com.lr D ===== FINISHING VALUE SEARCH ===== 2025-01-03 23:30:24.058 32522-893 SearchService com.lr D Total values analyzed: 4 2025-01-03 23:30:24.059 32522-893 SearchService com.lr D Total values found: 0 2025-01-03 23:30:24.216 32522-893 SearchService com.lr D ===== STARTING VALUE SEARCH ===== 2025-01-03 23:30:24.216 32522-893 SearchService com.lr D Target value set: 160 2025-01-03 23:30:26.533 32522-893 SearchService com.lr D ===== FINISHING VALUE SEARCH ===== 2025-01-03 23:30:26.533 32522-893 SearchService com.lr D Total values analyzed: 0 2025-01-03 23:30:26.533 32522-893 SearchService com.lr D Total values found: 0 2025-01-03 23:30:26.722 32522-893 SearchService com.lr D ===== STARTING VALUE SEARCH ===== 2025-01-03 23:30:26.722 32522-893 SearchService com.lr D Target value set: 160 2025-01-03 23:30:29.905 32522-893 SearchService com.lr D >>> Value greater or equal found: $165 (numeric value: 165 >= 160) 2025-01-03 23:30:29.905 32522-893 SearchService com.lr D >>> Value added to the list with bounds: Rect(36, 376 - 167, 479) 2025-01-03 23:30:29.908 32522-893 SearchService com.lr D >>> Value added to Store

**Code That’s Causing the Problem**

class SearchService {
    companion object {
        private const val TAG = "SearchService"
        private val NODE_REGEX = "<node[^>]*text=\"\\$[^\"]*\"[^>]*bounds=\"\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]\"[^>]*/>".toRegex()
        private val TEXT_REGEX = "text=\"(\\$[^\"]+)\"".toRegex()
        private val NUMBER_REGEX = "\\$(\\d+)".toRegex()
        private val BOUNDS_REGEX = "bounds=\"\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]\"[^>]*/>".toRegex()
    }

    fun getCurrentPackageName(): String? {
        val output = executeRootCommand("dumpsys window | grep mCurrentFocus")
        return try {
            val regex = "\\{.*?\\s+(\\S+)/".toRegex()
            val matchResult = regex.find(output)
            val packageName = matchResult?.groupValues?.get(1)
            Log.d(TAG, "Extracted package name: $packageName from output: $output")
            packageName
        } catch (e: Exception) {
            Log.e(TAG, "Error extracting package name: ${e.message}")
            null
        }
    }

    suspend fun searchForValues(targetValue: String, searching: AtomicBoolean): List<Pair<String, Rect>> {
        if (!searching.get()) {
            Log.d(TAG, "Search already completed earlier, ignoring call")
            return emptyList()
        }

        return withContext(Dispatchers.IO) {
            try {
                val startTime = System.currentTimeMillis()
                Log.d(TAG, "\n----------------------------------------")
                Log.d(TAG, "===== STARTING VALUE SEARCH =====")
                Log.d(TAG, "Target value set: $targetValue")

                if (targetValue.isEmpty()) {
                    Log.e(TAG, "ERROR: Target value not set!")
                    return@withContext emptyList()
                }

                val targetValueInt = targetValue.toIntOrNull() ?: run {
                    Log.e(TAG, "ERROR: Target value is not a valid number!")
                    return@withContext emptyList()
                }

                val values = mutableListOf<Pair<String, Rect>>()
                var totalValuesAnalyzed = 0

                Log.d(TAG, "Checking UiAutomator availability...")
                val uiautomatorPath = executeRootCommand("which uiautomator") ?: ""
                if (uiautomatorPath.isBlank()) {
                    Log.e(TAG, "UiAutomator not found!")
                    return@withContext emptyList()
                }

                executeRootCommand("chmod 777 /sdcard")

                val dumpCommand = "uiautomator dump /sdcard/window_dump.xml && cat /sdcard/window_dump.xml"

                Log.d(TAG, "Executing UiAutomator command: $dumpCommand")
                val xmlOutput = executeRootCommand(dumpCommand) ?: ""

                if (xmlOutput.isBlank()) {
                    Log.e(TAG, "No output from UiAutomator!")
                    return@withContext emptyList()
                }

                val matches = NODE_REGEX.findAll(xmlOutput)

                matches.forEach { match ->
                    try {
                        val fullText = TEXT_REGEX.find(match.value)?.groupValues?.get(1)

                        if (fullText != null) {
                            totalValuesAnalyzed++

                            val foundNumber = NUMBER_REGEX.find(fullText)?.groupValues?.get(1)?.toIntOrNull()

                            if (foundNumber != null && foundNumber >= targetValueInt) {
                                val boundsMatch = BOUNDS_REGEX.find(match.value)

                                if (boundsMatch != null) {
                                    val (left, top, right, bottom) = boundsMatch.destructured
                                    val bounds = Rect(
                                        left.toInt(),
                                        top.toInt(),
                                        right.toInt(),
                                        bottom.toInt()
                                    )

                                    val result = Pair(fullText, bounds)
                                    values.add(result)

                                    Log.d(TAG, ">>> Value greater or equal found: $fullText (numeric value: $foundNumber >= $targetValueInt)")
                                    Log.d(TAG, ">>> Value added to list with bounds: $bounds")

                                    Store.get().add(Item(
                                        txt = fullText,
                                        time = System.currentTimeMillis(),
                                        area = bounds
                                    ))
                                    Log.d(TAG, ">>> Value added to Store")
                                }
                            }
                        }
                    } catch (e: Exception) {
                        Log.e(TAG, "Error processing node: ${e.message}")
                    }
                }

                executeRootCommand("rm /sdcard/window_dump.xml")

                val endTime = System.currentTimeMillis()
                Log.d(TAG, "\n----------------------------------------")
                Log.d(TAG, "===== FINISHING VALUE SEARCH =====")
                Log.d(TAG, "Total values analyzed: $totalValuesAnalyzed")
                Log.d(TAG, "Total values found: ${values.size}")
                Log.d(TAG, "Total search time: ${endTime - startTime} ms")
                Log.d(TAG, "----------------------------------------\n")

                values
            } catch (e: Exception) {
                Log.e(TAG, "===== ERROR IN VALUE SEARCH =====")
                Log.e(TAG, "Error: ${e.message}")
                Log.e(TAG, "Stack trace: ${e.stackTrace.joinToString("\n")}")
                emptyList()
            }
        }
    }

    fun executeRootCommand(command: String): String {
        return try {
            val process = Runtime.getRuntime().exec("su")
            val outputStream = DataOutputStream(process.outputStream)

            Log.d(TAG, "Executing root command: $command")
            outputStream.writeBytes("$command\n")
            outputStream.flush()
            outputStream.writeBytes("exit\n")
            outputStream.flush()
            outputStream.close()

            val reader = BufferedReader(InputStreamReader(process.inputStream))
            val output = StringBuilder()
            var line: String?

            while (reader.readLine().also { line = it } != null) {
                output.append(line).append("\n")
            }

            val result = output.toString().trim()
            Log.d(TAG, "Root command output: $result")

            process.waitFor()
            process.destroy()

            result
        } catch (e: Exception) {
            Log.e(TAG, "Root command error: ${e.message}")
            ""
        }
    }
}
转载请注明原文地址:http://www.anycun.com/QandA/1746026693a91530.html