本文對Robotium的懸浮窗實作進行一點調研
Robotium本身支援懸浮窗點選,這裡我們可以參考詳細看下源碼
與Uiautomator稍微不一樣的是Robotium擷取到的是Activtity的rootview,而Uiautomator直接擷取的已經不是rootview了,從這個原理可以看出使用Uiautomator實作懸浮窗抓取也許是不可實作的(個人觀點)
在Android中,對于一般的Activity或其對話框,其rootView稱為DecorView,其實就是Activity和Dialog外面的那層框,Activity或dialog的層次可以用HierarchyViewer或者UiautomatorViewer檢視,HierarchyViewer可以檢視到DecorView,但是UiautomatorViewer檢視到的隻有Activity層了。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2QvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2LcZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DMwgzNycjM5EDOyETM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
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