阅读本文大约需要1分钟。
点击?小卡片,回复 “合集” 获取系统性的学习笔记和测试开发技能图谱
背景
在之前的一篇文章《移动端UI自动化过程中的难点及应对策略》中,我们提到在Android自动化测试执行过程中经常会遇到一些非预期的系统弹框,我们可以通过无障碍服务来实现智能点击处理,但是通常这个服务只能手动到设置中开启,今天就跟大家分享一下如何实现一个自定义的无障碍服务以及如何自动化的开启它。
实现自定义的无障碍服务
自定义一个服务继承自AccessibilityService
代码语言:javascript复制package com.android.jarvis.accessibility
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
import android.util.Log
import android.view.KeyEvent
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
class JarvisAccessibilityService : AccessibilityService() {
public override fun onServiceConnected() {
Log.i(TAG, "onServiceConnected: ")
val accessibilityServiceInfo = AccessibilityServiceInfo()
accessibilityServiceInfo.packageNames = null // 监听所有应用
accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK //监听哪些行为
accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_ALL_MASK //反馈
accessibilityServiceInfo.notificationTimeout = 200
serviceInfo = accessibilityServiceInfo
}
override fun onAccessibilityEvent(event: AccessibilityEvent) {
traverseNode(event.source)
processAccessibilityEvent(event)
}
private fun processAccessibilityEvent(event: AccessibilityEvent) {
processBlockingNotification(event)
if (rootInActiveWindow == null) {
Log.i(TAG, "AccessibilityNodeInfo = null")
return
}
traverseNode(rootInActiveWindow)
}
private fun processBlockingNotification(event: AccessibilityEvent) {
val node = event.source
if (node != null) {
if (findblockingUI("是否允许 USB 调试?", node)) {
findAndPerformCheck("始终允许使用这台计算机进行调试", node)
findAndPerformAction("确定", node)
}
if (findblockingUI("是否允许USB调试?", node)) {
findAndPerformCheck("始终允许使用这台计算机进行调试", node)
findAndPerformAction("确定", node)
}
if (findblockingUI("允许USB调试吗?", node)) {
findAndPerformCheck("一律允许使用这台计算机进行调试", node)
findAndPerformAction("确定", node)
}
if (findblockingUI("允许 USB 调试吗?", node)) {
findAndPerformCheck("一律允许使用这台计算机进行调试", node)
findAndPerformAction("允许", node)
}
if (findblockingUI("允许 USB 调试吗?", node)) {
findAndPerformCheck("一律允许使用这台计算机进行调试", node)
findAndPerformAction("确定", node)
}
}
}
private fun findblockingUI(text: String, source: AccessibilityNodeInfo): Boolean {
val nodes = source.findAccessibilityNodeInfosByText(text)
if (nodes == null || nodes.isEmpty()) {
return false
}
Log.d(TAG, "findblockingUI $text")
return true
}
public override fun onKeyEvent(event: KeyEvent): Boolean {
return true
}
override fun onInterrupt() {
Log.e(TAG, "服务被Interrupt")
}
private fun traverseNode(node: AccessibilityNodeInfo?) {
if (node != null) {
val count = node.childCount
if (count > 0) {
for (i in 0 until count) {
traverseNode(node.getChild(i))
}
return
}
val clickable = node.isClickable
val text = node.text
val pkgName = node.packageName
if (!"com.miui.home".contentEquals(pkgName)) {
Log.i(
TAG,
"pkg:" pkgName as Any " Node:" text as Any " clickable:" clickable
)
}
}
}
private fun findAndPerformAction(text: String, source: AccessibilityNodeInfo?): Int {
if (source == null) {
return 0
}
val nodes = source.findAccessibilityNodeInfosByText(text)
var count = 0
if (nodes != null && !nodes.isEmpty()) {
for (i in nodes.indices) {
if (performActionClick(nodes[i], text)) {
count
}
}
}
return count
}
private fun performActionClick(node: AccessibilityNodeInfo?, text: String): Boolean {
if (node == null) {
return false
}
if (!isButton(node) && !isTextView(node) && !isView(node)) {
return false
}
node.performAction(16)
return true
}
private fun findAndPerformCheck(text: String, source: AccessibilityNodeInfo?) {
if (source != null) {
val nodes = source.findAccessibilityNodeInfosByText(text)
if (nodes != null && !nodes.isEmpty()) {
for (i in nodes.indices) {
Log.d(TAG, "performCheck $text")
performActionCheck(nodes[i])
}
}
}
}
private fun performActionCheck(node: AccessibilityNodeInfo?) {
if (node != null && isCheckBox(node) && !node.isChecked) {
node.performAction(16)
}
}
private fun isButton(node: AccessibilityNodeInfo): Boolean {
return node.className == "android.widget.Button" || node.className == "amigo.widget.AmigoButton"
}
private fun isTextView(node: AccessibilityNodeInfo): Boolean {
return node.className == "android.widget.TextView"
}
private fun isView(node: AccessibilityNodeInfo): Boolean {
return node.className == "android.widget.View"
}
private fun isCheckBox(node: AccessibilityNodeInfo): Boolean {
return node.className == "android.widget.CheckBox"
}
private fun isCheckedTextView(node: AccessibilityNodeInfo): Boolean {
return node.className == "android.widget.CheckedTextView"
}
companion object {
private const val TAG = "JarvisAccessibility"
}
}
配置
在res/xml目录下新建accessibility_service_config.xml文件,如下:
代码语言:javascript复制<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_description"
android:accessibilityEventTypes="typeViewClicked|typeViewLongClicked|typeViewSelected|typeViewFocused|typeViewTextChanged|typeWindowStateChanged|typeNotificationStateChanged|typeViewHoverEnter|typeViewHoverExit|typeTouchExplorationGestureStart|typeTouchExplorationGestureEnd|typeWindowContentChanged|typeViewScrolled|typeViewTextSelectionChanged|typeAllMask"
android:accessibilityFlags="flagDefault"
android:accessibilityFeedbackType="feedbackGeneric"
android:canRetrieveWindowContent="true"
android:notificationTimeout="200" />
在AndroidManifest.xml中注册服务
代码语言:javascript复制 <service
android:name=".accessibility.JarvisAccessibilityService"
android:label="智能辅助服务"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter android:priority="2147483647">
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
自动开启无障碍服务
可以通过执行下面的命令就可以自动开启指定的无障碍服务:
代码语言:javascript复制adb shell content call --uri content://settings/secure --method PUT_secure --arg enabled_accessibility_services --extra _user:i:0 --extra value:s:com.android.jarvis/com.android.jarvis.accessibility.JarvisAccessibilityService
adb shell content call --uri content://settings/secure --method PUT_secure --arg accessibility_enabled --extra _user:i:0 --extra value:s:1
adb shell settings put secure enabled_accessibility_services com.android.jarvis/com.android.jarvis.accessibility.JarvisAccessibilityService
adb shell settings put secure accessibility_enabled 1