Android Accessibility(辅助功能)学习

写在前面

前一段项目的需要(需要自动开启微信并自动添加通讯录好友以及发朋友圈)接触了关于AccessibilityService(辅助功能)的开发。刚开始的时候根本没有想到可以用辅助功能来帮助实现这个需求,一直在研究屏幕监听和模拟点击功能,因为手机没有root屏幕监听和模拟点击功能不可以用,后来通过和同事讨论,发现可以使用辅助功能来实现这一需求。Accessibility主要目的是帮助一些因为有视觉,听觉,身体障碍而无法完全使用触摸屏或铃声等的用户来使用Android的。而实际上现在很多开发者都用它来实现一些其他功能了,比如说微信抢红包,自动安装APK,强制停止应用等。那么接下来开始介绍这个AccessibilityService(辅助功能):

AccessibilityService的使用

AccessibilityService是Service的子类,运行在后台,并且能够收到由系统发出的一些事件(AccessibilityEvent,这些事件表示用户界面一系列的状态变化),比如焦点改变,输入内容变化,按钮被点击了等等,该种服务能够请求获取当前活动窗口并查找其中的内容.换言之,界面中产生的任何变化都会产生一个时间,并由系统通知给AccessibilityService.这就像监视器监视着界面的一举一动,一旦界面发生变化,立刻发出警报.

创建自己的服务类

创建自己的服务类,需要继承AccessibilityService类。

需要重载以下方法:

onServiceConnected()

系统会在成功连接上你的服务的时候调用这个方法,在这个方法里你可以做一下初始化工作,例如设备的声音震动管理,也可以调用setServiceInfo()进行配置工作。

onAccessibilityEvent()

通过这个函数可以接收系统发送来的AccessibilityEvent,接收来的AccessibilityEvent是经过过滤的,过滤是在配置工作时设置的。

onInterrupt()

这个在系统想要中断AccessibilityService返给的响应时会调用。在整个生命周期里会被调用多次。

onUnbind(Intent intent)

在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作

声明

和其它Service服务一样,需要在AndroidManifest.xml声明,而且还要做以下两件事:

  1. 指定intent-filter为 “android.accessibilityservice.AccessibilityService”
  2. 添加 BIND_ACCESSIBILITY_SERVICE权限,确保只有系统可以绑定到它**

如果项目中缺少任意一个,系统会忽略AccessibilityService。下面是一个声明例子:

<service android:name=".MyAccessibilityService"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
   <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
   </intent-filter>
 . . .
</service>

参数配置

AccessibilityService可以配置用来接收特定类型的事件,只监听特定的packages,在特定的时间内只能获得每种事件只有一次,检索窗口内容,指定一个设置活动,等等

配置AccessibilityService有两种方法:

  • 在声明服务时,在AndroidManifest.xml中提供一个meta-data。下面给出了一个带有meta-data的服务声明:
 <service android:name=".MyAccessibilityService">
     <intent-filter>
          <action android:name="android.accessibilityservice.AccessibilityService" />
     </intent-filter>
     <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" />
</service>

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="feedbackGeneric"
        android:accessibilityFlags="flagDefault"
        android:canRetrieveWindowContent="true"
        android:description="@string/accessibility_service_description"
        android:notificationTimeout="100" 
        android:packageNames="com.tencent.mm,com.yulong.android.launcherL" />

注意:此方法使设置的所有属性。欲了解更多详情,请参阅 SERVICE_META_DATAaccessibility-service

  • 调用setServiceInfo(AccessibilityServiceInfo)。注意,此方法可以任何时候调用来动态地更改AccessibilityService的配置。

    @Override
      protected void onServiceConnected() {
          AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
          serviceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
          serviceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
          serviceInfo.packageNames = new String[]{"com.tencent.mm,com.yulong.android.launcherL"}; 
          serviceInfo.notificationTimeout=100;
          setServiceInfo(serviceInfo);
     }
    

    注意:这种方法只可以设置动态配置属性:eventTypes,feedbackType,flags,notificationTimeout,packageNames; 欲了解更多详情,请参阅AccessibilityServiceInfo

现在我们对配置中的重要属性进行说明:

accessibilityEventTypes: 表示接收事件的类型:

方法 说明
AccessibilityEvent.TYPES_ALL_MASK 全局事件响应
AccessibilityEvent.TYPE_VIEW_CLICKED 点击事件

accessibilityFeedbackType:表示反馈方式:

方法 说明
AccessibilityServiceInfo.FEEDBACK_GENERIC 通用的反馈
AccessibilityServiceInfo.FEEDBACK_AUDIBLE 声音反馈

canRetrieveWindowContent:

表示该服务能否访问活动窗口中的内容.如果在服务中获取窗体内容的化,则需要设置其值为true.

notificationTimeout:

接受事件的时间间隔,通常将其设置为100.

packageNames:

表示对该服务是用来监听哪个包的产生的事件,中间可以用","分隔开。

监听指定类型事件

监听AccessibilityEvent事件我们在onAccessibilityEvent(AccessibilityEvent event)方法中进行:

@Override  
public void onAccessibilityEvent(AccessibilityEvent event) {  
    int eventType = event.getEventType();  
    switch (eventType) {  
    case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
            //通知栏消息
            break;  
    case AccessibilityEvent.TYPE_VIEW_CLICKED:
            //界面点击
            break; 
        //.......  
    }  
} 

这个事件类型很多的,我们可以查看AccessibilityEvent类的源码:

@Deprecated  
public static final int MAX_TEXT_LENGTH = 500;  

/** 
 * Represents the event of clicking on a {@link android.view.View} like 
 * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. 
 */  
public static final int TYPE_VIEW_CLICKED = 0x00000001;  

/** 
 * Represents the event of long clicking on a {@link android.view.View} like 
 * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. 
 */  
public static final int TYPE_VIEW_LONG_CLICKED = 0x00000002;  

/** 
 * Represents the event of selecting an item usually in the context of an 
 * {@link android.widget.AdapterView}. 
 */  
public static final int TYPE_VIEW_SELECTED = 0x00000004;  

/** 
 * Represents the event of setting input focus of a {@link android.view.View}. 
 */  
public static final int TYPE_VIEW_FOCUSED = 0x00000008;  

/** 
 * Represents the event of changing the text of an {@link android.widget.EditText}. 
 */  
public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010;  

/** 
 * Represents the event of opening a {@link android.widget.PopupWindow}, 
 * {@link android.view.Menu}, {@link android.app.Dialog}, etc. 
 */  
public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020;  

/** 
 * Represents the event showing a {@link android.app.Notification}. 
 */  
public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040;  

/** 
 * Represents the event of a hover enter over a {@link android.view.View}. 
 */  
public static final int TYPE_VIEW_HOVER_ENTER = 0x00000080;  

/** 
 * Represents the event of a hover exit over a {@link android.view.View}. 
 */  
public static final int TYPE_VIEW_HOVER_EXIT = 0x00000100;  

/** 
 * Represents the event of starting a touch exploration gesture. 
 */  
public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 0x00000200;  

/** 
 * Represents the event of ending a touch exploration gesture. 
 */  
public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;  

/** 
 * Represents the event of changing the content of a window and more 
 * specifically the sub-tree rooted at the event's source. 
 */  
public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800;  

/** 
 * Represents the event of scrolling a view. 
 */  
public static final int TYPE_VIEW_SCROLLED = 0x00001000;  

/** 
 * Represents the event of changing the selection in an {@link android.widget.EditText}. 
 */  
public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000;  

/** 
 * Represents the event of an application making an announcement. 
 */  
public static final int TYPE_ANNOUNCEMENT = 0x00004000;  

/** 
 * Represents the event of gaining accessibility focus. 
 */  
public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000;  

/** 
 * Represents the event of clearing accessibility focus. 
 */  
public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000;  

/** 
 * Represents the event of traversing the text of a view at a given movement granularity. 
 */  
public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000;  

/** 
 * Represents the event of beginning gesture detection. 
 */  
public static final int TYPE_GESTURE_DETECTION_START = 0x00040000;  

/** 
 * Represents the event of ending gesture detection. 
 */  
public static final int TYPE_GESTURE_DETECTION_END = 0x00080000;  

/** 
 * Represents the event of the user starting to touch the screen. 
 */  
public static final int TYPE_TOUCH_INTERACTION_START = 0x00100000;  

/** 
 * Represents the event of the user ending to touch the screen. 
 */  
public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000;  

/** 
 * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: 
 * The type of change is not defined. 
 */  
public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0x00000000;  

/** 
 * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: 
 * A node in the subtree rooted at the source node was added or removed. 
 */  
public static final int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001;  

/** 
 * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: 
 * The node's text changed. 
 */  
public static final int CONTENT_CHANGE_TYPE_TEXT = 0x00000002;  

/** 
 * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: 
 * The node's content description changed. 
 */  
public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;  

查找节点View

系统提供了两个方法让我们来进行查找想要的节点View:

  1. 通过节点View的Text内容来查找:findAccessibilityNodeInfosByText(“查找内容”)这种方式查找,就是像TextView,Button等View有文本内容的,可以使用这种方式快速的找到。

    nodes = getRootInActiveWindow().findAccessibilityNodeInfosByText("手机联系人");
                   if (nodes != null && nodes.size() > 0) {
                       AccessibilityNodeInfo node = nodes.get(0).getParent();
                       node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
    
                   }
    
  2. 第二种是通过节点View在xml布局中的id名称来查找:findAccessibilityNodeInfosByViewId(“@id/xxx”);(关于怎样找到View在xml布局中的id我下面有说明)

    nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.tencent.mm:id/f_");
                 if (nodes != null && nodes.size() > 0) {
                     for (int i = 0; i < nodes.size(); i++) {
                         node = nodes.get(i).getParent();
                         node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                     }
                 }
    

模拟点击指定事件

我们找到我们想要的View节点,调用方法模拟事件:

调用performAction(AccessibilityNodeInfo.ACTION_CLICK)这个方法即可

当然这里的参数就是指定事件的名称,这个和AccessibilityEvent中监听的那些事件是一一对应的,这里是模拟点击事件,我们当然可以模拟View的滚动事件,长按事件等。

实际应用——微信抢红包插件

上面我们就介绍了一个辅助功能开发的具体步骤,那么下面就通过一个简单的例子,来实战一下:

坚持原创技术分享,您的支持将鼓励我继续创作!
0%