写在前面
前一段项目的需要(需要自动开启微信并自动添加通讯录好友以及发朋友圈)接触了关于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声明,而且还要做以下两件事:
- 指定intent-filter为 “android.accessibilityservice.AccessibilityService”
- 添加 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_DATA和accessibility-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:
通过节点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); }
第二种是通过节点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的滚动事件,长按事件等。
实际应用——微信抢红包插件
上面我们就介绍了一个辅助功能开发的具体步骤,那么下面就通过一个简单的例子,来实战一下: