转自子云心的博客
辅助功能定义
对于那些由于视力、听力或其它身体原因导致不能方便使用Android智能手机的用户,Android提供了Accessibility功能和服务帮助这些用户更加简单地操作设备,包括文字转语音(不支持中文)、触觉反馈、手势操作、轨迹球和手柄操作。开发者可以搭建自己的Accessibility服务,这可以加强可用性,例如声音提示,物理反馈,和其他可选的操作模式。
随着Android版本的不断升级,AndroidAccessibility功能也越来越强大,Android 4.0版本以前,系统辅助服务功能比较单一,仅仅能过单向获取窗口元素信息,比如获取输入框用户输入内容。到Android 4.1版本以后,系统辅助服务增加了与窗口元素的双向交互,此时可以通过辅助功能服务操作窗口元素,比如点击按钮等。
现实辅助功能准备工作
1、新建自己的AccessibilityService类
想要让自己程序实辅助功能,首先第一步就是要新建类并继承AccessibilityService类。继承自AccessibilityService的子类里实现几个重要的重载方法:
方法 | 描述 |
onAccessibilityEvent() | 必须。通过这个函数可以接收系统发送来的AccessibilityEvent,接收来的AccessibilityEvent是经过过滤的,过滤是在配置工作时设置的。 |
onInterrupt() | 必须。这个在系统想要中断AccessibilityService返给的响应时会调用。在整个生命周期里会被调用多次。 |
onServiceConnected() | 可选。在系统成功连接上这个AccessibilityService会调用。在这个方法里你可以做一下初始化工作。 |
onUnbind() | 可选。在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作。 |
2、新建AccessibilityService配置文件
1
2
|
<!--?xml version=
"1.0"
encoding=
"utf-8"
?-->
</accessibility-service>
|
属性分别代表意义:
属性 | 描述 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
description | 就是设置里辅助功能项的备注说明(不可在运行时修改)。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
accessibilityEventTypes | 设置响应事件的类型,typeAllMask当然就是响应所有类型的事件了。当然还有单击、长按、滑动等(多个值由“|”分隔)(可在运行时修改)。 值有:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
accessibilityFeedbackType | 设置回馈给用户的方式,有语音播出和振动(多个值由“|”分隔)(可在运行时修改)。 值有:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
notificationTimeout | 相同类型的两个事件之间的最小时间发送到该服务(可在运行时修改)。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
accessibilityFlags | 可访问性(多个值由“|”分隔)(可在运行时修改)。 值有:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
packageNames | 响应的程序的包名(多个值由“,”分隔)(可在运行时修改)。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
canRetrieveWindowContent | 是否能够检索活动窗口的内容(不可在运行时修改)。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
settingsActivity | 允许用户修改此服务的设置的活动组件名称,(如果不需要在运行时修改设置,可忽略该项)(不可在运行时修改)。 修改设置,可使用:android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo). |
3、AndroidManifest里注册服务
AndroidManifest.XML文件添加以下配置
1
2
3
4
5
6
|
<service android:label=
"@string/XXXXX"
android:name=
".XXXXX"
android:permission=
"android.permission.BIND_ACCESSIBILITY_SERVICE"
>
// 系统权限
<intent-filter>
</action></intent-filter>
<meta-data android:name=
"android.accessibilityservice"
android:resource=
"@xml/XXXXX"
>
// XXXXX为上面新建的配置XML文件
</meta-data></service>
|
获取UI元素
在onAccessibilityEvent中,使用参数event的getSource方法获取到的AccessibilityNodeInfo实例,即为触发这次事件的UI节点。获取到当前界面UITree的根节点可以使用findAccessibilityNodeInfosByText或者findAccessibilityNodeInfosByViewId方法。需要注意findAccessibilityNodeInfosByText在获取UI元素时的判断逻辑是包含而非等于。
示例代码如:
1
2
|
Listnodes = event.getSource().findAccessibilityNodeInfosByText(
"立即安装"
);
Listnodes = event.getSource().findAccessibilityNodeInfosByViewId(
"txt_1"
);</accessibilitynodeinfo></accessibilitynodeinfo>
|
模拟用户操作
模拟点击
获取界面上UI元素之后,可根据元素是否可响应操作进行相应的处理。例如,如果UI元素是一个有效的按钮,则可以使用下面的代码来进行模拟点击:
1
|
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
同理,如果UI元素是一个有效的列表,则可以使用下面的代码进行模拟滑动:
1
|
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
|
模拟物理键
也可以模拟物理键的操作,例如模拟按下返回键的代码是:
1
|
AccessibilityService.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
|
模拟打开消息通知栏
在上面介绍accessibilityEventTypes中提到有一个事件: typeNotificationStateChanged,它便是消息通知栏状态发生改变变触发。一些红包外挂工具里头,一有红包就提醒的原理便是在onAccessibilityEvent中监听该事件,然后判断关键字并打开通知,示例关键代码如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
if
(event.getEventType() != AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED){
return
;
}
List<charsequence>texts = event.getText();
if
(texts.size() <=
0
|| !mAutoTrack) {
return
;
}
for
(CharSequence text : texts) {
String content = text.toString();
if
(!content.contains(
"[微信红包]"
)){
continue
;
}
if
(event.getParcelableData() ==
null
|| !(event.getParcelableData() instanceofNotification)) {
continue
;
}
Notification notification = (Notification)event.getParcelableData();
PendingIntent pendingIntent =notification.contentIntent;
try
{
pendingIntent.send();
}
catch
(PendingIntent.CanceledException e) {
e.printStackTrace();
}
}</charsequence>
|
语音播放
首先,定义一个TextToSpeech对象,并在服务连上后进行初始化,示例代码如:
1
2
3
4
5
6
7
8
9
10
|
private
TextToSpeech mTts;
……
mTts = newTextToSpeech(getApplicationContext(),
new
TextToSpeech.OnInitListener() {
@Override
public
void
onInit(
int
status) {
if
(status == TextToSpeech.SUCCESS) {
mTts.setLanguage(Locale.US);
}
}
});
|
执行语音播放代码:
1
|
mTts.speak(
"hello"
,TextToSpeech.QUEUE_FLUSH,
null
);
|
最后别忘记释放资源:
1
|
mTts.shutdown();
|
建议:
在开发APP想支持语音播功能时,建议在用户界面控件中,通过使用android:contentDescription属性来描控件特性。
对于EditText控件,提供了一个android:hint属性代替了contentDescription属性
判断是否开启辅助功能
1
2
3
4
|
intaccessibilityEnabled =Settings.Secure.getInt(context.getApplicationContext().getContentResolver(),android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
if
(accessibilityEnabled ==
1
) {
String settingValue =Settings.Secure.getString(context.getApplicationContext().getContentResolver(),Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
}
|
如果手机中开启了一些APP的辅助功能,settingValue的值为:APP1包名/APP1继承AccessibilityService类全名: APP2包名/APP2继承AccessibilityService类全名
使用Dump View Hierarchy for UI Automator解析UI界面视图帮助查找节点对象
一、
二、
三、