6.7.0 - Alpha13 - 修复 auto.registerEvent 注册的无障碍服务事件会被其他脚本误清理的问题 (issue #466)

This commit is contained in:
SuperMonster003
2025-12-29 22:29:57 +08:00
parent 0b38cd9d44
commit 517872f620
5 changed files with 93 additions and 32 deletions

View File

@@ -56,6 +56,7 @@
"images 部分相关方法可能引发内存泄露的问题 _[`issue #372`](http://issues.autojs6.com/372)_", "images 部分相关方法可能引发内存泄露的问题 _[`issue #372`](http://issues.autojs6.com/372)_",
"ocr 部分重载方法可能无法正常使用的问题", "ocr 部分重载方法可能无法正常使用的问题",
"ocr.detect 方法获得的结果可能与 ocr.mode 不匹配的问题 _[`issue #468`](http://issues.autojs6.com/468)_", "ocr.detect 方法获得的结果可能与 ocr.mode 不匹配的问题 _[`issue #468`](http://issues.autojs6.com/468)_",
"auto.registerEvent 注册的无障碍服务事件会被其他脚本误清理的问题 _[`issue #466`](http://issues.autojs6.com/466)_",
"Android 10 UiObject#child 方法可能出现 ArrayIndexOutOfBoundsException 异常的问题 _[`issue #416`](http://issues.autojs6.com/416)_", "Android 10 UiObject#child 方法可能出现 ArrayIndexOutOfBoundsException 异常的问题 _[`issue #416`](http://issues.autojs6.com/416)_",
"运行项目时 project.json 配置参数无法正常解析的问题", "运行项目时 project.json 配置参数无法正常解析的问题",
"项目配置文件中构建版本号或构建时间出现较大数字时可能导致应用崩溃的问题", "项目配置文件中构建版本号或构建时间出现较大数字时可能导致应用崩溃的问题",

View File

@@ -9,11 +9,11 @@ import android.view.accessibility.AccessibilityNodeInfo
import org.autojs.autojs.core.accessibility.AccessibilityTool.Companion.DEFAULT_A11Y_SERVICE_START_TIMEOUT import org.autojs.autojs.core.accessibility.AccessibilityTool.Companion.DEFAULT_A11Y_SERVICE_START_TIMEOUT
import org.autojs.autojs.core.accessibility.SimpleActionAutomator.Companion.AccessibilityEventCallback import org.autojs.autojs.core.accessibility.SimpleActionAutomator.Companion.AccessibilityEventCallback
import org.autojs.autojs.core.automator.AccessibilityEventWrapper import org.autojs.autojs.core.automator.AccessibilityEventWrapper
import org.autojs.autojs.event.EventDispatcher
import org.autojs.autojs.core.pref.Language import org.autojs.autojs.core.pref.Language
import org.autojs.autojs.event.EventDispatcher
import org.autojs.autojs.ui.main.drawer.DrawerFragment.Companion.Event.AccessibilityServiceStateChangedEvent import org.autojs.autojs.ui.main.drawer.DrawerFragment.Companion.Event.AccessibilityServiceStateChangedEvent
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import java.util.TreeMap import java.util.*
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -21,7 +21,7 @@ import java.util.concurrent.locks.ReentrantLock
/** /**
* Created by Stardust on May 2, 2017. * Created by Stardust on May 2, 2017.
* Modified by SuperMonster003 as of Mar 20, 2022. * Modified by SuperMonster003 as of Dec 29, 2025.
*/ */
open class AccessibilityService : android.accessibilityservice.AccessibilityService() { open class AccessibilityService : android.accessibilityservice.AccessibilityService() {
@@ -30,7 +30,9 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
var fastRootInActiveWindow: AccessibilityNodeInfo? = null var fastRootInActiveWindow: AccessibilityNodeInfo? = null
var bridge: AccessibilityBridge? = null var bridge: AccessibilityBridge? = null
private val eventBox = TreeMap<Int, AccessibilityEventCallback?>() // eventType -> (ownerId -> callback)
private val eventBox = HashMap<Int, MutableMap<String, AccessibilityEventCallback?>>()
private val eventBoxLock = Any()
private val gestureEventDispatcher = EventDispatcher<GestureListener>() private val gestureEventDispatcher = EventDispatcher<GestureListener>()
@@ -43,23 +45,62 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
AccessibilityEvent::class.java.getField( AccessibilityEvent::class.java.getField(
"TYPE_${event.uppercase(Language.getPrefLanguage().locale)}" "TYPE_${event.uppercase(Language.getPrefLanguage().locale)}"
).get(null) as Int ).get(null) as Int
} catch (unused: NoSuchFieldException) { } catch (_: NoSuchFieldException) {
throw IllegalArgumentException("Unknown event: $event") throw IllegalArgumentException("Unknown event: $event")
} }
} }
fun addAccessibilityEventCallback(name: String, callback: AccessibilityEventCallback?) { fun addAccessibilityEventCallback(ownerId: String, name: String, callback: AccessibilityEventCallback?) {
eventBox[eventNameToType(name)] = callback val type = eventNameToType(name)
synchronized(eventBoxLock) {
val bucket = eventBox.getOrPut(type) { HashMap() }
if (callback == null) {
bucket.remove(ownerId)
if (bucket.isEmpty()) eventBox.remove(type)
} else {
bucket[ownerId] = callback
}
}
} }
fun removeAccessibilityEventCallback(name: String) { fun removeAccessibilityEventCallback(ownerId: String, name: String) {
eventBox.remove(eventNameToType(name)) val type = eventNameToType(name)
synchronized(eventBoxLock) {
val bucket = eventBox[type] ?: return
bucket.remove(ownerId)
if (bucket.isEmpty()) eventBox.remove(type)
}
}
fun removeAllAccessibilityEventCallbacks(ownerId: String) {
synchronized(eventBoxLock) {
val it = eventBox.entries.iterator()
while (it.hasNext()) {
val entry = it.next()
entry.value.remove(ownerId)
if (entry.value.isEmpty()) it.remove()
}
}
} }
override fun onAccessibilityEvent(event: AccessibilityEvent) { override fun onAccessibilityEvent(event: AccessibilityEvent) {
instance = this instance = this
val type = event.eventType val type = event.eventType
eventBox[type]?.onAccessibilityEvent(AccessibilityEventWrapper(event))
// Snapshot callbacks to avoid holding lock while invoking user code.
// zh-CN: 为回调建立快照, 以避免在调用用户代码时持锁.
val callbacks: List<AccessibilityEventCallback> = synchronized(eventBoxLock) {
val bucket = eventBox[type] ?: return@synchronized emptyList()
bucket.values.filterNotNull().toList()
}
if (callbacks.isNotEmpty()) {
val wrapper = AccessibilityEventWrapper(event)
callbacks.forEach { cb ->
cb.onAccessibilityEvent(wrapper)
}
}
if (containsAllEventTypes || eventTypes.contains(type)) { if (containsAllEventTypes || eventTypes.contains(type)) {
if (type == TYPE_WINDOW_STATE_CHANGED || type == TYPE_VIEW_FOCUSED) { if (type == TYPE_WINDOW_STATE_CHANGED || type == TYPE_VIEW_FOCUSED) {
rootInActiveWindow?.also { fastRootInActiveWindow = it } rootInActiveWindow?.also { fastRootInActiveWindow = it }
@@ -67,11 +108,9 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
for ((_, delegate) in delegates) { for ((_, delegate) in delegates) {
val types = delegate.eventTypes val types = delegate.eventTypes
if (types == null || types.contains(type)) { if (types == null || types.contains(type)) {
// val start = System.currentTimeMillis()
if (delegate.onAccessibilityEvent(this@AccessibilityService, event)) { if (delegate.onAccessibilityEvent(this@AccessibilityService, event)) {
break break
} }
// Log.v(TAG, "millis: " + (System.currentTimeMillis() - start) + " delegate: " + delegate::class.java.name)
} }
} }
} }
@@ -101,11 +140,7 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
} }
override fun getRootInActiveWindow(): AccessibilityNodeInfo? { override fun getRootInActiveWindow(): AccessibilityNodeInfo? {
return try { return runCatching { super.getRootInActiveWindow() }.getOrNull()
super.getRootInActiveWindow()
} catch (e: Exception) {
null
}
} }
override fun onDestroy() { override fun onDestroy() {
@@ -177,13 +212,10 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
} }
} }
fun stop() = try { fun stop() = runCatching {
instance?.disableSelf() instance?.disableSelf()
instance = null instance = null
true }.isSuccess
} catch (e: Exception) {
false
}
fun waitForStarted(timeout: Long = DEFAULT_A11Y_SERVICE_START_TIMEOUT): Boolean { fun waitForStarted(timeout: Long = DEFAULT_A11Y_SERVICE_START_TIMEOUT): Boolean {
if (hasInstance()) { if (hasInstance()) {
@@ -209,7 +241,11 @@ open class AccessibilityService : android.accessibilityservice.AccessibilityServ
@JvmStatic @JvmStatic
fun clearAccessibilityEventCallback() { fun clearAccessibilityEventCallback() {
instance?.eventBox?.clear() instance?.let { svc ->
synchronized(svc.eventBoxLock) {
svc.eventBox.clear()
}
}
} }
fun setCallback(listener: AccessibilityServiceCallback?) { fun setCallback(listener: AccessibilityServiceCallback?) {

View File

@@ -34,9 +34,12 @@ import android.accessibilityservice.AccessibilityService as AndroidAccessibility
/** /**
* Created by Stardust on Apr 2, 2017. * Created by Stardust on Apr 2, 2017.
* Modified by SuperMonster003 as of Dec 29, 2025.
*/ */
class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge, private val scriptRuntime: ScriptRuntime) { class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge, private val scriptRuntime: ScriptRuntime) {
private val mA11yEventOwnerId: String = scriptRuntime.ownerId
private val mGlobalActionAutomatorRaw by lazy { private val mGlobalActionAutomatorRaw by lazy {
GlobalActionAutomator(scriptRuntime.uiHandler.applicationContext, Handler(scriptRuntime.loopers.servantLooper)) { GlobalActionAutomator(scriptRuntime.uiHandler.applicationContext, Handler(scriptRuntime.loopers.servantLooper)) {
ensureService() ensureService()
@@ -198,12 +201,27 @@ class SimpleActionAutomator(private val accessibilityBridge: AccessibilityBridge
// ! zh-CN: 参考 Auto.js Pro. // ! zh-CN: 参考 Auto.js Pro.
fun registerEvent(eventName: String, callback: AccessibilityEventCallback?) { fun registerEvent(eventName: String, callback: AccessibilityEventCallback?) {
ensureService() ensureService()
AccessibilityService.instance?.addAccessibilityEventCallback(eventName, callback) val service = AccessibilityService.instance ?: return
if (callback == null) {
service.removeAccessibilityEventCallback(mA11yEventOwnerId, eventName)
return
}
service.addAccessibilityEventCallback(mA11yEventOwnerId, eventName, callback)
} }
// @Created by 抠脚本人 on Jul 10, 2023. // @Created by 抠脚本人 on Jul 10, 2023.
fun removeEvent(eventName: String) { fun removeEvent(eventName: String) {
AccessibilityService.instance?.removeAccessibilityEventCallback(eventName) AccessibilityService.instance?.removeAccessibilityEventCallback(mA11yEventOwnerId, eventName)
}
/**
* Called when the script exits, only clears accessibility
* event listeners registered by the current script.
* zh-CN: 脚本退出时调用, 只清理由当前脚本注册的无障碍事件监听.
*/
fun removeAllEventsForThisRuntime() {
AccessibilityService.instance?.removeAllAccessibilityEventCallbacks(mA11yEventOwnerId)
} }
private fun performAction(simpleAction: SimpleAction): Boolean { private fun performAction(simpleAction: SimpleAction): Boolean {

View File

@@ -11,7 +11,6 @@ import org.autojs.autojs.annotation.ScriptInterface
import org.autojs.autojs.annotation.ScriptVariable import org.autojs.autojs.annotation.ScriptVariable
import org.autojs.autojs.concurrent.VolatileDispose import org.autojs.autojs.concurrent.VolatileDispose
import org.autojs.autojs.core.accessibility.AccessibilityBridge import org.autojs.autojs.core.accessibility.AccessibilityBridge
import org.autojs.autojs.core.accessibility.AccessibilityService
import org.autojs.autojs.core.accessibility.SimpleActionAutomator import org.autojs.autojs.core.accessibility.SimpleActionAutomator
import org.autojs.autojs.core.accessibility.monitor.CloseableManager import org.autojs.autojs.core.accessibility.monitor.CloseableManager
import org.autojs.autojs.core.activity.ActivityInfoProvider import org.autojs.autojs.core.activity.ActivityInfoProvider
@@ -177,8 +176,8 @@ import org.autojs.autojs.runtime.api.augment.util.VersionCodes as UtilVersionCod
/** /**
* Created by Stardust on Jan 27, 2017. * Created by Stardust on Jan 27, 2017.
* Modified by SuperMonster003 as of Dec 1, 2021. * Modified by SuperMonster003 as of Dec 29, 2025.
* Created by SuperMonster003 on May 24, 2024. * Transformed by SuperMonster003 on May 24, 2024.
*/ */
@Suppress("unused", "PropertyName", "PrivatePropertyName") @Suppress("unused", "PropertyName", "PrivatePropertyName")
class ScriptRuntime private constructor(builder: Builder) { class ScriptRuntime private constructor(builder: Builder) {
@@ -186,6 +185,8 @@ class ScriptRuntime private constructor(builder: Builder) {
private val mJob = SupervisorJob() private val mJob = SupervisorJob()
val coroutineScope = CoroutineScope(Dispatchers.Default + mJob) val coroutineScope = CoroutineScope(Dispatchers.Default + mJob)
val coroutineContext = coroutineScope.coroutineContext val coroutineContext = coroutineScope.coroutineContext
val ownerId = "runtime@${System.identityHashCode(this)}"
private var mUiHandlerAppContext: Context private var mUiHandlerAppContext: Context
private var mRootShell: AbstractShell? = null private var mRootShell: AbstractShell? = null
@@ -601,7 +602,12 @@ class ScriptRuntime private constructor(builder: Builder) {
// ! 清空无障碍事件. // ! 清空无障碍事件.
// ! en-US (translated by SuperMonster003 on Jul 29, 2024): // ! en-US (translated by SuperMonster003 on Jul 29, 2024):
// ! To clear accessibility event callbacks. // ! To clear accessibility event callbacks.
ignoresException({ AccessibilityService.clearAccessibilityEventCallback() }) // @Hint by SuperMonster003 on Dec 29, 2025.
// ! Only clean up accessibility event callbacks registered
// ! by the current script to avoid affecting other still-running scripts.
// ! zh-CN: 只清理当前脚本注册的无障碍事件回调,避免影响其他仍在运行的脚本.
// # ignoresException({ AccessibilityService.clearAccessibilityEventCallback() })
ignoresException({ automator.removeAllEventsForThisRuntime() })
ignoresException({ RootUtils.resetRuntimeOverriddenRootModeState() }) ignoresException({ RootUtils.resetRuntimeOverriddenRootModeState() })

View File

@@ -1,5 +1,5 @@
#Mon Dec 29 18:48:41 CST 2025 #Mon Dec 29 19:40:59 CST 2025
BUILD_TIME=1767005321663 BUILD_TIME=1767008459948
COMPILE_SDK_VERSION=36 COMPILE_SDK_VERSION=36
IMAGE_QUANT_CMAKE_VERSION=3.22.1 IMAGE_QUANT_CMAKE_VERSION=3.22.1
IMAGE_QUANT_NDK_VERSION=26.1.10909125 IMAGE_QUANT_NDK_VERSION=26.1.10909125
@@ -27,6 +27,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3 RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
TARGET_SDK_VERSION=36 TARGET_SDK_VERSION=36
TARGET_SDK_VERSION_INRT=29 TARGET_SDK_VERSION_INRT=29
VERSION_BUILD=3561 VERSION_BUILD=3562
VERSION_NAME=6.7.0 Alpha13 VERSION_NAME=6.7.0 Alpha13
VSCODE_EXT_REQUIRED_VERSION=1.0.8 VSCODE_EXT_REQUIRED_VERSION=1.0.8