美文网首页安卓开发Android开发经验谈安卓开发
安卓辅助功能(无障碍)AccessibilityService实

安卓辅助功能(无障碍)AccessibilityService实

作者: 蓝不蓝编程 | 来源:发表于2019-03-26 09:54 被阅读2次

    简要介绍

    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/cxyzy1/accessibilityDemo

    安卓开发技术分享: https://www.jianshu.com/p/442339952f26
    更多技术总结好文,请关注:「程序园中猿」

    相关文章

      网友评论

        本文标题:安卓辅助功能(无障碍)AccessibilityService实

        本文链接:https://www.haomeiwen.com/subject/njvwvqtx.html