当前位置: 首页 > article >正文

Android使用辅助服务AccessibilityService实现自动化任务

Android 辅助服务(AccessibilityService)旨在帮助具有视觉、身体或年龄相关限制的用户更轻松地使用 Android 设备和应用。通过辅助服务,可以将一些人工操作自动化,从而解放用户的双手。
因此我们可以使用它来实现一些自动化任务,比如抢红包,测试,自动玩游戏(外挂?)。author: https://blog.csdn.net/keeng2008

  1. 编写辅助服务MyAccessibilityService,继承AccessibilityService
class MyAccessibilityService : AccessibilityService() {
    override fun onServiceConnected() {
        super.onServiceConnected()
        Log.i(TAG, "onServiceConnected")
    }

    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        Log.i(TAG, "onAccessibilityEvent: $event")

        val packageName = event.packageName
        /* 处理界面变动事件 */
    }

    override fun onInterrupt() {
        Log.i(TAG, "onInterrupt")
    }
}

当辅助服务开启后,Service会开始运行,界面上的元素变动,窗口变动会回调onAccessibilityEvent。在其中可以实现模拟点击。

  • 这个服务不能主动开启,需要用户在设置-无障碍中手动开启。
  1. 把MyAccessibilityService添加到Manifest
    <service
        android:name=".accessbi.MyAccessibilityService"
        android:exported="true"
        android:label="控制辅助服务Demo"
        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/my_accessibility" />
    </service>

在res/xml中添加文件 my_accessibility.xml

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:accessibilityFlags="flagReportViewIds"
    android:canRetrieveWindowContent="true"
    android:canPerformGestures="true"
    android:notificationTimeout="100"/>

这样就已经注册好辅助服务,但是需要用户手动到“设置”中的“无障碍”,去启动无障碍功能。

  1. 引导用户直接跳转到无障碍中启动
  • 3.1 判断当前是否已经启动
    fun isAccessibilitySettingsOn(context: Context): Boolean {
        val accessibilityEnabled = Settings.Secure.getInt(
            context.contentResolver,
            Settings.Secure.ACCESSIBILITY_ENABLED
        )
        if (accessibilityEnabled != 1) return false
        val value = Settings.Secure.getString(
            context.contentResolver,
            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
        ) ?: return false

        return value.contains(context.packageName) &&
                value.contains(MyAccessibilityService::class.java.simpleName)
    }
  • 3.2 跳转到无障碍设置界面
    发现未开启服务后,引导用户跳转到设置界面,手动打开。
    fun openAccessibilitySetting(context: Context) {
        val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
        context.startActivity(intent)
    }
  • 3.3 开启后能否保持状态
    经测试,如果进程被主动杀死重启,那么开关就被关闭了。
    其它情况下即使关机重启,更新APP,它的状态能保持-
  1. AccessibilityService常用方法
  • 获取当前的APP: val packageName = event.packageName
  • 获取当前的Activity:
    fun getActivityName(context: Context, event: AccessibilityEvent): String {
        val componentName = ComponentName(event.packageName.toString(), event.className.toString())
        try {
            var activityName = context.packageManager.getActivityInfo(componentName, 0).toString()
            activityName =
                activityName.substring(activityName.indexOf(" "), activityName.indexOf("}"))
            return activityName
        } catch (e: Exception) {
        }
        return ""
    }

不是每个event都能获取到Activity的,比如一些Toast,Dialog的事件,拿不到Activity; 所以可以把最后的Activity保存起来,随时读取。

  • 通过文本获取当前界面的View
fun findViewByText(svr: AccessibilityAble, text: String): AccessibilityNodeInfo? {
    val nodes = svr.getRootNode()?.findAccessibilityNodeInfosByText(text)
    if (nodes != null) {
        for (node in nodes) {
            if (node.text == text) {
                return node
            }
        }
    }
    return nodes?.firstOrNull()
}

AccessibilityAble: 主是要封装了对 getService().rootInActiveWindow 的引用, 这样可以从当前的全局根结点开始寻找。

  • 通过viewId获取当前界面的View
    格式示例 :“APP包名:id/operator_normal_iv”
fun findViewById(svr: AccessibilityAble, viewId: String): AccessibilityNodeInfo? {
    val nodes = svr.getRootNode()?.findAccessibilityNodeInfosByViewId(viewId)
    return nodes?.firstOrNull()
}
  • 模拟View点击
val btn: AccessibilityNodeInfo? = AccessibilityUtils.findViewByText(svr, "下次再说")
if (btn != null && btn.isClickable && btn.isVisibleToUser) {
    btn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
  • 通过手势长按指定坐标
fun performLongPress(svr: AccessibilityAble, point: PointF, durationMill: Long) {
    // 创建手势描述
    val gestureBuilder = GestureDescription.Builder()
    // 移动到指定坐标
    val path = Path().apply {
        moveTo(point.x, point.y)
    }

    val duration: Long = durationMill // 持续时间 毫秒
    val strokeDescription = GestureDescription.StrokeDescription(path, 0, duration)

    // 添加手势到描述
    gestureBuilder.addStroke(strokeDescription)
    val gesture = gestureBuilder.build()

    // 执行手势
    val cb = object : AccessibilityService.GestureResultCallback() {
        override fun onCompleted(gestureDescription: GestureDescription?) {
            Log.i(TAG, "onCompleted, $gestureDescription")
        }

        override fun onCancelled(gestureDescription: GestureDescription?) {
            Log.i(TAG, "onCancelled, $gestureDescription")
        }
    }
    val res = svr.getService().dispatchGesture(gesture, cb, null)
    Log.i(TAG, "dispatchGesture, res: $res")
}
  • 点击Node所在位置:主要是有些文本不可点击,直接模拟点击该位置
fun performClick(svr: AccessibilityAble, node: AccessibilityNodeInfo) {
    val rect = Rect()
    node.getBoundsInScreen(rect)
    val centerP = PointF(rect.exactCenterX(), rect.exactCenterY())
    Log.i(TAG, "Start Simple Click at $centerP")
    performLongPress(
        svr,
        centerP,
        100L
    )
}

author: https://blog.csdn.net/keeng2008 有了这神器,能实现些什么新奇玩法呢?抢红包,自动打卡,继续探索中。


http://www.kler.cn/a/453080.html

相关文章:

  • FPGA的DMA应用——pcileech
  • C语言基础:指针(数组指针与指针数组)
  • 如何在 Scrum 管理中化解团队冲突?
  • 数据结构之栈,队列,树
  • PetaLinux 内核输出信息的获取方式
  • GPUStack v0.4.1 单节点与多节点安装与部署指南 Docker PowerShell
  • 力扣11. 盛最多水的容器
  • 【Pytorch实用教程】PyTorch 自带的数据集全面解读
  • 消息队列(一)消息队列的工作流程
  • 地理数据库Telepg面试内容整理-基础技术栈
  • 重温设计模式----装饰模式
  • SSE(Server-Sent Events)返回n ,前端接收数据时被错误的截断【如何避免SSE消息中的换行符或回车符被解释为事件消息的结束】
  • Halcon 的标定
  • 如何识别钓鱼邮件和诈骗网站?(附网络安全意识培训PPT资料)
  • hhdb客户端介绍(54)
  • 数据结构基本认识与必要知识点准备工作
  • 大型语言模型(LLMs)演化树 Large Language Models
  • Wux weapp 组件库的 bug—— wux-picker选择器组件无法正确初始化到选定的value
  • 基于TP5框架的家具购物小程序的设计与实现【附源码、文档】
  • HTTP,续~
  • 记一次Vue3中使用vue-awesome-swiper遇到的坑
  • vscode写python,遇到问题:ModuleNotFoundError: No module named ‘pillow‘(已解决 避坑)
  • 前端案例---自定义鼠标右键菜单
  • HTML 新手易犯的标签属性设置错误
  • sentinel学习笔记6-限流降级(上)
  • 创建线程的四种方式