您的位置:首页 > 移动开发 > Android开发

Android自动化测试中AccessibilityService获取控件信息

2014-10-26 21:40 423 查看
原著出处(这里顺便对先驱表示感谢,省得我多走弯路,所以这里直接转载过来,我们没有必要做重新造轮子的事情,既然先驱们已经帮我们把轮子造好了):http://blog.csdn.net/itfootball/article/details/21953763

AccessibilityService为一个响应用户发送AccessibilityEvent事件的服务类,主要用作对于一些辅助功能的实现中。对于某些方面有缺陷的人群,可以通过辅助功能反馈给用户。

AccessibilityService的介绍,网络上有很多,我就不多做介绍了。我把怎么实现它跟大家分享一下,以及怎么把它跟Android自动化测试扯上关系的学习过程给大家介绍一下。


第一步 编写辅助程序

创建一个Android项目,创建一个服务类,继承AccessibilityService。



[java] view
plaincopy





import android.accessibilityservice.AccessibilityService;

import android.accessibilityservice.AccessibilityServiceInfo;

import android.annotation.SuppressLint;

import android.util.Log;

import android.view.accessibility.AccessibilityEvent;

import android.view.accessibility.AccessibilityNodeInfo;



@SuppressLint("NewApi")

public class MyAccessibility extends AccessibilityService {

private static final String TAG = "MyAccessibility";

String[] PACKAGES = { "com.android.settings" };



@Override

protected void onServiceConnected() {

Log.i(TAG, "config success!");

AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();

// accessibilityServiceInfo.packageNames = PACKAGES;

accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;

accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;

accessibilityServiceInfo.notificationTimeout = 1000;

setServiceInfo(accessibilityServiceInfo);

}



@SuppressLint("NewApi")

@Override

public void onAccessibilityEvent(AccessibilityEvent event) {

// TODO Auto-generated method stub

int eventType = event.getEventType();

String eventText = "";

Log.i(TAG, "==============Start====================");

switch (eventType) {

case AccessibilityEvent.TYPE_VIEW_CLICKED:

eventText = "TYPE_VIEW_CLICKED";

break;

case AccessibilityEvent.TYPE_VIEW_FOCUSED:

eventText = "TYPE_VIEW_FOCUSED";

break;

case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED:

eventText = "TYPE_VIEW_LONG_CLICKED";

break;

case AccessibilityEvent.TYPE_VIEW_SELECTED:

eventText = "TYPE_VIEW_SELECTED";

break;

case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:

eventText = "TYPE_VIEW_TEXT_CHANGED";

break;

case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:

eventText = "TYPE_WINDOW_STATE_CHANGED";

break;

case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:

eventText = "TYPE_NOTIFICATION_STATE_CHANGED";

break;

case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:

eventText = "TYPE_TOUCH_EXPLORATION_GESTURE_END";

break;

case AccessibilityEvent.TYPE_ANNOUNCEMENT:

eventText = "TYPE_ANNOUNCEMENT";

break;

case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:

eventText = "TYPE_TOUCH_EXPLORATION_GESTURE_START";

break;

case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:

eventText = "TYPE_VIEW_HOVER_ENTER";

break;

case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:

eventText = "TYPE_VIEW_HOVER_EXIT";

break;

case AccessibilityEvent.TYPE_VIEW_SCROLLED:

eventText = "TYPE_VIEW_SCROLLED";

break;

case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:

eventText = "TYPE_VIEW_TEXT_SELECTION_CHANGED";

break;

case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:

eventText = "TYPE_WINDOW_CONTENT_CHANGED";

break;

}

eventText = eventText + ":" + eventType;

Log.i(TAG, eventText);

Log.i(TAG, "=============END=====================");

}



@Override

public void onInterrupt() {

// TODO Auto-generated method stub



}



}



该服务类继承了3个方法:

1.onServiceConnected():绑定服务所用方法,一些初始化的操作放在这里面。

2.onAccessibilityEvent(AccessibilityEvent event):响应AccessibilityEvent的事件,在用户操作的过程中,系统不断的发送sendAccessibiltyEvent(AccessibilityEvent event);然后通过onAccessibilityEvent()可以捕捉到该事件,然后分析。

3.public void onInterrupt():打断获取事件的过程,本例中暂不适用。

在onServiceConnected()我们做了一些初始化的工作,通过AccessibilityServiceInfo设置了AccessibilityService的一些参数

//ccessibilityServiceInfo.packageNames = PACKAGES:响应某个应用的时间,包名为应用的包名;可以用String[]对象传入多包。如果不设置,默认响应所有应用的事件。

ssibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK:响应时间的类型,事件分很多种:单击、长按、滑动等,需要指定,我设置了所有事件都响应:TYPES_ALL_MASK。

cessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN:设置回馈给用户的方式,是语音播出还是振动。这个我们稍后尝试配置一些TTS引擎,让它发音。

cessibilityServiceInfo.notificationTimeout = 1000:看意思就能明白,响应时间的设置。

接下来我们要在配置文件AndroidManifest.xml配置该服务:



[java] view
plaincopy





<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.spreadtrum.accessibilityservice"

android:versionCode="1"

android:versionName="1.0" >



<uses-sdk

android:minSdkVersion="8"

android:targetSdkVersion="17" />



<application

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<service

android:name=".MyAccessibility"

android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >

<intent-filter>

<action android:name="android.accessibilityservice.AccessibilityService" />

</intent-filter>

</service>

</application>



</manifest>

这些设置很重要,因为AccessibilityService服务不是用户去开启的。我们无法用startService()方法启动它,所以我连activity都没写。因为它是通过该配置让系统自动识别的,识别成功后,就会在我们手机里面看到该服务,然后需要手动开启该服务(所以千万别粗心写错,我就是因为少写了一个单词,然后安装上app,一直没反应,它也不报错,害我找错找了半天,后来发现是自己的低级错误。)。

现在我们就可以将应用部署到手机上,部署成功后我们点:设置->辅助功能。找到我们的服务,如图:









看到我们的应用会显示在其中,默认是关闭的;如果你在此没看见你的服务,那么你应该核对一下你的配置文件,确保配置正确;点进去,打开该服务。

然后我们就可以随意在你的手机设备上操作,看看log输出:







我们就会看到我们点击的事件,被服务在后台捕捉到了。接下来,我们尝试着从我们捕捉到的时间中获得我们有用的时间,AccessibilityEvent API 中有一个getSource()方法。得到的是AccessibilityNodeInfo,试着获得这个对象。我们看看是什么东西。


第二步 获得控件信息

在调用这个getSource()方法时,需要事先配置一下,也就是需要android:canRetrieveWindowContent="true",这个属性是API14(android4.0)给出的,它的配置方法受限于需要采用XML初始化的方式配置AccessibilityService,也就是我们现在不在onServiceConnected()里配置初始化信息,改在xml里实现。那么我们现在就要将上面的程序修改一下。首先,删除onServiceConnected()的配置(因为getSource()不是所有的操作都有该方法,所以得在特定的方法中实现该方法,我们选择在TYPE_VIEW_CLICKED里实现),修改后的服务类如下:



[java] view
plaincopy





package com.spreadtrum.accessibilityservice;



import android.accessibilityservice.AccessibilityService;

import android.accessibilityservice.AccessibilityServiceInfo;

import android.annotation.SuppressLint;

import android.util.Log;

import android.view.accessibility.AccessibilityEvent;

import android.view.accessibility.AccessibilityNodeInfo;



@SuppressLint("NewApi")

public class MyAccessibility extends AccessibilityService {

private static final String TAG = "MyAccessibility";

String[] PACKAGES = { "com.android.settings" };



@Override

protected void onServiceConnected() {

Log.i(TAG, "config success!");

}



@SuppressLint("NewApi")

@Override

public void onAccessibilityEvent(AccessibilityEvent event) {

// TODO Auto-generated method stub

int eventType = event.getEventType();

String eventText = "";

switch (eventType) {

case AccessibilityEvent.TYPE_VIEW_CLICKED:

Log.i(TAG, "==============Start====================");

eventText = "TYPE_VIEW_CLICKED";

AccessibilityNodeInfo noteInfo = event.getSource();

Log.i(TAG, noteInfo.toString());

Log.i(TAG, "=============END=====================");

break;

case AccessibilityEvent.TYPE_VIEW_FOCUSED:

eventText = "TYPE_VIEW_FOCUSED";

break;

case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED:

eventText = "TYPE_VIEW_LONG_CLICKED";

break;

case AccessibilityEvent.TYPE_VIEW_SELECTED:

eventText = "TYPE_VIEW_SELECTED";

break;

case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:

eventText = "TYPE_VIEW_TEXT_CHANGED";

break;

case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:

eventText = "TYPE_WINDOW_STATE_CHANGED";

break;

case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:

eventText = "TYPE_NOTIFICATION_STATE_CHANGED";

break;

case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:

eventText = "TYPE_TOUCH_EXPLORATION_GESTURE_END";

break;

case AccessibilityEvent.TYPE_ANNOUNCEMENT:

eventText = "TYPE_ANNOUNCEMENT";

break;

case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:

eventText = "TYPE_TOUCH_EXPLORATION_GESTURE_START";

break;

case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:

eventText = "TYPE_VIEW_HOVER_ENTER";

break;

case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:

eventText = "TYPE_VIEW_HOVER_EXIT";

break;

case AccessibilityEvent.TYPE_VIEW_SCROLLED:

eventText = "TYPE_VIEW_SCROLLED";

break;

case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:

eventText = "TYPE_VIEW_TEXT_SELECTION_CHANGED";

break;

case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:

eventText = "TYPE_WINDOW_CONTENT_CHANGED";

break;

}

}



@Override

public void onInterrupt() {

// TODO Auto-generated method stub



}



}



然后我们要在res文件写创建一个XML文件夹,里面新建accessibility.xml配置文件。







在accessibility.xml配置一些属性:



[java] view
plaincopy





<?xml version="1.0" encoding="UTF-8"?>

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"

android:accessibilityEventTypes="typeViewClicked|typeViewFocused"

android:accessibilityFeedbackType="feedbackSpoken"

android:notificationTimeout="100"

android:canRetrieveWindowContent="true"

/>



重点就是标红的这一句,这样我们就能在程序中得到AccessibilityNodeInfo对象。然后在AndroidManifest.xml配置文件里引用该XML文件:



[html] view
plaincopy





<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.spreadtrum.accessibilityservice"

android:versionCode="1"

android:versionName="1.0" >



<uses-sdk

android:minSdkVersion="8"

android:targetSdkVersion="17" />



<application

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<service

android:name=".MyAccessibility"

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/accessibility" />

</service>

</application>



</manifest>



然后我们部署应用到手机设备,看输出情况:







注意:

在你配置成功后,有可能会报空指针错误,但有可能你已经配置正确,只是你点的对象有问题。因为在API文档中有一句话:如果发起的窗口事件仍然是活动的窗口,该调用将会返回一个对象,否则,会返回null!所以首先确保你刚开始点击的那个控件所在的窗口要是活动的状态,不然也会报null。例如:我刚开始点击HOME界面的所有应用按钮进入到应用的列表,这时候就会报错,如果你是点击通讯录触发服务的,就没问题;原理我也不太清楚,所以不敢大放厥词,反正在一个应用中来回点是没问题的;这个随着深入的了解后,相信会有答案的。


第三步 获得所有窗体控件

已经做到了获得控件信息,但是AccessibilityEvent.getSource()得到的是被点击的单体对象。我们需要获得是整个窗口的对象,在API16中AccessibilityService新引入的方法getRootInActiveWindow()可以满足我们的要求,所以我们用这个方法得到整个窗口,然后遍历得到所有子节点。

[java] view
plaincopy





AccessibilityNodeInfo rowNode = getRootInActiveWindow();

if (rowNode == null) {

Log.i(TAG, "noteInfo is null");

return;

} else {

recycle(rowNode);

}

Log.i(TAG, "==============================================");

其中循环的方法recycle():

[java] view
plaincopy





public void recycle(AccessibilityNodeInfo info) {

if (info.getChildCount() == 0) {

Log.i(TAG, "child widget----------------------------" + info.getClassName());

Log.i(TAG, "showDialog:" + info.canOpenPopup());

Log.i(TAG, "Text:" + info.getText());

Log.i(TAG, "windowId:" + info.getWindowId());

} else {

for (int i = 0; i < info.getChildCount(); i++) {

if(info.getChild(i)!=null){

recycle(info.getChild(i));

}

}

}

}

打印输出的信息有:控件名、是否点击弹出对话框、窗口ID;你还可以查看API里的方法,获得你想要的信息。

部署到手机上,测试结果如下:



总结

三种方式学习完了,对比一下各种方式的优缺点!



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐