从零开始无障碍服务


文章目录

  • 从零开始无障碍服务
  • 前言
  • 一、新建项目-选择Empty Activity
  • 二、新建BaseService类和AccessService类
    • 1. BaseService类
    • 2. AccessService类
  • 三、修改AndroidManifest.xml
    • 1. 添加AccessibilityService配置
    • 2. 添加allocation.xml配置文件
  • 四、修改MainActivity类
  • 五、实现1+2+3=6
    • 1. 获取APP包名
    • 2. 查找并点击1、2、3、+、=
    • 3. 运行效果

前言

以前安卓root权限很容易获取的时候,可以写一些日常工作批处理的助手工具,而现在的安卓手机权限管理越来越严,root权限越来越难获取,于是就开始使用安卓自带的无障碍服务来实现自己的操作了,虽然也有限制,但是总体的操作也是很符合预期。本文将从零编写一个无障碍服务的工具,不适合安卓初学者,请小白自行百度详细。


一、新建项目-选择Empty Activity

Minimun SDK我直接选择API 26,省去一些杂七杂八的问题,读者有更低版本兼容需求的话,请自行填坑。

二、新建BaseService类和AccessService类

1. BaseService类

BaseService继承AccessibilityService,里面封装了一些常用的查找、定位、手势方法。

代码如下:

import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.GestureDescription;
import android.content.Context;
import android.content.Intent;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;import java.util.ArrayList;
import java.util.List;
import java.util.Stack;/** 基类,封装了查找定位、点击、手势方法* */
public class BaseService extends AccessibilityService {private AccessibilityManager mAccessibilityManager;private Context mContext;private static BaseService mInstance;public void init(Context context) {mContext = context.getApplicationContext();mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);}public static BaseService getInstance() {if (mInstance == null) {mInstance = new BaseService();}return mInstance;}/*** Check当前辅助服务是否启用** @param serviceName serviceName* @return 是否启用*/public boolean checkAccessibilityEnabled(String serviceName) {List<AccessibilityServiceInfo> accessibilityServices =mAccessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);for (AccessibilityServiceInfo info : accessibilityServices) {if (info.getId().equals(serviceName)) {return true;}}return false;}/*** 前往开启辅助服务界面*/public void goAccess() {Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);mContext.startActivity(intent);}/*** 模拟点击事件,如果该node不能点击,则点击父node,将点击事件一直向父级传递,直至到根node或者找到一个可以点击的node** @param nodeInfo nodeInfo*/public void performViewClick(AccessibilityNodeInfo nodeInfo) {if (nodeInfo == null) {return;}while (nodeInfo != null) {if (nodeInfo.isClickable()) {nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);break;}nodeInfo = nodeInfo.getParent();}}/*** 模拟返回操作*/public void performBackClick() {performGlobalAction(GLOBAL_ACTION_BACK);}/*** 模拟下滑操作*/public void performScrollBackward() {performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);}/*** 模拟上滑操作*/public void performScrollForward() {performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);}/*** 查找对应文本的View,无论该node能不能点击** @param text text* @return View*/public AccessibilityNodeInfo findViewByText(String text) {AccessibilityNodeInfo viewByText = findViewByText(text, true);if (viewByText == null) {viewByText = findViewByText(text, false);}return viewByText;}/*** 查找对应文本的View** @param text      text* @param clickable 该View是否可以点击* @return View*/public AccessibilityNodeInfo findViewByText(String text, boolean clickable) {AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();if (accessibilityNodeInfo == null) {return null;}List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);if (nodeInfoList != null && !nodeInfoList.isEmpty()) {for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {if (nodeInfo != null && (nodeInfo.isClickable() == clickable)) {return nodeInfo;}}}return null;}/*** 查找对应ID的View** @param id id* @return View*/public AccessibilityNodeInfo findViewByID(String id) {AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();if (accessibilityNodeInfo == null) {return null;}List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);if (nodeInfoList != null && !nodeInfoList.isEmpty()) {Log.d("dd", "findViewByID: " + nodeInfoList.size());for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {if (nodeInfo != null) {Log.d("dd", "findViewByID: " + nodeInfo.toString());return nodeInfo;}}}return null;}/*** 点击对应文本的一个view,前提是这个view能够点击,即 clickable == true,** @param text 要查找的文本*/public void clickViewByText(String text) {AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();if (accessibilityNodeInfo == null) {return;}List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);if (nodeInfoList != null && !nodeInfoList.isEmpty()) {for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {if (nodeInfo != null) {performViewClick(nodeInfo);break;}}}}/*** 点击对应id的一个view,前提是这个view能够点击,即 clickable == true,** @param id 要查找的id*/public void clickViewByID(String id) {AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();if (accessibilityNodeInfo == null) {return;}List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);if (nodeInfoList != null && !nodeInfoList.isEmpty()) {for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {if (nodeInfo != null) {performViewClick(nodeInfo);break;}}}}/*** 递归遍历node及其子node,点击文本相同的节点,全点击** @param text* @param parentNode*/public void clickNodesByText(String text, AccessibilityNodeInfo parentNode) {if (parentNode == null) {return;}int childCount = parentNode.getChildCount();if (childCount == 0) {  //叶节点if (parentNode.getText() == null) {return;}if (!text.equals(parentNode.getText().toString())) {return;}Rect rect = new Rect();parentNode.getBoundsInScreen(rect);int moveToX = (rect.left + rect.right) / 2;int moveToY = (rect.top + rect.bottom) / 2;int lineToX = (rect.left + rect.right) / 2;int lineToY = (rect.top + rect.bottom) / 2;gesture(moveToX, moveToY, lineToX, lineToY, 100L, 400L);return;}for (int i = 0; i < childCount; i++) {AccessibilityNodeInfo child = parentNode.getChild(i);clickNodesByText(text, child);}}/*** 根据文本查找节点** @param text 要查找的文本* @return 与文本相同的节点列表,找不到则返回空*/public List<AccessibilityNodeInfo> findNodesByText(String text) {List<AccessibilityNodeInfo> accessibilityNodeInfos = new ArrayList<>();Stack<AccessibilityNodeInfo> nodeStack = new Stack<>();AccessibilityNodeInfo node = getRootInActiveWindow();nodeStack.add(node);while (!nodeStack.isEmpty()) {node = nodeStack.pop();if (node != null && node.getText() != null && node.getText().toString().equals(text)) {accessibilityNodeInfos.add(node);}if (node == null || node.getChildCount() == 0) {continue;}//获得节点的子节点,对于二叉树就是获得节点的左子结点和右子节点int childCount = node.getChildCount();for (int i = 0; i < childCount; i++) {AccessibilityNodeInfo child = node.getChild(i);if (child != null) {nodeStack.push(child);}}}if (accessibilityNodeInfos.size() > 0) {return accessibilityNodeInfos;} else {return null;}}/*** 模拟输入,低版本的输入有所不同,读者请自行百度** @param nodeInfo nodeInfo* @param text     text*/public void inputText(AccessibilityNodeInfo nodeInfo, String text) {Bundle arguments = new Bundle();arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);}/*** 手势操作,因为path不能小于0,因此小于则直接返回,不操作,另外如果有需求,可以自行修改小于则设置为0或者屏幕的宽高** @param moveToX* @param moveToY* @param lineToX* @param lineToY* @param startTime* @param duration*/public void gesture(int moveToX, int moveToY, int lineToX, int lineToY, long startTime, long duration) {if (moveToX < 0 || moveToY < 0 || lineToX < 0 || lineToY < 0) {Log.e("path", "path nagative");return;}GestureDescription.Builder builder = new GestureDescription.Builder();Path path = new Path();path.moveTo(moveToX, moveToY);path.lineTo(lineToX, lineToY);GestureDescription gestureDescription = builder.addStroke(new GestureDescription.StrokeDescription(path, startTime, duration, false)).build();dispatchGesture(gestureDescription, new AccessibilityService.GestureResultCallback() {@Overridepublic void onCompleted(GestureDescription gestureDescription) {super.onCompleted(gestureDescription);}@Overridepublic void onCancelled(GestureDescription gestureDescription) {}}, new Handler(Looper.getMainLooper()));}protected void sleep(long ms) {try {Thread.sleep(ms);} catch (Exception e) {e.printStackTrace();}}@Overridepublic void onAccessibilityEvent(AccessibilityEvent event) { }@Overridepublic void onInterrupt() { }@Overrideprotected void onServiceConnected() { super.onServiceConnected(); }
}

2. AccessService类

AccessService则继承BaseService,具体的无障碍处理逻辑都在这个类里面实现。

代码如下:

import android.view.accessibility.AccessibilityEvent;/*** 操作类,在这里实现具体逻辑*/
public class AccessService extends BaseService {private String appPackageName = "xxx.xxx.xxx";@Overridepublic void onAccessibilityEvent(AccessibilityEvent event) {String packageName = event.getPackageName() == null ? "" : event.getPackageName().toString();if (!packageName.equals(appPackageName)) {// 如果活动APP不是目标APP则不响应return;}int eventType = event.getEventType();switch (eventType) {case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:// 捕获窗口内容改变事件break;default:break;}}
}

三、修改AndroidManifest.xml

1. 添加AccessibilityService配置

在application节点下添加service节点,为程序配置AccessibilityService的属性

内容如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.anheimoxin.access"><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><!--  添加AccessibilityService配置    --><serviceandroid:name="com.anheimoxin.access.service.AccessService"android:label="暗黑魔心的无障碍服务"android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"><intent-filter><action android:name="android.accessibilityservice.AccessibilityService" /></intent-filter><meta-dataandroid:name="android.accessibilityservice"android:resource="@xml/allocation" /></service></application></manifest>

2. 添加allocation.xml配置文件

在res目录下新建xml文件夹,并在新建的xml目录下新建一个名为allocation.xml的文件

文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"android:accessibilityEventTypes="typeWindowContentChanged"android:accessibilityFeedbackType="feedbackGeneric"android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews|flagRequestEnhancedWebAccessibility|flagReportViewIds"android:canPerformGestures="true"android:canRetrieveWindowContent="true"android:canRequestFilterKeyEvents="true"android:canRequestEnhancedWebAccessibility="true"android:notificationTimeout="300" />

文件结构如下图:


四、修改MainActivity类

修改MainActivity类,让程序打开后就检查检查是否开启无障碍服务并跳转到设置页面。

代码如下:

import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;import com.anheimoxin.access.service.BaseService;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);BaseService instance = BaseService.getInstance();instance.init(this);if (!instance.checkAccessibilityEnabled("暗黑魔心的无障碍服务")) {instance.goAccess();}}
}

项目当前的运行效果如下:

五、实现1+2+3=6

万变不离其宗,这里就举个简单的例子,实现打开计算器后自动点击1+2+3=6的操作。

1. 获取APP包名

首先要获取到计算器的包名,可以将手机设置调试模式连接到电脑,使用ADB指令获取。

查看当前活动的APP的包名的指令如下:

adb logcat | findstr Displayed

2. 查找并点击1、2、3、+、=

获取到了包名就可以直接操作了,很简单的实现,先查找定位,然后就可以点击了。如果要详细分析APP的布局,可以使用android studio自带的工具UI Automator Viewer,路径在SDK文件夹下面的tools/bin/中


实现代码如下:

import android.graphics.Rect;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;import java.util.List;/*** 操作类,在这里实现具体逻辑*/
public class AccessService extends BaseService {private String appPackageName = "com.huawei.calculator";private boolean refresh = true; // 控制在未处理完逻辑前不要进入逻辑空间@Overridepublic void onAccessibilityEvent(AccessibilityEvent event) {String packageName = event.getPackageName() == null ? "" : event.getPackageName().toString();if (!packageName.equals(appPackageName)) {// 如果活动APP不是目标APP则不响应return;}int eventType = event.getEventType();switch (eventType) {case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:// 捕获窗口内容改变事件if (packageName.equals(appPackageName)) {if (refresh) {refresh = false;AccessibilityNodeInfo nodeOne = findViewByText("1");if (nodeOne != null) {performViewClick(nodeOne);sleep(500);}// 有些view是没有text的,就可以通过ID、类名等属性来获取AccessibilityNodeInfo nodeAdd = findViewByID("com.huawei.calculator:id/op_add");if (nodeAdd != null) {performViewClick(nodeAdd);sleep(500);}// 查找所有的2,并点击List<AccessibilityNodeInfo> nodeOneList = findNodesByText("2");if (nodeOneList != null && nodeOneList.size() != 0) {for (int i = 0; i < nodeOneList.size(); i++) {AccessibilityNodeInfo node = nodeOneList.get(i);if (node != null) {Rect rect = new Rect();node.getBoundsInScreen(rect);int moveToX = (rect.left + rect.right) / 2;int moveToY = (rect.top + rect.bottom) / 2;int lineToX = (rect.left + rect.right) / 2;int lineToY = (rect.top + rect.bottom) / 2;// 有些View是不能点击,这时候可以用手势来处理gesture(moveToX, moveToY, lineToX, lineToY, 100L, 400L);sleep(500);}}}nodeAdd = findViewByID("com.huawei.calculator:id/op_add");if (nodeOne != null) {performViewClick(nodeAdd);sleep(500);}// getRootInActiveWindow返回整个view的root节点,深度优先遍历查找所有的3,并点击clickNodesByText("3", getRootInActiveWindow());sleep(500);AccessibilityNodeInfo nodeEq = findViewByID("com.huawei.calculator:id/eq");if (nodeOne != null) {performViewClick(nodeEq);sleep(500);}// 更多的操作请看BaseService,或者自行百度refresh = true;}}break;default:break;}}
}

3. 运行效果

至此整个教程的内容就完了,如果有疑问也可以扫码关注我的微信公众号与我联系,下面就是本次项目的效果动图、微信公众号二维码和源码下载链接


需要项目源码的话,可关注公众号【编程杂绪】回复【安卓无障碍服务源码】获取百度云盘链接,或者点击此处下载

从零开始安卓无障碍服务Accessibility相关推荐

  1. Android无障碍服务( Accessibility Service)应用

    无障碍服务是一个应用程序,它给有残疾的用户或暂时无法与设备完全交互的用户提供了更好的无障碍用户交互功能.比如驾驶.照顾小孩或者在吵闹的派对上可能需要额外或者替代的交互反馈. Android提供了标准的 ...

  2. 如何使用安卓无障碍服务之uiautomatorviewer.bat(UI Automator Viewer)层级查看器

    首先安装Android studio,我使用的android版本是 android-studio-2022.1.1.21-windows.zip No .exe installer 下载地址为 Dow ...

  3. 安卓无障碍API封装库: Android-Accessibility-Api

    Android-Accessibility-Api Github > 更新:Android-Accessibility-Api (2.0) 安卓无障碍服务Api, 为了简化无障碍服务使用,并使用 ...

  4. 安卓使用无障碍服务监听微信和QQ的收款信息

    导读: 资深的安卓程序员想必都了解,安卓的通知监听服务(NotificationListenerService)可以监听通知栏的信息,从通知栏信息里获取到我们想要的收款信息(比如收款类型.收款金额). ...

  5. android accessibilityservice自动点击_【Android】无障碍服务(一)入门轻踩

    无障碍服务是一种应用,可提供界面增强功能,来协助残障用户或可能暂时无法与设备进行全面互动的用户完成操作.小编这边用无障碍服务实现一系列自动化操作,有点像按键精灵,踩了不少坑.首要部署声明才能被 And ...

  6. 利用无障碍服务自动获取微信号

    利用无障碍服务自动获取微信号 前言 基本思路 过程 AccessibilityService是什么? 如何创建一个AccessibilityService? 如何开启AccessibilityServ ...

  7. Android实现自动点击 - 无障碍服务

    ps: 不想看代码的滑到最下面有apk包百度网盘下载地址 1. 先看效果图 不然都是耍流氓 2.项目目录 3.一些配置 build.gradle plugins {id 'com.android.ap ...

  8. Android 无障碍服务自动点击

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

  9. Android无障碍服务开发

    https://actionwind.wordpress.com/2022/04/17/android%e6%97%a0%e9%9a%9c%e7%a2%8d%e6%9c%8d%e5%8a%a1%e5% ...

最新文章

  1. Google首席执行官:AI就像火和电,有用而又危险
  2. java----代理机制或动态类的生成
  3. bme280 环境传感器开发板_盘一盘那些年我们常用的物联网开发板!
  4. 模拟inode号耗尽、EXT和XFS类型文件恢复(详细图解)
  5. 架构:Android 组件化开发
  6. 【英语学习】【Daily English】U14 Transportation L04 I'm going to go screen
  7. c语言可以利用数组处理批量数据库,C语言程序设计 利用数组处理批量数据.ppt...
  8. oracle--存储过程--bai
  9. python标准库os_Python标准库 os
  10. Tableau6——地图绘制
  11. CSS布局——导航栏悬浮滚动更改背景色
  12. 告别微服务:究竟是千军易得还是一将难求
  13. 相似度系列-5:语义方法:BERTSCORE: EVALUATING TEXT GENERATION WITH BERT
  14. html svg图片不显示,html/css svg怎么显示不出来?
  15. FRAM芯片扩展在低功率应用中的耐力
  16. Python自动化测试学习3
  17. 【Linux】常见指令收官拓展
  18. SaaS模式、技术与案例详解——第11章 可配置性
  19. python爬虫(17)爬出新高度_抓取微信公众号文章(selenium+phantomjs)
  20. 基于matlab 汉字字符识别——神经网络

热门文章

  1. 英语不好影响考PMP吗?
  2. Superfetch 注册表设置
  3. C51单片机 1602显示一排方块的问题
  4. 二进制空间权重矩阵_空间权重矩阵(SWM)
  5. jboss ejb 3
  6. 触摸IC(JTW6C12)的踩坑经验
  7. 椭圆形方程的差分解法
  8. 雷达、定位、跟踪等信号处理邻域SCI期刊整理及推荐
  9. IFC模型文件查看器(基于IFC++开源库实现)
  10. win10离线装linux子系统 运行ubuntu.exe失败闪退没反应