Menu

Android辅助服务AccessibilityService实践总结

  • 代记账1     2021-3-5
<返回列表

20

分钟

速读仅需12分钟

来源:腾讯在线教育技术 | 作者:shuchenghe一.前言

最近在写运营助手的时候,接触了Android辅助服务,即AccessibilityService的相关内容,也算是解决了我一直以来的困惑——某些具有自动化功能的手机插件是怎么实现的。这两天,抽空总结一下这一部分相关的内容,本篇文章将重点介绍辅助服务的实践方法。

二.概述 1.辅助服务是什么

辅助服务的设计初衷提供给无法和界面进行交互的残疾用户。来协助帮助他们进行一些用户操作,比如点击,返回,长按,获取屏幕信息等能力。后来被开发者另辟蹊径,用于一些插件开发,做一些监听第三方应用的插件。

下面是辅助服务的继承关系:

2.辅助服务生命周期

辅助服务的生命周期由系统专门管理,并遵循Server的生命周期。服务的启动只能用户在设备设置中明确启动服务来触发。当系统绑定到服务后,它会调用AccessibilityService#onServiceConnected方法。当用户在设置设置中关闭时,辅助服务功能将停止,或者调用AccessibilityService#disableSelf方法。giant服务会被关闭销毁

设备设置无障碍选择:

下面是关于AccessibilityService的使用

三.配置 1.继承AccessbilityService类

要使用辅助服务,首先先继承AccessbilityService类,并且重写其方法。

  1. public class StatusAccessibilityService extends AccessibilityService {
  2. /**
  3. * 发生用户界面事件回调此事件
  4. * @param event
  5. */
  6. @Override
  7. public void onAccessibilityEvent(AccessibilityEvent event) {
  8. }
  9. /**
  10. * 中断可访问性反馈
  11. */
  12. @Override
  13. public void onInterrupt {
  14. }
  15. }

除了上面的两个必须要重写的方法外,AccessbilityService还提供了下面的一些方法:

不太常用的:

AccessbilityService是一个服务,所以同样,他也要在AndroidManifest中注册:

  1. <service
  2. android:name=\”com.hahak.walle.accessibilitydame.StatusAccessibilityService\”
  3. android:label=\”辅助服务测试\”
  4. android:permission=\”android.permission.BIND_ACCESSIBILITY_SERVICE\”>
  5. <intent-filter>
  6. <action android:name=\”android.accessibilityservice.AccessibilityService\”/>
  7. </intent-filter>
  8. </service>

接下来,就是配置服务参数,即设定AccessbilityService所能干的事。配置方法有两种,一种是在代码中动态设置,一种是写配置文件。

方法1.写配置文件首先在AndroidManifest中生命配置文件的位置

< <service

  1. android:name=\”com.hahack.walle.AutoCheckStatusAccessibilityService\”
  2. android:label=\”运营助手:自动艾特用户\”
  3. android:permission=\”android.permission.BIND_ACCESSIBILITY_SERVICE\”>
  4. <intent-filter>
  5. <action android:name=\”android.accessibilityservice.AccessibilityService\”/>
  6. </intent-filter>
  7. <meta-data
  8. android:name=\”android.accessibilityservice\”
  9. android:resource=\”@xml/allocation\”/>
  10. </service>

其次,在@xml/allocation文件中,声明下面的内容

  1. <accessibility-service xmlns:android=\”http://schemas.android.com/apk/res/android\”
  2. android:accessibilityEventTypes=\”typeAllMask\”
  3. android:deion=\”demo\”
  4. android:accessibilityFeedbackType=\”feedbackSpoken\”
  5. android:canRetrieveWindowContent=\”true\”
  6. android:notificationTimeout=\”1000\”/>

方法2.在代码中动态添加在自定义的AccessibilityService类中,可以通过在AccessibilityServiceInfo类重写onServiceConnected,并通过 this.setServiceInfo来添加配置

  1. @Override
  2. public void onServiceConnected {
  3. info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED |
  4. AccessibilityEvent.TYPE_VIEW_FOCUSED;
  5. info.packageNames = new String[]
  6. {\”com.example.android.myFirstApp\”, \”com.example.android.mySecondApp\”};
  7. info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN
  8. info.notificationTimeout = 100;
  9. this.setServiceInfo(info);
  10. }

下面说说配置内容的具体含义:

常量 描述
typeViewClicked 点击事件
typeViewSelected view被选择
typeViewScrolled 滑动事件
typeWindowContentChanged 窗口内容该表
typeAllMask 所有事件
常量 描述
feedbackSpoken 语音反馈
feedbackHaptic 触觉(震动)反馈
feedbackAudible 音频反馈
feedbackVisual 视频反馈
feedbackGeneric 通用反馈
feedbackAllMask 以上都具有

辅助服务的启动必须通过用户设置来开启,所以我们要先提醒用户进行无障碍功能列表

  1. Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
  2. startActivity(intent);

通过上面的代码就可以打开系统的无障碍功能列表

2.获取事件信息

当我们监听的目标应用界面或者界面等信息,会通过onAccessibilityEvent回调我们的事件,接着进行事件的处理。

  1. @Override
  2. public void onAccessibilityEvent(final AccessibilityEvent event) {
  3. String packageName = event.getPackageName.toString;
  4. if (!packageName.equals(\”com.tencent.mm\”)) {
  5. return;
  6. }
  7. int eventType = event.getEventType;
  8. switch (eventType) {
  9. case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
  10. 处理
  11. ….
  12. }

可以看出,当发生变化时,回调onAccessibilityEvent方法,并传入AccessibilityEvent类型。下面让我们看看AccessibilityEvent的含义。

先看看AccessibilityEvent提供的方法(源是指触发此方法的ui/时间):

关于事件类型getEventType返回值:

当我们接受到事件后,根据事件就可以处理对应的时间,比如说检测到弹窗,就可以转换为动作:点击弹窗。处理事件总的来说,分为两个步骤。第一步,寻找该控件。第二部,模拟人的操作对各个控件进行操作(点击,长按,输入,读取)

寻找控件首先要找的它对象窗口内容对应的树,即AccessibilityWindowInfo(代表)和AccessibilityNodeInfo(代表具体的View)。注意,这个功能需要在xml资源配置中声明SERVICEMETADATA。

针对寻找AccessibilityWindowInfo和AccessibilityNodeInfo,谷歌官方提供了下面的api

AccessibilityWindowInfo:AccessibilityWindowInfo表示可访问窗口的状态快照。屏幕内容包含一个或者多个窗口,其中一些窗口可以是其他窗口的后代,窗口是次序排序的。AccessibilityWindowInfor提供的api简介:

AccessibilityNodeInfo:AccessibilityNodeInfo表示窗口内容的节点以及可以进行的操作。AccessibilityNodeInfo内部类介绍:

4.处理事件–操作控件

在上面一小节里,我们通过addAction,可以给AccessibilityNodeInfo添加对于的动作,其参数是 AccessibilityNodeInfo的内部类AccessibilityAction。

AccessibilityAction表示可以对AccessibilityNodeInfo。每个操作都有一个唯一的ID,这是必需的和可选的数据。其有三类动作

可提供的操作

在Android7.0之后,AccessibilityService又增加了一个新的方法dispatchGesture。可以将手势发送到触摸屏上。但是要使用这个功能必须在配置文件中声明,canPerformGestures = “true”

api接口:

  1. public final boolean dispatchGesture (GestureDeion gesture,
  2. AccessibilityService.GestureResultCallback callback,
  3. Handler handler)

参数:

使用示例

  1. Path path=new Path;
  2. path.moveTo(0,400);
  3. path.lineTo(400,400);
  4. final GestureDeion.StrokeDeion sd;
  5. sd=new GestureDeion.StrokeDeion(path,100,50);
  6. //先横滑
  7. boolean flag=this.dispatchGesture(new GestureDeion.Builder.addStroke(sd).build,new AccessibilityService.GestureResultCallback{
  8. @Override
  9. public void onCompleted(GestureDeion gestureDeion){
  10. super.onCompleted(gestureDeion);
  11. Log.d(\”22222\”,\”onCompleted:横滑 \”);
  12. Path path2=new Path;
  13. path2.moveTo(600,600);
  14. path2.lineTo(600,800);
  15. final GestureDeion.StrokeDeion sd2=new GestureDeion.StrokeDeion(path2,1000,500);
  16. //滑完后再过1秒竖滑
  17. BaseAccessibilityService.this.dispatchGesture(new GestureDeion.Builder.addStroke(sd2)/*.addStroke(sd2)*/.build,null,null);
  18. }
  19. @Override
  20. public void onCancelled(GestureDeion gestureDeion){
  21. Log.d(\”22222\”,\”onCancelled\”);
  22. super.onCancelled(gestureDeion);
  23. }},null);

上图的示例为左滑和下滑,如果要实现点击事件,则可以只传入一个点Path。即

  1. Path path = new Path;
  2. path.moveTo(0, 400);
  3. sd = new GestureDeion.StrokeDeion(path, 100, 50);

下面是对AccessibilityService提供的各种方法的封装

publicclassBaseAccessibilityServiceextendsAccessibilityService{

  1. private AccessibilityManager mAccessibilityManager;
  2. private Context mContext;
  3. private static BaseAccessibilityService mInstance;
  4. public void init(Context context) {
  5. mContext = context.getApplicationContext;
  6. mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
  7. }
  8. public static BaseAccessibilityService getInstance {
  9. if (mInstance == null) {
  10. mInstance = new BaseAccessibilityService;
  11. }
  12. return mInstance;
  13. }
  14. /**
  15. * Check当前辅助服务是否启用
  16. *
  17. * @param serviceName serviceName
  18. * @return 是否启用
  19. */
  20. private boolean checkAccessibilityEnabled(String serviceName) {
  21. List<AccessibilityServiceInfo> accessibilityServices =
  22. mAccessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
  23. for (AccessibilityServiceInfo info : accessibilityServices) {
  24. if (info.getId.equals(serviceName)) {
  25. return true;
  26. }
  27. }
  28. return false;
  29. }
  30. /**
  31. * 前往开启辅助服务界面
  32. */
  33. public void goAccess {
  34. Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
  35. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  36. mContext.startActivity(intent);
  37. }
  38. /**
  39. * 模拟点击事件
  40. *
  41. * @param nodeInfo nodeInfo
  42. */
  43. public void performViewClick(AccessibilityNodeInfo nodeInfo) {
  44. if (nodeInfo == null) {
  45. return;
  46. }
  47. while (nodeInfo != null) {
  48. if (nodeInfo.isClickable) {
  49. nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
  50. break;
  51. }
  52. nodeInfo = nodeInfo.getParent;
  53. }
  54. }
  55. /**
  56. * 模拟返回操作
  57. */
  58. public void performBackClick {
  59. try {
  60. Thread.sleep(500);
  61. } catch (InterruptedException e) {
  62. e.printStackTrace;
  63. }
  64. performGlobalAction(GLOBAL_ACTION_BACK);
  65. }
  66. /**
  67. * 模拟下滑操作
  68. */
  69. public void performScrollBackward {
  70. try {
  71. Thread.sleep(500);
  72. } catch (InterruptedException e) {
  73. e.printStackTrace;
  74. }
  75. performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
  76. }
  77. /**
  78. * 模拟上滑操作
  79. */
  80. @RequiresApi(api = Build.VERSION_CODES.N)
  81. public void performScrollForward {
  82. try {
  83. Thread.sleep(500);
  84. } catch (InterruptedException e) {
  85. e.printStackTrace;
  86. }
  87. performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
  88. }
  89. /**
  90. * 查找对应文本的View
  91. *
  92. * @param text text
  93. * @return View
  94. */
  95. public AccessibilityNodeInfo findViewByText(String text) {
  96. return findViewByText(text, false);
  97. }
  98. /**
  99. * 查找对应文本的View
  100. *
  101. * @param text text
  102. * @param clickable 该View是否可以点击
  103. * @return View
  104. */
  105. public AccessibilityNodeInfo findViewByText(String text, boolean clickable) {
  106. AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow;
  107. if (accessibilityNodeInfo == null) {
  108. return null;
  109. }
  110. List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
  111. if (nodeInfoList != null && !nodeInfoList.isEmpty) {
  112. for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
  113. if (nodeInfo != null && (nodeInfo.isClickable == clickable)) {
  114. return nodeInfo;
  115. }
  116. }
  117. }
  118. return null;
  119. }
  120. /**
  121. * 查找对应ID的View
  122. *
  123. * @param id id
  124. * @return View
  125. */
  126. @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
  127. public AccessibilityNodeInfo findViewByID(String id) {
  128. AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow;
  129. if (accessibilityNodeInfo == null) {
  130. return null;
  131. }
  132. List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
  133. if (nodeInfoList != null && !nodeInfoList.isEmpty) {
  134. Log.d(\”dd\”, \”findViewByID: \” + nodeInfoList.size);
  135. for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
  136. Log.d(\”dd\”, \”findViewByID: \” + nodeInfo.toString);
  137. if (nodeInfo != null) {
  138. return nodeInfo;
  139. }
  140. }
  141. }
  142. return null;
  143. }
  144. public void clickTextViewByText(String text) {
  145. AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow;
  146. if (accessibilityNodeInfo == null) {
  147. return;
  148. }
  149. List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
  150. if (nodeInfoList != null && !nodeInfoList.isEmpty) {
  151. for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
  152. if (nodeInfo != null) {
  153. performViewClick(nodeInfo);
  154. break;
  155. }
  156. }
  157. }
  158. }
  159. @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
  160. public void clickTextViewByID(String id) {
  161. AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow;
  162. if (accessibilityNodeInfo == null) {
  163. return;
  164. }
  165. List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
  166. if (nodeInfoList != null && !nodeInfoList.isEmpty) {
  167. for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
  168. if (nodeInfo != null) {
  169. performViewClick(nodeInfo);
  170. break;
  171. }
  172. }
  173. }
  174. }
  175. /**
  176. * 模拟输入
  177. *
  178. * @param nodeInfo nodeInfo
  179. * @param text text
  180. */
  181. public void inputText(AccessibilityNodeInfo nodeInfo, String text) {
  182. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  183. Bundle arguments = new Bundle;
  184. arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
  185. nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
  186. } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
  187. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
  188. ClipData clip = ClipData.newPlainText(\”label\”, text);
  189. clipboard.setPrimaryClip(clip);
  190. nodeInfo.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
  191. nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);
  192. }
  193. }
  194. @Override
  195. public void onAccessibilityEvent(AccessibilityEvent event) {
  196. Log.d(\”dd\”, \”onAccessibilityEvent: \” + event.toString);
  197. }
  198. @Override
  199. public void onInterrupt {
  200. }
  201. @Override
  202. protected void onServiceConnected {
  203. super.onServiceConnected;
  204. Log.d(\”llll\”, \”onServiceConnected: \”);
  205. }
  206. }

4.实际应用

学习了大部分的AccessibilityService相关知识。就可以灵活运用这些内容进行组装。比如说:

官方api文档:(https://developer.android.google.cn/reference/android/accessibilityservice/AccessibilityService.html#lifecycle)


更多阅读

北京海淀区凡诺企业网站管理系统V3.0代码审计

代记账1 2021-3-19
政务处理 0×00 前言 大家好,我是掌控安全学院的聂风,在此,我做一个代码审计的文章分享来方便同学们学习。我逛了逛CNVD,发现有一个叫做凡诺企业网站...

北京市税务审计是做什么的?与财务审计的区别

代记账1 2021-3-19
政务处理 税务审计主要为了查看企业是否按规定交税、有没有偷税漏税行为,有没有弄虚作假等情况。当企业面临税务稽查、变更、并购等问题时,对企业进行全...

北京5家会计师事务成功中标公安部机关审计项目

代记账1 2021-3-19
政务处理 中机国际招标有限公司受公安部机关政府采购办公室 的委托,就“公安部机关审计服务项目”项目(项目编号:0702-19412G109)组织采购,评标工作已...
返回列表
扫描二维码分享到微信
确 认

Copyright © 2021 代记账服务

     
扫码二维码立即咨询
确 认