(AccessibilityService) Android 辅

作者: 芥末末的沫 | 来源:发表于2016-10-24 17:20 被阅读12131次

    最近因为项目的需要(在某个界面需要自动的点击某个按钮),所以接触了关于辅助功能的开发。刚开始的时候根本没有想到可以用辅助功能来帮助实现这个需求,一直在看关于屏幕监听和模拟点击的一些资料。后来通过有经验的同事的介绍,发现可以使用辅助功能来实现我这一需求。话不多说,开始介绍这个辅助功能(AccessibilityService):


    1. 创建自己的辅助功能类:

    编写自己的服务类,需要继承AccessibilityService类。

    对于一些需要重载的方法的介绍:

    onServiceConnected();

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

    onAccessibilityEvent();

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

    onInterrupt();

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

    onUnbind(Intent intent);

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

    1.1 配置:

    对于辅助功能类的配置有两种方式:

    (1)onServiceConnected()中初始化的方式:

    配置service_info

    (2)使用在manifest中添加meta-data的方式(Android 4.0 开始 ——可以参见我在注册中的代码片段)

    在value文件夹中,添加一个xml文件夹(名字应该是可以自定义的,个人没有试过),并在其中添加一个accessibilityservice.xml或者任何你喜欢的名字。可参见下图:

    配置service_info

    建议将一些不可动态更改的服务配置写到xml文件中。

    可以看到上边使用了部分的参数,都是比较常用的一些参数配置,下面来介绍下这些参数的作用:

    Event types(android:accessibilityEventTypes  /  info.eventTypes)

    简单说两个常用的:(具体的可以参照API真的太多了)

    typeAllMask  /  AccessibilityEvent.TYPES_ALL_MASK:全局事件响应

    typeViewClicked  /  AccessibilityEvent.TYPE_VIEW_CLICKED :点击事件

    Feedback Types(android:accessibilityFeedbackType  /  info.feedbackType )

    feedbackGeneric  /  AccessibilityServiceInfo.FEEDBACK_GENERIC : 通用的反馈

    feedbackAudible  /  AccessibilityServiceInfo.FEEDBACK_AUDIBLE : 声音反馈

    feedbackSpoken  /  AccessibilityServiceInfo.FEEDBACK_SPOKEN  :  语音反馈

    CanRetrieveWindow  (android:canRetrieveWindowContent  / info.getCanRetrieveWindowContent()

    从一个AccessibilityEvent中调查完全视图层级的能力隐式地暴露私有用户信息给你的无障碍服务。出于这个原因,你的服务必须通过无障碍服务配置XML文件请求这个级别的访问权,通过包含canRetrieveWindowContent属性和设置它为true。如果你不在你的服务配置xml文件中包含这个设置,那么对getSource()的调用会失败。

    PackageNames (android:packageNames=  /  info.packageNames)

    需要服务监听的包名,中间可以用","分隔开。

    notificationTimeout(android:notificationTimeout  /  info.notificationTimeout)

    响应时间的设置

    1.2 Service类中对事件的拦截处理

    这个部分主要在onAccessibilityEvent()中处理回调返回的AccessibilityEvent。

    主要需要了解的有:

    AccessibilityNodeInfo(findAccessibilityNodeInfosByViewId,findAccessibilityNodeInfosByText)

    AccessibilityEvent(eventType)

    (1)AccessibilityEvent:

    首先介绍AccessibilityEvent:

    This class represents accessibility events that are sent by the system when something notable happens in the user interface.(在用户交互使用时系统返回的event事件)

    方法:

    getSource() :

    Gets the AccessibiltyNodeInfo of the event source.

    当前event的节点信息

    顺便说下getRootInActiveWindow()

    Gets the root node in the currently active window if this service can retrieve window content.

    中文的翻译应该是获取到当前活跃中本服务的可检索到窗口的根节点

    getSource()  约等于? AccessibilityService.getRootInActiveWindow();(我输出过两个NodeInfo的child个数,不太一致,有个数差。)

    It is a client responsibility to recycle the received info by calling AccessibilityNodeInfo.recycle() to avoid creating of multiple instances.

    为避免创建重复的实例通过recycle方法回收掉nodeInfo(我们自己手动去回收)

    eventType :(常用)

    TYPE_NOTIFICATION_STATE_CHANGED

    represents the event of change in the content of a window. This change can be adding/removing view, changing a view size, etc.

    基本窗口view的变化都可以使用这个type来监听

    TYPE_WINDOW_STATE_CHANGED

    Represents the event of opening a Pop,Menu,Dialog etc.

    打开popupwindow,菜单,对话框时候会触发

    TYPE_WINDOW_CONTENT_CHANGED

    Represents the event of changing the content of a window and more specifically the sub-tree rooted at the event's source.

    更加精确的代表了基于当前event.source中的子view的内容变化

    TYPE_WINDOWS_CHANGED

    Represents the event change in the windows shown on the screen.

    窗口的变化

    AccessibilityEvent

    (2)AccessibilityNodeInfo:

    我觉得是辅助功能中最重要的的一个内容,也是最坑的地方到来了。

    关于节点这个问题应该是没有太多的可说的地方,但是呢,出问题出的最多的地方也是这里。

    先看下怎么获取NodeInfo:

    AccessibilityNodeInfo mNodeInfo = getRootInActiveWindow();

    AccessibilityNodeInfo mNodeInfo= event.getSource();

    两种获取方式,之前我也提过,有时候两种方式获取的childNode个数不一致,挨了个球,我都不知道是肿么回事,有了解原理的大大请给我解解惑吧。

    好的接下来我们来查找我们需要做操作的view,在NodeInfo中,默认提供了两个方法来查找我们需要操作的对象:(开始重头戏)

    findAccessibilityNodeInfosByViewId(String str)

    findAccessibilityNodeInfosByText(String str)

    (1)先说上一个方法:findAccessibilityNodeInfosByViewId(String id)

    List <AccessibilityNodeInfo> listNodes = mNodeInfo.findAccessibilityNodeInfosByViewId("id");

    好的,关于这个ID我真的想说说事,每个API不同,同一个对象的ID可能不同:So,做国际化的时候,噩梦来了,为什么在大Samsung上边能够找到并操作这个node对象,在LG上就不可以捏....

    OK,这里就来到了我们的关于node对象的id问题了,放心,Google会让你有解决方法的。(不过就是要累死你,累死你累死你)隆重的来到Hierarchy View。(这里可以百度,大概就是在Android Device Monitor中打开这个View,查看对应界面的view的ID信息)。

    关于真机不能连接Hierarchy View,可以看下我另一篇文章《AccessibilityService 获取View的Id》

    好了,好多同学以为知道id就完了,呵呵,too young to naive。

    id的格式大概是这样的"com.android.settings:id/force_stop_button"(这里以设置界面的强制停止按钮作为范例),请注意加粗的部分,WTF我怎么知道这个前边是神马...

    不要惊慌,注意看这里:

    聪明的小朋友了解了吧。哈哈不多说,没懂就多看看图。

    (2)说完了findById,我们接着说findAccessibilityNodeInfosByText(String text)

    List listNodes = mNodeInfo.findAccessibilityNodeInfosByText("text");

    看了上边的findById之后,这个方法也简单多了吧。没有那么坑,只是做中文系统的时候,直接就可以看图写关键词。所以就不多说了。

    (3)关于node的使用:

    node.performAction(AccessibilityNodeInfo.ACTION_CLICK);

    点击事件简单至极,一看就明白了。

    需要其他的操作,只需要看看API,换换ACTION啦。

    (4)全局按钮的操作

    performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);

    同上。全局的操作是在service做出的action,和node没有太大的关系。

    2. 注册:

    AccessibilityService,从名字上来看,有经验的小朋友就不难看出来,他是一个service,(内心独白:屁话,是个猿都能看出来,鄙视...)那对于他的注册,就不需要多说了,这里简要的将manifest中service注册的一些参数做出说明:

    name:对应的是自定义service的包名

    label:对应了在系统辅助功能开关界面中,你的service的名字(例如:手机管家等)

    description :则是点击对应的服务进入开关界面后,该服务的简介

    permission:应该都不陌生,对应的权限

    (亦可在service中单独写出来

    <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE">)

    intent-filter:指定了执行的组件为辅助功能类

    3. 其他的一些辅助判断的方法:

    3.1是否开启辅助功能的判断

    3.2 跳转到辅助功能界面,开启辅助功能

    总结:

    在辅助功能开发中,初次开发的朋友可能会遇到的一些难点(我觉得我第一次开发的时候最难得就是找viewId)

    1、获取需要操作的viewId

    之前也提到过可以使用hierarchy View来查找对应的viewId,但是在实际开发中,很多手机是没有办法连接server进行dump view的,在这个时候,我们其实可以在AccessibiltiyService的配置中添加一个flag。对应的属性是:android:accessibilityFlags="flagReportViewIds"。在代码中我们就可以通过node节点来getViewIdResourceName()获取对应的节点的id。

    2、获取到id后,查找到需要操作的node

    根据id查找节点的方法上边也有介绍过,需要注意的是,id的格式,对于packageName我们可以通过getPackageName()方法获取。

    最后,祝大家都能够愉快的进行辅助功能的开发工作。

    PS.在网上搜索辅助功能的话,都会出现一些关于微信抢红包的插件。没错,微信抢红包的插件就是使用了我们今天要介绍的辅助功能来开发的。

    相关文章

      网友评论

      • yunhen:关键问题是配置完了不走onAccessibilityEvent啊,怎么解决
      • NathansLiu:getViewIdResourceName 为null 6.0.1
      • 0d6c212d0f7f:3个imageview控件 如何区分呢
      • 丶世事繁华:博主有研究过accessibilityservice里面怎样去处理listview跟gridview的itemclick事件吗?我试了下使用accessibilityservice.dispatchgesture遇到点问题,直接在xml配置文件里面添加android:canperformGestures="true"这个属性会报错找不到这个属性,编译跟最低的版本都是设置的24,在onserviceconnected里面设置AccessibilityServiceInfo flags|=AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES测试又没有效果。
        丶世事繁华:已经搞定,没注意大小写。
      • 3e5b12732dfb:event.getSource 这个获取的是当前有焦点的View 和 getRootInActiveWindow 不一样, getRootInActiveWindow 是获取所有 ,比如说当你点击一个LinearLayout event.getSource 获取的节点就是LinearLayout 和它的子view ,getRootInActiveWindow 每次获取的都是屏幕上所有的view 节点
      • 潇洒的夏夏:有的id属性是clickable为false,但是父布局为true,然而父布局没有id属性,这个怎么解决!
        d165f2d2a93c:nodeChild.getParent() 获取父节点
      • 此生红黑:findAccessibilityNodeInfosByViewId(String id),在Android7.0的手机上找不到是什么问题啊?博主碰到过吗?
        此生红黑:谢博主回答
        1.关于系统问题,我基本上确定了7.0的系统不行,试过了好几台7.0的手机。6.0的手机都可以。
        2.关于rootinactivewindow,不为空,这个也是确定的,source这个方法没用到过。或许这个是切入点。
        3.有时候在开启辅助功能的时候,服务开关打开了了,但是没有走onserviceconnected。这个确实遇到过,然后重启手机就好了。
        芥末末的沫:1、首先确定服务是开启了
        2、确定只有7.0才找不到,其他版本的手机是可以的吗
        3、看下是使用event.source、还是rootinactivrwindow找不到node(或者把两个都找下node)
        4、尝试获取下其他的node,多找几个看下能找到不
        5、在配置服务的meta-data里边把超时时间设长一点,或者检查下是否限制了包名
        芥末末的沫:可能会有这种情况,是因为event.source。或者rootinactivewindow这两个没有获取到。可能为空。或者rootinactivewindow和event的source是不同的两个布局。还有你可以尝试下先卸载,重启之后在安装。有时候在开启辅助功能的时候,服务开关打开了了,但是没有走onserviceconnected。很诡异(基本是多次开启辅助功能,覆盖安装几次就会出现,开起来了,但是服务没有走onServiceConnected)
      • yuyu000:大佬 , 请问一下 怎么获取软键盘view , 需要监听软键盘的点击事件 . .google的talkback可以监听到,不知如何实现的
        芥末末的沫:@yuyu000 好的一起学习
        yuyu000:@芥末末的沫 非常感谢回复 , onKeyEvent(KeyEvent event) 可以监听到系统按键(声音加 减等) 键盘的事件还没想到办法 .

        KeyEvent 这个类中有所有按键变量 , 不过大都弃用了 标记了@Deprecated

        不过talkback能获取到 , 我想肯定有办法 , 等我找到了再跟你分享
        芥末末的沫:这个需要去研究下了,我记得只是有个getSoftKeyboardController方法可以获取到软键盘显示隐藏状态。如果只是软键盘点击的话,应该是可以通过focus状态来判断下。但是点击键盘具体按钮的事件回调这个就要好好想想。如果是获取到输入的内容的话是可以的。
      • 54abb48ee5a2:在手机界面上显示图片,想通过长按然后保存图片,竟然没有这个图片对应的模块,怎么回事?
        芥末末的沫:有界面应该就有root和child view,多试几次。
      • 54abb48ee5a2:通过id查找node好坑啊!!!!
      • 潇洒的夏夏:怎么实现qq自动回复,在不开启通知栏的情况下
        芥末末的沫:通知栏接收到了QQ的消息这个notification现实出来了辅助功能才收的到触发Event,然而对该event具体怎么做就是我们自己来做了,辅助功能只是可以帮助我们手动的点击已经呈现出来的窗体,如果通知来了不点开,我们怎么回复消息呢?是吧。
      • c5651a28be45:这个不多说了,那个也不多说了。。。
        芥末末的沫:然而真的并没有什么好说的:joy: 如果都说完就可以写论文了,这里只是简单的介绍了常用的东西

      本文标题:(AccessibilityService) Android 辅

      本文链接:https://www.haomeiwen.com/subject/rvdluttx.html