业余时间了解了Android无障碍服务的一些有趣功能,比如微信自动抢红包、应用宝的一键安装功能等。大致原理是监听手机窗体内容变化,拿到对应的View,进行点击、长按等Touch操作,下面我们就借助 AccessibilityService 这个服务类实现模拟点击功能。 


效果

Android view无障碍 android无障碍服务 模拟点击_Android view无障碍

一、创建 MyAccessibilityService

1、继承AccessibilityService,重写 onAccessibilityEvent

class MyAccessibilityService : AccessibilityService() {

    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        //获取指定包名应用
        val packageName = event.packageName
        //只使用界面变化的监听,避免点击事件监听进入死循环
        if(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED != event.eventType){
            return
        }
        if ("com.yufs.accessibility" == packageName) {
            LogUtils.e("Thread:${Thread.currentThread().name},event:${event}")
            //找到对应node,开始点击
            val nodeInfo = AsUtils.findNodeInfo(
                this,
                "com.yufs.accessibility:id/btn_click_node",
                "节点模拟点击",
                ""
            )
            nodeInfo?.let {
                thread {
                    LogUtils.e("找到节点,三秒后执行点击事件")
                    Thread.sleep(3000)
                    AsUtils.performClickNodeInfo(it)
                }
            }


        }
    }

    override fun onInterrupt() {
        LogUtils.e("onInterrupt==>")
    }

    /**
     * 服务连接成功
     */
    override fun onServiceConnected() {
        super.onServiceConnected()
        thread {
            //便于设置完成后返回来看到显示效果
            Thread.sleep(5000)
            LogUtils.e("坐标点击:500,515")
            AsUtils.click(this, 507f, 512f)
        }
    }
}

 onAccessibilityEvent 是最重要的方法,所有界面的操作事件都会回调此方法,不限于当前应用,可以监听手机上其他应用的界面。例如界面变化时,我们打印AccessibilityEvent的信息,可以看到基本的包信息、类名等

 

Android view无障碍 android无障碍服务 模拟点击_Android view无障碍_02

2、 注册服务

<service
            android:name=".service.MyAccessibilityService"
            android:description="@string/description_in_xml"
            android:exported="true"
            android:enabled="true"
            android:label="@string/app_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_config" />
        </service>

3、服务的配置文件 accessibility_config.xml

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:canPerformGestures="true"
    android:canRetrieveWindowContent="true"
    android:canRequestTouchExplorationMode="true"
    android:description="@string/description_in_xml"
    android:settingsActivity="com.yufs.accessibility.MainActivity"
    android:notificationTimeout="100" />

<!--
accessibilityEventTypes:可以接收的事件类型,例如界面变化、点击等。typeAllMask 接收所有,根据实际情况选择合适的类型,减少电量的消耗
accessibilityFeedbackType:事件的反馈类型,暂时不知道用作啥
canPerformGestures:是否支持手势
canRetrieveWindowContent:是否允许读取窗口中的内容
canRequestTouchExplorationMode:在这种模式下,被触摸的项目会被大声说出,用户界面可以被激活通过手势探索
description:服务的描述文字
settingsActivity:开启服务界面显示一个设置按钮,可以返回应用指定界面
notificationTimeout:事件的发送间隔事件,单位毫秒
-->

二、实现点击

1、坐标点击:我们可以打开手机的指针位置,查看按钮的坐标

Android view无障碍 android无障碍服务 模拟点击_xml_03

2、坐标点击代码 

/**
     * 坐标模拟点击:最低api24,即要求Android7.0以上
     */
    fun click(accessibilityService: AccessibilityService, x: Float, y: Float) {
        val builder = GestureDescription.Builder()
        val path = Path()
        path.moveTo(x, y)
        path.lineTo(x, y)
        builder.addStroke(GestureDescription.StrokeDescription(path, 0, 1))
        val gesture = builder.build()
        accessibilityService.dispatchGesture(
            gesture,
            object : AccessibilityService.GestureResultCallback() {
                override fun onCancelled(gestureDescription: GestureDescription) {
                    super.onCancelled(gestureDescription)
                }

                override fun onCompleted(gestureDescription: GestureDescription) {
                    super.onCompleted(gestureDescription)
                }
            },
            null
        )
    }

3、根据id查找View 

获取应用中view的id可以通过Android SDK中monitor.bat查看,双击运行

Android view无障碍 android无障碍服务 模拟点击_android_04

  

Android view无障碍 android无障碍服务 模拟点击_xml_05

 

 拿到resouce id 就可以查找view

/**
     * 查找节点信息
     *
     * @param id
     * @param text
     * @param contentDescription
     * @return null表示未找到
     */
    fun findNodeInfo(
        service: AccessibilityService,
        id: String,
        text: String,
        contentDescription: String
    ): AccessibilityNodeInfo? {
        if (TextUtils.isEmpty(text) && TextUtils.isEmpty(contentDescription)) {
            return null
        }
        val nodeInfo = service.rootInActiveWindow
        if (nodeInfo != null) {
            val list = nodeInfo.findAccessibilityNodeInfosByViewId(id)
            for (n in list) {
                val nodeInfoText =
                    if (TextUtils.isEmpty(n.text)) "" else n.text
                        .toString()
                val nodeContentDescription =
                    if (TextUtils.isEmpty(n.contentDescription)) "" else n.contentDescription
                        .toString()
                if (TextUtils.isEmpty(text)) {
                    if (contentDescription == nodeContentDescription) {
                        return n
                    }
                } else {
                    if (text == nodeInfoText) {
                        return n
                    }
                }
            }
        }
        return null
    }

4、View节点点击 

/**
     * 点击节点
     * @return true表示点击成功
     */
    fun performClickNodeInfo(nodeInfo: AccessibilityNodeInfo?): Boolean {
        if (nodeInfo != null) {
            if (nodeInfo.isClickable) {
                nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)
                return true
            } else {
                val parent = nodeInfo.parent
                if (parent != null) {
                    val isParentClickSuccess = performClickNodeInfo(parent)
                    parent.recycle()
                    return isParentClickSuccess
                }
            }
        }
        return false
    }

 三、源代码

代码下载

四、总结

通过学习无障碍服务可以实现很多重复性操作,方便用户解决更多的问题,