安卓辅助功能(无障碍)AccessibilityService实战介绍
发布日期:2021-06-29 18:33:29 浏览次数:2 分类:技术文章

本文共 8759 字,大约阅读时间需要 29 分钟。

简要介绍

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定义
......
  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
? { val nodeInfo = rootNodeInfo return nodeInfo?.findAccessibilityNodeInfosByText(text) } /** * 根据View的ID搜索符合条件的节点,精确搜索方式; * 这个只适用于自己写的界面,因为ID可能重复 * * @param viewId */ fun findNodesById(viewId: String): List
? { 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
{ val rootNodeInfo = this.rootNodeInfo val resultNodeList = mutableListOf
() 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
{ val rootNodeInfo = this.rootNodeInfo val resultNodeList = mutableListOf
() 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
{ val resultNodeList = mutableListOf
() 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
?): 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://cxyxy.blog.csdn.net/article/details/88813268 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:快速批量解决Kotlin包名和文件所在路径不一致问题
下一篇:安卓多渠道打包之指定渠道打包

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月13日 14时55分39秒