简要介绍

AccessibilityService是安卓平台上提供的无障碍服务,用于帮助残障人士使用手机,不过通过此功能可以完成很多事情.可以进行模拟界面操作,如点击界面上某个按钮等.

使用详细步骤

  1. 增加service定义
    在onAccessibilityEvent函数中进行相应操作(安卓界面有变化时,都会调用此函数)
class MyAccessibilityService : AccessibilityService() {
private val TAG = MyAccessibilityService::class.java.simpleName
private var mContext: Context? = null

override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
mContext = applicationContext
AccessibilityOperator.getInstance().init(this)
}

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
return Service.START_STICKY
}

override fun onAccessibilityEvent(event: AccessibilityEvent) {
AccessibilityOperator.getInstance().updateEvent(event)
val packageName = AccessibilityOperator.getInstance().rootNodeInfo?.packageName?.toString()
// pasteToEditTextContent(packageName)
var accessibilityService = AccessibilityOperator.getInstance()
//按下返回键
// accessibilityService.performGlobalAction(GLOBAL_ACTION_BACK)
//向下拉出状态栏
// accessibilityService.performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS)
//向下拉出状态栏并显示出所有的快捷操作按钮
// accessibilityService.performGlobalAction(GLOBAL_ACTION_QUICK_SETTINGS)
//按下HOME键
// accessibilityService.performGlobalAction(GLOBAL_ACTION_HOME)
//显示最近任务
// accessibilityService.performGlobalAction(GLOBAL_ACTION_RECENTS)
//长按电源键
// accessibilityService.performGlobalAction(GLOBAL_ACTION_POWER_DIALOG)
//分屏
// accessibilityService.performGlobalAction(GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN)
//锁屏(安卓9.0适用)
// accessibilityService.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN)
//截屏(安卓9.0适用)
// accessibilityService.performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT)
//打开快速设置
accessibilityService.performGlobalAction(GLOBAL_ACTION_QUICK_SETTINGS)
}

/**
* 修改EditText输入框内容。
* 下面样例修改了QQ搜索输入框内容。
*/
private fun changeEditTextContent(packageName: String?) {
getNodeToOperate(packageName)?.let {
val arguments = Bundle()
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "被无障碍服务修改啦")
it.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
}
}



/**
* 读取剪贴板内容,粘贴到EditText输入框。
* 下面样例修改了QQ搜索输入框内容。
*/
private fun pasteToEditTextContent(packageName: String?) {
getNodeToOperate(packageName)?.let {
it.performAction(AccessibilityNodeInfo.FOCUS_INPUT)
it.performAction(AccessibilityNodeInfo.ACTION_PASTE)
it.recycle()
}
}

private fun getNodeToOperate(packageName: String?): AccessibilityNodeInfo? {
if (packageName != null && packageName == "com.tencent.mobileqq") {
val nodes = AccessibilityOperator.getInstance().findNodesById("com.tencent.mobileqq:id/et_search_keyword")
if (nodes != null && nodes.isNotEmpty()) {
return nodes[0]
}
}
return null
}

override fun onInterrupt() {
}

}
  1. 在Manifest文件中增加service定义
<application>
......
<service
android:name=".MyAccessibilityService"
android:enabled="true"
android:exported="true"
android:label="@string/accessibility_service_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
</application>
  1. AccessibilityOperator工具类
class AccessibilityOperator private constructor() {
private var mAccessibilityEvent: AccessibilityEvent? = null
private var accessibilityService: AccessibilityService? = null

val rootNodeInfo: AccessibilityNodeInfo?
get() {
var nodeInfo: AccessibilityNodeInfo? = null
accessibilityService?.let {
nodeInfo = accessibilityService!!.rootInActiveWindow
}

if (nodeInfo == null && mAccessibilityEvent != null) {
nodeInfo = mAccessibilityEvent!!.source
}

return nodeInfo
}

fun init(service: AccessibilityService) {
accessibilityService = service
}

fun updateEvent(event: AccessibilityEvent) {
mAccessibilityEvent = event
}

/**
* 根据Text搜索所有符合条件的节点, 模糊搜索方式
*/
fun findNodesByText(text: String): List<AccessibilityNodeInfo>? {
val nodeInfo = rootNodeInfo
return nodeInfo?.findAccessibilityNodeInfosByText(text)
}

/**
* 根据View的ID搜索符合条件的节点,精确搜索方式;
* 这个只适用于自己写的界面,因为ID可能重复
*
* @param viewId
*/
fun findNodesById(viewId: String): List<AccessibilityNodeInfo>? {
val nodeInfo = rootNodeInfo
return nodeInfo?.findAccessibilityNodeInfosByViewId(viewId)
}

fun clickByText(text: String): Boolean {
return performClick(findNodesByText(text))
}

fun clickParentByText(text: String, depth: Int): Boolean {
return this.performClick(this.findParentNodesByText(text, depth))
}

fun clickParentById(viewId: String, depth: Int): Boolean {
return this.performClick(this.findParentNodesById(viewId, depth))
}

fun findParentNodesByText(text: String, depth: Int): List<AccessibilityNodeInfo> {
val rootNodeInfo = this.rootNodeInfo
val resultNodeList = mutableListOf<AccessibilityNodeInfo>()
if (rootNodeInfo != null) {
val nodeList = findAccessibilityNodeInfosByText(rootNodeInfo, text)
val iterator = nodeList.iterator()

while (iterator.hasNext()) {
val accessibilityNodeInfo = iterator.next() as AccessibilityNodeInfo
resultNodeList.add(getParentNode(accessibilityNodeInfo, depth))
}
}

return resultNodeList
}

fun findParentNodesById(viewId: String, depth: Int): List<AccessibilityNodeInfo> {
val rootNodeInfo = this.rootNodeInfo
val resultNodeList = mutableListOf<AccessibilityNodeInfo>()
if (rootNodeInfo != null) {
val nodeList = rootNodeInfo.findAccessibilityNodeInfosByViewId(viewId)
val iterator = nodeList.iterator()

while (iterator.hasNext()) {
val accessibilityNodeInfo = iterator.next() as AccessibilityNodeInfo
resultNodeList.add(this.getParentNode(accessibilityNodeInfo, depth))
}
}

return resultNodeList
}

private fun findAccessibilityNodeInfosByText(node: AccessibilityNodeInfo?, text: String?): List<AccessibilityNodeInfo> {
val resultNodeList = mutableListOf<AccessibilityNodeInfo>()
if (node != null && text != null) {
val nodeList = node.findAccessibilityNodeInfosByText(text)
if (nodeList != null && !nodeList.isEmpty()) {
val iterator = nodeList.iterator()
while (iterator.hasNext()) {
val nodeInList = iterator.next() as AccessibilityNodeInfo
if (TextUtils.equals(nodeInList.text, text)) {
resultNodeList.add(nodeInList)
}
}
}

return resultNodeList
} else {
return resultNodeList
}
}

private fun getParentNode(nodeInfo: AccessibilityNodeInfo, depth: Int): AccessibilityNodeInfo {
var resultNodeInfo = nodeInfo

for (i in 0 until depth) {
val parentNode = resultNodeInfo.parent
resultNodeInfo = parentNode
}

return resultNodeInfo
}

/**
* 根据View的ID搜索符合条件的节点,精确搜索方式;
* 这个只适用于自己写的界面,因为ID可能重复
*
* @param viewId
* @return 是否点击成功
*/
fun clickById(viewId: String): Boolean {
return performClick(findNodesById(viewId))
}

private fun performClick(nodeInfoList: List<AccessibilityNodeInfo>?): Boolean {
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
var node: AccessibilityNodeInfo
for (i in nodeInfoList.indices) {
node = nodeInfoList[i]
// 进行模拟点击
if (node.isEnabled) {
return node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
}
return false
}

fun clickBackKey(): Boolean {
return performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
}

fun performGlobalAction(action: Int): Boolean {
return accessibilityService!!.performGlobalAction(action)
}

private fun getNodeInfo(nodeInfo: AccessibilityNodeInfo?): String {
var result = ""
if (nodeInfo != null) {
result = nodeInfo.className.toString() + ";text:" + nodeInfo.text + ";id:" + nodeInfo.viewIdResourceName + ";"
}
return result
}

fun clickTextParent(text: String): Boolean {
val nodeInfo = rootNodeInfo
return nodeInfo?.let { clickTextParent(it, text) } ?: false
}

private fun clickTextParent(rootInfo: AccessibilityNodeInfo?, text: String): Boolean {
if (rootInfo != null && !TextUtils.isEmpty(rootInfo.className)) {
if ("android.widget.TextView" == rootInfo.className.toString()) {
if (!TextUtils.isEmpty(rootInfo.text) && rootInfo.text.toString().startsWith(text)) {
val result = performClick(rootInfo.parent)
Log.v(TAG, rootInfo.parent.className.toString() + ":result=" + result)

return result
}
}
for (i in 0 until rootInfo.childCount) {
val result = clickTextParent(rootInfo.getChild(i), text)
if (result) {
return result
}
}
return false
}
return false
}

private fun performClick(targetInfo: AccessibilityNodeInfo): Boolean {
return targetInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}

companion object {
private val TAG = "AccessibilityOperator"
val instance = AccessibilityOperator()
}
}

Demo源代码

https://gitee.com/hspbc/accessibilityDemo

关于我

厦门大学计算机专业 | 前华为工程师

分享编程技术,没啥深度,但看得懂,适合初学者。

Java | 安卓 | 前端 | 小程序 | 鸿蒙

公众号:花生皮编程

​​​​​​安卓辅助功能(无障碍)AccessibilityService实战介绍_android