天天看点

Android 自动化测试关于悬浮窗点击(DecorView)

    本文对Robotium的悬浮窗实现进行一点调研

    Robotium本身支持悬浮窗点击,这里我们可以参考详细看下源码

    与Uiautomator稍微不一样的是Robotium获取到的是Activtity的rootview,而Uiautomator直接获取的已经不是rootview了,从这个原理可以看出使用Uiautomator实现悬浮窗抓取也许是不可实现的(个人观点)

    在Android中,对于一般的Activity或其对话框,其rootView称为DecorView,其实就是Activity和Dialog外面的那层框,Activity或dialog的层次可以用HierarchyViewer或者UiautomatorViewer查看,HierarchyViewer可以查看到DecorView,但是UiautomatorViewer查看到的只有Activity层了。

Android 自动化测试关于悬浮窗点击(DecorView)
Android 自动化测试关于悬浮窗点击(DecorView)
Android 自动化测试关于悬浮窗点击(DecorView)

    Robotium是如何实现获取到DecorView层源码如下:

1)在android4.2之前,获取类为android.view.WindowManagerImpl,在android4.2之后,获取类为WindowManagerGlobal。

private static Class<?> windowManager;
	static{
		try {
			String windowManagerClassName;
			if (android.os.Build.VERSION.SDK_INT >= 17) {
				windowManagerClassName = "android.view.WindowManagerGlobal";
			} else {
				windowManagerClassName = "android.view.WindowManagerImpl"; 
			}
			windowManager = Class.forName(windowManagerClassName);

		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		} catch (SecurityException e) {
			e.printStackTrace();
		}
	}
           

2)获得类的实例,此类是个单例类,有直接的静态变量可以获取到其实例,4.2及之后的版本其变量名称为sDefaultWindowManager,3.2至4.1,其变量名称为sWindowManager,3.2之前,变量名称为mWindowManager

private void setWindowManagerString(){

		if (android.os.Build.VERSION.SDK_INT >= 17) {
			windowManagerString = "sDefaultWindowManager";

		} else if(android.os.Build.VERSION.SDK_INT >= 13) {
			windowManagerString = "sWindowManager";

		} else {
			windowManagerString = "mWindowManager";
		}
	}
           

3)获取变量的值,从4.4开始类型变为ArrayList<View>,之前为View[]

public View[] getWindowDecorViews()
	{

		Field viewsField;
		Field instanceField;
		try {
			viewsField = windowManager.getDeclaredField("mViews");
			instanceField = windowManager.getDeclaredField(windowManagerString);
			viewsField.setAccessible(true);
			instanceField.setAccessible(true);
			Object instance = instanceField.get(null);
			View[] result;
			if (android.os.Build.VERSION.SDK_INT >= 19) {
				result = ((ArrayList<View>) viewsField.get(instance)).toArray(new View[0]);
			} else {
				result = (View[]) viewsField.get(instance);
			}
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
           

4)对mViews的过滤

mViews中会包含三种类型的View:

1) 当前显示的以及没有显示的Activity的DecorView

2) 当前对话框的DecorView

3) 悬浮框View等其他不属于DecorView的独立View

在搜索控件时,显然需要在最上层界面中搜索,所以搜索范围为:

最上层的Activity/Dialog + 悬浮框

对于悬浮框,robotium中的处理是找出mViews中不属于DecorView类的View,并将其所有子控件引入。

/**
	 * Returns whether a view is a DecorView
 	 * @param view
	 * @return true if view is a DecorView, false otherwise
	 */
	private boolean isDecorView(View view) {
		if (view == null) {
			return false;
		}

		final String nameOfClass = view.getClass().getName();
		return (nameOfClass.equals("com.android.internal.policy.impl.PhoneWindow$DecorView") ||
				nameOfClass.equals("com.android.internal.policy.impl.MultiPhoneWindow$MultiPhoneDecorView") ||
				nameOfClass.equals("com.android.internal.policy.PhoneWindow$DecorView"));
	}
           

对于Activity/Dialog的筛选,Robotium采取对比DrawingTime的方法选出最后绘制的DecorView,其即为最上层Activity/Dialog的DecorView:

/**
	 * Returns the most recent view container
	 *
	 * @param views the views to check
	 * @return the most recent view container
	 */

	private final View getRecentContainer(View[] views) {
		View container = null;
		long drawingTime = 0;
		View view;

		for(int i = 0; i < views.length; i++){
			view = views[i];
			if (view != null && view.isShown() && view.hasWindowFocus() && view.getDrawingTime() > drawingTime) {
				container = view;
				drawingTime = view.getDrawingTime();
			}
		}
		return container;
	}
           

对于悬浮窗的控件ID获取,目前没有找到一个好的工具可以获取到id或者text,用代码捕获内容,代码如下:

public void dumpAllView()
	{
		Log.e("","===== dump all view =====");
		for(View v: getViews())
		{
			Log.e("", v.getId()+":"+v.getClass().getSimpleName());
			Log.e("", v.toString());
		}
	}
           

感谢以下博客,部分内容参考如下

http://www.tuicool.com/articles/UjuM3u

http://www.cnblogs.com/yogin/p/4061050.html