天天看點

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

繼續閱讀