AccessibilityService讲解

来源:互联网 发布:《南风知我意》 编辑:程序博客网 时间:2024/05/17 09:08

1.概念

AccessibilityService它运行在后台,而且当AccessibilityEvents事件被触发的时候系统会自动回调AccessibilityService中的相关方法。这些AccessibilityEvents事件表示用户界面上的一些状态的改变,例如:焦点发生变化,一个button被点击等等。AccessibilityService可以根据自己的需要查询活动窗口的内容(也就是说,界面中产生的任何变化都会产生一个时间,并由系统通知给AccessibilityService.这就像监视器监视着界面的一举一动,一旦界面发生变化,立刻发出警报。)我们要开发AccessibilityService需要继承AccessibilityService类并且实现里面的抽象方法即可。

1.5使用AccessibilityService类步骤

别看我下面写这么多,其实AccessibilityService用起来非常简单步骤如下:

1.继承AccessibilityService类,重写里面的方法(下面有介绍)2.在AndroidMainifest中注册(具体使用参考下面第3条)3.添加配置文件(具体使用参考下面第4条)

下面我们对继承AccessibilityService类里面需要重写的几个重要方法进行简单介绍:

1.onAccessibilityEvent()必须重写。通过这个函数可以接收系统发送来的AccessibilityEvent,接收来的AccessibilityEvent是经过过滤的,过滤是在配置工作时设置的。  2.onInterrupt()必须重写。这个在系统想要中断AccessibilityService返给的响应时会调用。在整个生命周期里会被调用多次。3.onServiceConnected()可选。在系统成功连接上这个AccessibilityService会调用。在这个方法里你可以做一下初始化工作。4.onUnbind()可选。在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作。

代码如下:

public class RobService extends AccessibilityService {    @Override    public void onAccessibilityEvent(AccessibilityEvent event) {}    @Override    public void onInterrupt() {}    @Override    protected void onServiceConnected() { } }

下面我们对继承AccessibilityService类里面需要重写的几个重要方法进行简单介绍:

1.onAccessibilityEvent()必须重写。通过这个函数可以接收系统发送来的AccessibilityEvent,接收来的AccessibilityEvent是经过过滤的,过滤是在配置工作时设置的。  2.onInterrupt()必须重写。这个在系统想要中断AccessibilityService返给的响应时会调用。在整个生命周期里会被调用多次。3.onServiceConnected()可选。在系统成功连接上这个AccessibilityService会调用。在这个方法里你可以做一下初始化工作。4.onUnbind()可选。在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作。

2.生命周期

要理解该中服务的生命周期只需要记住以下三点即可:

1.该种服务完全由系统管理,并遵循已有的服务周期.2.开启一个服务只能由用户在设置中打开,而关闭则只能由用户在设置中关闭或者服务本身通过diableSelf()方法关闭(当然,现在有些第三放软件也可以强制关闭该类型服务)3.系统绑定该服务之后,会调用onServiceConnected()方法,这个方法可以被重写,在其中,你可以做一些初始化的操作.

3.声明AccessibilityService

声明一个AccessibilityService和在menifest中声明其他service一样,但是必须做2件事情:

1.配置<intent-filter>,其name为固定的2.声明BIND_ACCESSIBILITY_SERVICE权限,以便系统能够绑定该服务(4.1版本后要求)

注意:任何一点配置错误,系统都检测不到该服务,因此在menifest中其固定配置如下:

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

4.配置

官网是这样解释的:

AccessibilityService可以添加一些配置信息,目的是接收一些特定的事件.例如:监听特定的包、检索windows内容、在给定的时间范围内只允许从每种类型获取事件一次等等。

android给我们提供2种配置方法:

方法1:meta-data标签方式:

在manifest声明的servce中提供一个meta-data标签,然后通过android:resource指定相应的配置文件(在res目录下创建xml文件,并在其中创建配置文件accessibilityservice.xml):

 <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>

接下来我们看看accessibilityservice.xml中有什么信息:

    <?xml version="1.0" encoding="utf-8"?>    <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"        android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged"        android:accessibilityFeedbackType="feedbackGeneric"        android:accessibilityFlags="flagDefault"        android:canRetrieveWindowContent="true"        android:notificationTimeout="100"        android:packageNames="com.tencent.mm" />

注意:此方法可以设置所有属性。我们在只需要仿照该配置文件根据自己的需求进行修改即可.

方法2:调用setServiceInfo(AccessibilityServiceInfo)方式:

注意

1.这个方法可以在任何时候调用,动态的去改变service配置信息2.这个方法只能用来配置动态属性,如:eventTypes,feedbackType,flags,notificationTimeout,packageNames

通常是在onServiceConnected()进行配置,如下代码:

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

在这里涉及到了AccessibilityServiceInfo类,做个说明:
AccessibilityServiceInfo该类被用于配置AccessibilityService信息,该类中包含了大量用于配置的常量字段及用来xml 属性,比如常见的android:accessibilityEventTypes,android:canRequestFilterKeyEvents,android:packageNames等等,更多信息参见官方文档

这里,简单的对重要属性进行说明:

accessibilityEventTypes:表示该服务对界面中的哪些变化感兴趣,即哪些事件通知,比如窗口打开,滑动,焦点变化,长按等.具体的值可以在AccessibilityEvent类中查到,如typeAllMask表示接受所有的事件通知.accessibilityFeedbackType:表示反馈方式,比如是语音播放,还是震动canRetrieveWindowContent:表示该服务能否访问活动窗口中的内容.也就是如果你希望在服务中获取窗体内容的化,则需要设置其值为true.notificationTimeout:接受事件的时间间隔,通常将其设置为100即可.packageNames:表示对该服务是用来监听哪个包的产生的事件

5.检索窗口内容

仅仅知道事件的信息是不够的,我们还希望通过事件来获取发出该事件(事件源)的信息,比如Button按钮被点击时它的text.一个服务可以配置为可以检索窗口内容,即获取窗口内容.整个窗口内容本质上是关于AccessibilityWindowInfo和AccessibilityNodeInfo的树结构,我称之为内容树.(类似View Tree,但由不完全相同)

需要注意,该服务可能配置了只检测了部分事件,而不是全部事件,这就意味着,当内容树发生变化后,该服务可能并不知道,即该服务无法及时的了解当前的内容树是否发生了变化.比如说,你的服务只检测了点击事件,但是此时界面的输入焦点已经变化,这样整个结点树也发生了变化,但是你的服务却不知道,此时你在结点中拿到的窗口内容可能已经不是最新的了.因此,如果你想及时的获知当前窗口的内容,那么就在配置的时候,设置监听全部事件.

正如上面所提到的,要想获取窗口内容,,在配置AccessibilityService时需设置canRetrieveWindowContent为true.之后,便可以通过AccessibilityEvent.getSource(),findFocus(int),getWindow()或者getRootInActiveWindow()获取窗口内容.

6.启动服务

当我们做完以上操作,便可将app安装到手机.安装成功后,在设置->辅助功能中便可以找到我们的服务.该服务默认处在关闭状态,需要手动开启.

7.获取事件信息

上面我们说道,onAccessibilityEvent(AccessibilityEvent event)是该服务的核心方法,其中参数event封装来自界面相关事件的信息,比如我们可以获得该事件的事件类型,进而根据起类型选择不同的处理方式:

public void onAccessibilityEvent(AccessibilityEvent event) {    int eventType = event.getEventType();    switch (eventType) {        case AccessibilityEvent.TYPE_VIEW_CLICKED:            //界面点击            break;        case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:            //界面文字改动            break;    }}

这里我们对AccessibilityEvent进行简单的说明:
当用户发生发生变化时,系统会发送一系列的AccessibilityEvent事件,比如按钮被电击时会发送TYPE_VIEW_CLICKED类型的事件.

getEventType() 事件类型  getSource() 获取事件源对应的结点信息  getClassName() 获取事件源对应类的类型,比如点击事件是有某个Button产生的,那么此时获取的就是Button的完整类名  getText()获取事件源的文本信息,比如事件是有TextView发出的,此时获取的就是TextView的text属性.如果该事件源是树结构,那么此时获取的是这个树上所有具有text属性的值的集合  isEnabled()事件源(对应的界面控件)是否处在可用状态getItemCount()如果事件源是树结构,将返回该树根节点下子节点的数量

系统不断的产生各种事件,有些是界面控件产生的,有些是系统产生的.对于由界面控件的产生的事件,通常我们将该控件称之为事件源.并不是所有的事件都能通过getSource()方法获取到事件源,比如像通知消息类型的事件(TYPE_NOTIFICATION_STATE_CHANGED).

**注意:**onAccessibilityEvent回调函数非常重要,当我们注册了监听事件的时候,当有事件发生就会通知我们这个函数,但是一定要注意这个函数通知是异步的,

8. 检测服务是否开启

介绍了一些AccessibilityService的基础知识之后,再补充一点关于检测某个服务是否开启的知识.通常来说大体有一下两种方法:

方法一:借助服务管理器AccessibilityManager来判断,但是该方法不能检测app本身开启的服务.

private boolean enabled(String name) {    AccessibilityManager am = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);    List<AccessibilityServiceInfo> serviceInfos = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);    List<AccessibilityServiceInfo> installedAccessibilityServiceList = am.getInstalledAccessibilityServiceList();    for (AccessibilityServiceInfo info : installedAccessibilityServiceList) {        Log.d("MainActivity", "all -->" + info.getId());        if (name.equals(info.getId())) {            return true;        }    }    return false;}

既然谈到了AccessibilityManager,那么在这里我们就做个简单的介绍:
AccessibilityManager是系统级别的服务,用来管理AccessibilityService服务,比如分发事件,查询系统中服务的状态等等,更多信息参考官方文档

getAccessibilityServiceList()   获取服务列表(api 14之后废弃,用下面的方法代替)getInstalledAccessibilityServiceList()  获取已安装到系统的服务列表getEnabledAccessibilityServiceList(int feedbackTypeFlags)   获取已启用的服务列表isEnabled() 判断服务是否启用sendAccessibilityEvent(AccessibilityEvent event)    发送事件

方法二:我们知道大部分的系统属性都在settings中进行设置,比如wifi,蓝牙状态等,而这些信息主要是存储在settings对应的的数据库中(system表和serure表),同样我们也可以通过直接读取setting设置来判断相关服务是否开启:

private boolean checkStealFeature1(String service) {    int ok = 0;    try {        ok = Settings.Secure.getInt(getApplicationContext().getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);    } catch (Settings.SettingNotFoundException e) {    }    TextUtils.SimpleStringSplitter ms = new TextUtils.SimpleStringSplitter(':');    if (ok == 1) {        String settingValue = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);        if (settingValue != null) {            ms.setString(settingValue);            while (ms.hasNext()) {                String accessibilityService = ms.next();                if (accessibilityService.equalsIgnoreCase(service)) {                    return true;                }            }        }

9.我们把AccessibilityServic用歪了

官网上明确说明了,AccessibilityServic是用来帮助残障人使用的。google大神们的想法是非常好的,但是现在对它的使用越来越偏离了它的设计初衷。AccessibilityServic用途非常广泛,下面我们就来总结一下:
1.apk自动安装。
2.微信抢红包和自动回复
3.防卸载自己的app
4.监控密码框(呵呵,这个你真想多了,系统还算有点良心,所有的设置为password类型的EditText都是无法被监控的。)
5.浏览器地址栏劫持、搜索劫持、桌面点击劫持以及防卸载等(参考这篇文章)

参考文献

1.深入了解AccessibilityService

2.官网

3.AccessibilityService从入门到出轨

结尾

AccessibilityServic是一把双刃剑,希望大家能正确对待它。

自己在技术上依旧是个小渣渣,加油勉励自己!

1 0
原创粉丝点击