6.7.0 - Alpha2 - 修复 Android 10 UiObject#child 异常 (issue #416); 移除 UiObject#hashCode/equals 覆写; 新增 UiObject#isShifted 方法

This commit is contained in:
SuperMonster003
2025-06-23 16:48:01 +08:00
parent cd3bb8491a
commit 65ebc8311f
4 changed files with 87 additions and 96 deletions

View File

@@ -1,15 +1,17 @@
{ {
"$data": { "$data": {
"v6.7.0": { "v6.7.0": {
"released_date": "2025/06/15", "released_date": "2025/06/23",
"feature": [ "feature": [
"zip 模块, 用于文件压缩与解压缩操作 (Ref to [Auto.js Pro](https://g.pro.autojs.org/)) (参阅 项目文档 > [Zip](https://docs.autojs6.com/#/zip))", "zip 模块, 用于文件压缩与解压缩操作 (Ref to [Auto.js Pro](https://g.pro.autojs.org/)) (参阅 项目文档 > [Zip](https://docs.autojs6.com/#/zip))",
"mediainfo 模块, 用于查看媒体文件的详细信息 (参阅 项目文档 > [媒体信息](https://docs.autojs6.com/#/mediainfo))" "mediainfo 模块, 用于查看媒体文件的详细信息 (参阅 项目文档 > [媒体信息](https://docs.autojs6.com/#/mediainfo))",
"UiObject#isShifted 方法, 用于检测控件位置变化"
], ],
"fix": [ "fix": [
"使用 XML 语法将 JavaScript 表达式作为属性值时, this 对象可能出现指向错误的问题", "使用 XML 语法将 JavaScript 表达式作为属性值时, this 对象可能出现指向错误的问题",
"调用 images.requestScreenCapture 时用户取消授权可能导致应用崩溃的问题", "调用 images.requestScreenCapture 时用户取消授权可能导致应用崩溃的问题",
"images 部分相关方法出现异常时 oneShot 标记功能失效的问题 _[`issue #372`](http://issues.autojs6.com/372)_", "images 部分相关方法出现异常时 oneShot 标记功能失效的问题 _[`issue #372`](http://issues.autojs6.com/372)_",
"Android 10 UiObject#child 方法可能出现 ArrayIndexOutOfBoundsException 异常的问题 _[`issue #416`](http://issues.autojs6.com/416)_",
"打包应用无法正常使用 Paddle OCR 与 Rapid OCR 功能的问题", "打包应用无法正常使用 Paddle OCR 与 Rapid OCR 功能的问题",
"版本历史页面部分系统因字体差别导致统计数据显示不完整的问题" "版本历史页面部分系统因字体差别导致统计数据显示不完整的问题"
], ],
@@ -19,6 +21,7 @@
"内置模块相关方法实参类型的异常消息增加类型摘要信息" "内置模块相关方法实参类型的异常消息增加类型摘要信息"
], ],
"dependency": [ "dependency": [
"附加 Androidx Core (KTX) 版本 1.16.0",
"升级 Gradle 版本 8.14 -> 8.14.2", "升级 Gradle 版本 8.14 -> 8.14.2",
"升级 Apache Commons 版本 3.16.0 -> 3.17.0", "升级 Apache Commons 版本 3.16.0 -> 3.17.0",
"升级 Retrofit2 Retrofit 版本 2.11.0 -> 2.12.0", "升级 Retrofit2 Retrofit 版本 2.11.0 -> 2.12.0",
@@ -31,7 +34,8 @@
"升级 Jsoup 版本 1.19.1 -> 1.20.1", "升级 Jsoup 版本 1.19.1 -> 1.20.1",
"升级 Junit Jupiter 版本 5.10.3 -> 5.13.0", "升级 Junit Jupiter 版本 5.10.3 -> 5.13.0",
"升级 Androidx AppCompat 版本 1.7.0 -> 1.7.1", "升级 Androidx AppCompat 版本 1.7.0 -> 1.7.1",
"升级 Material Progressbar 版本 1.4.2 -> 1.6.1" "升级 Material Progressbar 版本 1.4.2 -> 1.6.1",
"升级 Androidx Room 版本 2.7.1 -> 2.7.2"
] ]
}, },
"v6.6.4": { "v6.6.4": {

View File

@@ -27,6 +27,11 @@ val buildTypeRelease = "release"
val buildActionAssemble = "assemble" val buildActionAssemble = "assemble"
val templateName = "template" val templateName = "template"
val taskNames = gradle.startParameter.taskNames
val isAppAssembleTaskRequested = taskNames.any { it.contains(Regex("^(:?$flavorNameApp:)?$buildActionAssemble", IGNORE_CASE)) }
val isInrtAssembleTaskRequested = taskNames.any { it.contains(Regex("^(:?$flavorNameApp:)?$buildActionAssemble$flavorNameInrt", IGNORE_CASE)) }
val isInrtTaskRequested = taskNames.any { it.contains(flavorNameInrt, true) }
plugins { plugins {
id("com.android.application") id("com.android.application")
id("com.google.devtools.ksp") id("com.google.devtools.ksp")
@@ -40,6 +45,9 @@ dependencies /* Unclassified */ {
// Kotlin reflect // Kotlin reflect
implementation(kotlin("reflect")) implementation(kotlin("reflect"))
// Androidx Core
implementation("androidx.core:core-ktx:1.16.0")
// LeakCanary // LeakCanary
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14") debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
@@ -218,9 +226,9 @@ dependencies /* Unclassified */ {
implementation(files("$rootDir/libs/tiny-sign-0.9.jar")) implementation(files("$rootDir/libs/tiny-sign-0.9.jar"))
// Room // Room
implementation("androidx.room:room-runtime:2.7.1") implementation("androidx.room:room-runtime:2.7.2")
implementation("androidx.room:room-ktx:2.7.1") implementation("androidx.room:room-ktx:2.7.2")
ksp("androidx.room:room-compiler:2.7.1") ksp("androidx.room:room-compiler:2.7.2")
// ApkSig // ApkSig
// implementation("com.android.tools.build:apksig:8.7.3") // implementation("com.android.tools.build:apksig:8.7.3")
@@ -549,10 +557,10 @@ android {
} }
androidResources { androidResources {
if (gradle.startParameter.taskNames.any { it.contains(Regex("^(:?$flavorNameApp:)?$buildActionAssemble")) }) { if (isAppAssembleTaskRequested) {
ignoreAssetsPatterns.addAll(listOf(".idea", "declarations", "sample/declarations")) ignoreAssetsPatterns.addAll(listOf(".idea", "declarations", "sample/declarations"))
} }
if (gradle.startParameter.taskNames.any { it.contains(Regex("^(:?$flavorNameApp:)?$buildActionAssemble$flavorNameInrt", IGNORE_CASE)) }) { if (isInrtAssembleTaskRequested) {
// @Hint by SuperMonster003 on Oct 16, 2023. // @Hint by SuperMonster003 on Oct 16, 2023.
// ! Runtime assets will be copied from flavor "app" // ! Runtime assets will be copied from flavor "app"
// ! while building an apk on org.autojs.autojs.ui.project.BuildActivity. // ! while building an apk on org.autojs.autojs.ui.project.BuildActivity.
@@ -635,7 +643,7 @@ android {
"EmojiReference.txt", "EmojiReference.txt",
).let { resources.excludes.addAll(it) } ).let { resources.excludes.addAll(it) }
if (gradle.startParameter.taskNames.any { it.contains(flavorNameInrt, true) }) { if (isInrtTaskRequested) {
listOf( listOf(
"**/prob_emit.txt", // Jieba Analysis (zh-CN: 结巴分词) "**/prob_emit.txt", // Jieba Analysis (zh-CN: 结巴分词)
"**/dict-chinese-*.db.gzip", // Jieba Analysis (zh-CN: 结巴分词) "**/dict-chinese-*.db.gzip", // Jieba Analysis (zh-CN: 结巴分词)
@@ -742,9 +750,7 @@ android {
// Configures multiple APKs based on ABI. // Configures multiple APKs based on ABI.
abi { abi {
// Enables building multiple APKs per ABI. // Enables building multiple APKs per ABI.
isEnable = /* isNotAssembleInrt */ gradle.startParameter.taskNames.none { isEnable = !isInrtAssembleTaskRequested
it.contains(Regex("^(:?$flavorNameApp:)?$buildActionAssemble$flavorNameInrt", IGNORE_CASE))
}
// By default, all ABIs are included, so use reset() and include to specify that we only // By default, all ABIs are included, so use reset() and include to specify that we only
// want APKs for x86 and x86_64. // want APKs for x86 and x86_64.
// Resets the list of ABIs that Gradle should create APKs for to none. // Resets the list of ABIs that Gradle should create APKs for to none.
@@ -788,7 +794,7 @@ tasks {
rename { name -> rename { name ->
Utils.digestCRC32(file("${src}/$name")).let { digest -> Utils.digestCRC32(file("${src}/$name")).let { digest ->
name.replace("^(.+?)(\\.$ext)\$".toRegex(), "\$1-$digest\$2") name.replace(Regex("^(.+?)(\\.$ext)$"), "$1-$digest$2")
} }
} }

View File

@@ -13,9 +13,10 @@ import org.autojs.autojs.core.accessibility.AccessibilityNodeInfoHelper
import org.autojs.autojs.core.accessibility.UiSelector import org.autojs.autojs.core.accessibility.UiSelector
import org.autojs.autojs.extension.AnyExtensions.isJsNullish import org.autojs.autojs.extension.AnyExtensions.isJsNullish
import org.autojs.autojs.extension.AnyExtensions.isJsUndefined import org.autojs.autojs.extension.AnyExtensions.isJsUndefined
import org.autojs.autojs.extension.ArrayExtensions.toHashCode import org.autojs.autojs.extension.AnyExtensions.jsBrief
import org.autojs.autojs.extension.ArrayExtensions.toNativeArray import org.autojs.autojs.extension.ArrayExtensions.toNativeArray
import org.autojs.autojs.runtime.ScriptRuntime import org.autojs.autojs.runtime.ScriptRuntime
import org.autojs.autojs.runtime.exception.WrappedIllegalArgumentException
import org.autojs.autojs.util.DisplayUtils.toRoundIntX import org.autojs.autojs.util.DisplayUtils.toRoundIntX
import org.autojs.autojs.util.DisplayUtils.toRoundIntY import org.autojs.autojs.util.DisplayUtils.toRoundIntY
import org.autojs.autojs.util.RhinoUtils import org.autojs.autojs.util.RhinoUtils
@@ -24,9 +25,9 @@ import org.autojs.autojs.util.StringUtils.str
import org.autojs.autojs6.R import org.autojs.autojs6.R
import org.mozilla.javascript.BaseFunction import org.mozilla.javascript.BaseFunction
import org.mozilla.javascript.Context import org.mozilla.javascript.Context
import org.mozilla.javascript.Scriptable
import org.opencv.core.Point import org.opencv.core.Point
import org.opencv.core.Size import org.opencv.core.Size
import kotlin.math.abs
import kotlin.math.roundToInt import kotlin.math.roundToInt
/** /**
@@ -56,32 +57,45 @@ open class UiObject(
indexInParent: Int = -1, indexInParent: Int = -1,
) : this(info, null, depth, indexInParent) ) : this(info, null, depth, indexInParent)
open fun parent(): UiObject? = try { open fun parent(): UiObject? = runCatching {
super.getParent()?.let { node -> super.getParent()?.let { node ->
UiObject(node.unwrap(), depth - 1, node.parent?.run { UiObject(node.unwrap(), depth - 1, node.parent?.run {
// @Dubious by SuperMonster003 on May 26, 2022. // @Dubious by SuperMonster003 on May 26, 2022.
// ! May lead to poor performance? // ! May lead to poor performance?
// ! zh-CN: 可能导致性能不佳? // ! zh-CN: 可能导致性能不佳?
(0 until childCount).firstOrNull { getChild(it) == node } ?: -1 (0 until childCount).firstOrNull { child(it) == node } ?: -1
} ?: 0) } ?: 0)
} }
} catch (e: IllegalStateException) { }.onFailure {
// FIXME by Stardust on May 5, 2017. // FIXME by Stardust on May 5, 2017.
null.also { e.printStackTrace() } it.printStackTrace()
}.getOrNull()
fun parent(i: Int): UiObject? {
require(i >= 0) { "Index for UiObject#parent cannot be negative ($i)" }
return compass("p$i")
} }
fun parent(i: Int): UiObject? = if (i < 0) throw Exception("i < 0") else compass("p$i") open fun child(i: Int): UiObject? = when {
i >= childCount -> {
open fun child(i: Int): UiObject? { null
if (i < 0) {
return (i + childCount).takeIf { it >= 0 }?.let { child(it) }
} }
return try { i < 0 -> when (val targetIdx = i + childCount) {
super.getChild(i)?.run { UiObject(unwrap(), depth + 1, i) } in 0 until childCount -> child(targetIdx)
} catch (e: IllegalStateException) { else -> null
}
else -> runCatching {
// @Caution by SuperMonster003 on Jun 19, 2025.
// ! `super.getChild` may cause ArrayIndexOutOfBoundsException when `i` is out of range of [0..childCound).
// ! zh-CN: 当 `i` 超出 [0..childCount) 范围时可能导致 ArrayIndexOutOfBoundsException 异常.
// @Dubious by SuperMonster003 on Jun 19, 2025.
// ! It seems that the situation above only happens on API Level 29 (Android 10) [Q] ?
// ! zh-CN: 似乎上述情况只发生在 API Level 29 (Android 10) [Q] ?
super.getChild(i)?.let { UiObject(it.unwrap(), depth + 1, i) }
}.onFailure {
// FIXME by Stardust on May 5, 2017. // FIXME by Stardust on May 5, 2017.
null.also { e.printStackTrace() } it.printStackTrace()
} }.getOrNull()
} }
// @Deprecated by SuperMonster003 on Jul 20, 2023. // @Deprecated by SuperMonster003 on Jul 20, 2023.
@@ -413,10 +427,13 @@ open class UiObject(
false.also { e.printStackTrace() } false.also { e.printStackTrace() }
} }
override fun getChild(index: Int): AccessibilityNodeInfoCompat = override fun getChild(index: Int): AccessibilityNodeInfoCompat {
allocator?.getChild(this, index) ?: super.getChild(index) return allocator?.getChild(this, index) ?: super.getChild(index)
}
override fun getParent(): AccessibilityNodeInfoCompat? = allocator?.getParent(this) ?: super.getParent() override fun getParent(): AccessibilityNodeInfoCompat? {
return allocator?.getParent(this) ?: super.getParent()
}
override fun findAccessibilityNodeInfosByText(text: String): List<AccessibilityNodeInfoCompat> { override fun findAccessibilityNodeInfosByText(text: String): List<AccessibilityNodeInfoCompat> {
return allocator?.findAccessibilityNodeInfosByText(this, text) return allocator?.findAccessibilityNodeInfosByText(this, text)
@@ -492,49 +509,7 @@ open class UiObject(
return "[${UiObject::class.java.simpleName}] $simpledClassName ${summary()}" return "[${UiObject::class.java.simpleName}] $simpledClassName ${summary()}"
} }
override fun hashCode() = listOf( fun isSimilar(other: Any?): Boolean {
"${packageName()}",
"$parent",
"${id()}",
"${fullId()}",
"${idHex()}",
"${desc()}",
text(),
"${bounds()}",
"${className()}",
"${clickable()}",
"${longClickable()}",
"${scrollable()}",
"${indexInParent()}",
"${childCount()}",
"${depth()}",
"${checked()}",
"${enabled()}",
"${editable()}",
"${focusable()}",
"${checkable()}",
"${selected()}",
"$isDismissable",
"${visibleToUser()}",
"$isContextClickable",
"${focused()}",
"$isAccessibilityFocused",
"${rowCount()}",
"${columnCount()}",
"${row()}",
"${column()}",
"${rowSpan()}",
"${columnSpan()}",
"$drawingOrder",
"${actionNames()}",
"${isSingleton()}",
"${firstSibling()}",
"${lastSibling()}",
"${firstChild()}",
"${lastChild()}",
).toHashCode()
override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other !is UiObject) return false if (other !is UiObject) return false
@@ -543,6 +518,7 @@ open class UiObject(
val info = unwrap() ?: return false val info = unwrap() ?: return false
return info == other.unwrap() && return info == other.unwrap() &&
bounds() == other.bounds() &&
packageName() == other.packageName() && packageName() == other.packageName() &&
parent == other.parent && parent == other.parent &&
id() == other.id() && id() == other.id() &&
@@ -550,7 +526,6 @@ open class UiObject(
idHex() == other.idHex() && idHex() == other.idHex() &&
desc() == other.desc() && desc() == other.desc() &&
text() == other.text() && text() == other.text() &&
bounds() == other.bounds() &&
className() == other.className() && className() == other.className() &&
clickable() == other.clickable() && clickable() == other.clickable() &&
longClickable() == other.longClickable() && longClickable() == other.longClickable() &&
@@ -578,26 +553,32 @@ open class UiObject(
drawingOrder == other.drawingOrder && drawingOrder == other.drawingOrder &&
actionNames() == other.actionNames() && actionNames() == other.actionNames() &&
isSingleton() == other.isSingleton() && isSingleton() == other.isSingleton() &&
"${firstSibling()}" == "${other.firstSibling()}" && "${firstSibling() ?: ""}" == "${other.firstSibling()}" &&
"${lastSibling()}" == "${other.lastSibling()}" && "${lastSibling() ?: ""}" == "${other.lastSibling()}" &&
"${firstChild()}" == "${other.firstChild()}" && "${firstChild() ?: ""}" == "${other.firstChild()}" &&
"${lastChild()}" == "${other.lastChild()}" "${lastChild() ?: ""}" == "${other.lastChild()}"
} }
fun isSimilar(other: Any?): Boolean { fun isShifted(other: UiObject) = bounds() == other.bounds()
if (this === other) return true
if (other == null) return false fun isShifted(other: UiObject, tolerance: Double): Boolean {
when (other) { val a = bounds()
is Scriptable -> { val b = other.bounds()
val thisHashCode = hashCode() return when {
val otherHashCode = RhinoUtils.hashCodeOfScriptable(other) ?: return false tolerance < 0 -> throw WrappedIllegalArgumentException(
return thisHashCode == otherHashCode "Argument \"tolerance\" ${tolerance.jsBrief()} for UiObject#isShifted must be greater than or equal to 0",
)
tolerance < 1 -> (width() to height()).let { (w, h) ->
return@let abs(a.left - b.left) > tolerance * w
|| abs(a.top - b.top) > tolerance * h
|| abs(a.right - b.right) > tolerance * w
|| abs(a.bottom - b.bottom) > tolerance * h
} }
else -> { else -> let {
return when { return@let abs(a.left - b.left) > tolerance
other.javaClass != UiObject::class.java -> false || abs(a.top - b.top) > tolerance
else -> hashCode() == other.hashCode() || abs(a.right - b.right) > tolerance
} || abs(a.bottom - b.bottom) > tolerance
} }
} }
} }

View File

@@ -1,5 +1,5 @@
#Sun Jun 15 14:16:44 CST 2025 #Mon Jun 23 16:34:06 CST 2025
BUILD_TIME=1749968204434 BUILD_TIME=1750667646268
COMPILE_SDK_VERSION=35 COMPILE_SDK_VERSION=35
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
@@ -19,6 +19,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=35 TARGET_SDK_VERSION=35
TARGET_SDK_VERSION_INRT=29 TARGET_SDK_VERSION_INRT=29
VERSION_BUILD=3293 VERSION_BUILD=3302
VERSION_NAME=6.7.0 Alpha2 VERSION_NAME=6.7.0 Alpha2
VSCODE_EXT_REQUIRED_VERSION=1.0.8 VSCODE_EXT_REQUIRED_VERSION=1.0.8