最近使用AccessibilityService完成业务,感觉还是蛮有意思的,顺手写一下这个类的用法,将来要是再有需要用到的时候也比较方便复习查阅。

AccessibilityService是用于开发无障碍功能应用的api类,帮助残障人士使用app。同样的,使用它可以帮助我们对用户体验进行提升,例如手机助手中的一键安装,免去了我们多次点击的麻烦,它还能帮助我们完成一些看似外挂般的插件,比如抢红包插件。

如何使用AccessibilityService:

首先,创建子类继承AccessibilityService,AccessibilityService是服务的一种,那么我们需要在Mainifest注册,同时添加相对应的权限和过滤条件如下图。

<service
   android:name="packagename.YourAccessibilityService"
   android:enabled="true"
   android:exported="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/rob_service_config" />
service>

AccessibilityService可以在xml中配置它的辅助信息即

<meta-data
       android:name="android.accessibilityservice"
       android:resource="@xml/rob_service_config" />

我们需要在res文件夹中增加对应的xml文件res--xml--rob_service_config.xml,内容如下

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
   android:accessibilityEventTypes="typeAllMask"
   android:accessibilityFeedbackType="feedbackSpoken"
   android:description="@string/accessibility_service_description"
   android:accessibilityFlags="flagDefault"
   android:canRetrieveWindowContent="true"
   android:notificationTimeout="100"
   android:packageNames="package_name_1,package_name_2" />

eventTypes代表该服务关注的事件类型,例如:

typeNotificationStateChanged  通知栏状态改变
typeViewClicked               点击事件
typeWindowStateChanged        窗体状态变化
typeAllMask                   拦截所有的事件

等等等,这里我们可以根据我们的需求去选择拦截类型。

packageNames即我们想要监听的应用包名,可以监听多个应用,包名之间以","隔开。

同样的我们可以在代码中配置这些信息:

    @Override
   public void onCreate() {
       super.onCreate();
//      AccessibilityServiceInfo info = new AccessibilityServiceInfo();
//      info.packageNames = installPackge; //监听过滤的包名
//      info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; //监听哪些行为
//      info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN; //反馈
//      info.notificationTimeout = 100; //通知的时间
//      setServiceInfo(info);
   }

做完这些以后,开启服务,打开设置中的辅助选项,选择你的辅助服务并开启,你就可以享受拦截其他app各种操作的快感了~那么,拦截到的事件在什么地方呢,AccessibilityService是一个抽象类,它的核心方法就是他的抽象方法

public abstract void onAccessibilityEvent(AccessibilityEvent event);

这个event就是我们拦截到的事件,这个方法是异步执行的(当然了,万一这事件被拦截并处理了半天,人家的app还让不让人用了),这个时间有view中的accessibilityDelegate对象发出。

好了,事件拿到了,就能对里面的内容大做文章了,AccessibilityEvent最重要的就是它的eventType了,

/**
* Gets the event type.
*
* @return The event type.
*/
public int getEventType() {
   return mEventType;
}

毕竟我们需要知道这是什么类型时间才好继续往下做嘛,然后就是

String className = event.getClassName().toString()

因为监听的是别人家的app,我们不知道我们想要知道的页面类名是什么,所以想要在指定页面做指定事情那就需要这个页面的类名,有了这个方法,你还会不知道别人家的活动页面的类名叫什么了吗?

接下来就是getSource了获取事件的节点信息,又或者我们可以使用

/**
* Gets the root node in the currently active window if this service
* can retrieve window content. The active window is the one that the user
* is currently touching or the window with input focus, if the user is not
* touching any window.
*


* Note: In order to access the root node your service has
* to declare the capability to retrieve window content by setting the
* {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
* property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
*


*
* @return The root node if this service can retrieve window content.
*/
public AccessibilityNodeInfo getRootInActiveWindow() {
   return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId);
}

获取该事件触发时的活动窗口,从这个活动窗口拿到我们想要的信息,比如包含某文字的控件,或者执行想要做的辅助事件,比如点击、滚动等。AccessibilityNodeIfo对象包含了树状父子节点信息,

public List findAccessibilityNodeInfosByViewId(String viewId){...}
public List findAccessibilityNodeInfosByText(String text){...}

这两个方法可以找到包含某文字或者某控件id名称的节点

public boolean performAction(int action) {...}
public boolean performAction(int action, Bundle arguments){...}

这两个方法可以使该节点执行某个动作比如

AccessibilityNodeInfoA.CTION_CLICK

此外对于部分action,可以携带额外的参数

Bundle bundle = new Bundle();
bundle.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT,0);
bundle.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT,1);
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION,bundle);

此外AccessibilityService本身有一个方法

* @see #GLOBAL_ACTION_BACK
* @see #GLOBAL_ACTION_HOME
* @see #GLOBAL_ACTION_NOTIFICATIONS
* @see #GLOBAL_ACTION_RECENTS
*/
public final boolean performGlobalAction(int action)

让我们去执行全局的动作,如回退,返回home页等。



AccessibilityService的使用大体就是这样了,至于再详细的东西就一个个的去文档中找,并一个个使用它们吧。


此外,再记一下使用AccessibilityService过程中用到的tool

一个是D:\Users\XXX\Android\sdk\build-tools\23.0.0目录下的aapt.exe
使用cmd切换到aapt目录执行aapt dump badging 可以查看指定apk的包名等详细内容;

aapt使用小结   这里有十分详细的介绍,对于这里来说,我们只需要知道目标应用的包名即可;

另一个是D:\Users\XXX\Android\sdk\tools目录下的uiautomatorviewer.bat

打开此文件连接手机,点击工具的截屏按钮,稍后你就能看到该页面的布局层次以及各控件的信息(有我AccessibilityService需要的resId,这样妈妈在不用担心我找不到想要的那个nodeInfo了)。


Ok,this is the end.