`
Stream.Town
  • 浏览: 23831 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

Robotium 5.0.1 源码解析之控件搜索

阅读更多

     自己和Android的自动化测试已经打了3年交道有余,却一直没有详细了解一下robotium,最近终于抽出时间阅读了其源码,把收获好好记录一番。

     众所周知,Robotium是基于Android的单元测试框架Instrumentation,而robotium对于Instrumentation封装的比较强的地方便是控件搜索,因此首先先来了解一下在robotium中控件的搜索原理,这部分的源码主要位于ViewFetcher.java中。

     

     1.mViews的获取

    要先搜索控件,必须先得到Activity的rootView。在Android中,对于一般的Activity或其对话框,其rootView叫做DecorView,其实就是Activity和Dialog外面的那层框(关于Activity或dialog的层次可以用HierarchyViewer来查看)。

 

   虽然通过Activity类的getWindow().getDecorView可以获取到Activity自身的DecorView,但是无法获取到对话框的,因此Robotium中界面控件是从WindowManagerGlobal(或WindowManagerImpl)中的mViews获取到的。当然mViews中不但包含DecorView,还包含同进程内的所有界面的根节(如悬浮框的根节点)。mView的值的获取过程主要如下:

 

   1) 确定mViews所在类:android 4.2之前,获取类为android.view.WindowManagerImpl4.2及之后,获取类为WindowManagerGlobal

 

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

 

         

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

 

 

   /**
    * Sets the window manager string.
    */
   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). 获取mViews变量的值了,从4.4开始类型变为ArrayList<View>,之前为View[]

                     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);
                     }

 

     

  2.mViews的过滤

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

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

   2) 当前对话框的DecorView

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

 

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

              最上层的Activity/Dialog + 悬浮框

 

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

private final View[] getNonDecorViews(View[] views) {
		 View[] decorViews = null;

		 if(views != null) {
			 decorViews = new View[views.length];

			 int i = 0;
			 View view;

			 for (int j = 0; j < views.length; j++) {
				 view = views[j];
				 if (view != null && !(view.getClass().getName()
						 .equals("com.android.internal.policy.impl.PhoneWindow$DecorView"))) {
					 decorViews[i] = view;
					 i++;
				 }
			 }
		 }
		 return decorViews;
	 }

    

    对于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;
	 }

 

 

  3.控件过滤&控件列表生成

     得到悬浮框的根节点和最上层的DecorView后,robotium会将所有View统一添加到一个ArrayList中生成控件列表。添加方法本身很简单,就是一个简单的递归,但需要注意的是此处有一个onlySufficientlyVisible的判断。onlySufficientlyVisibleViewFetcher中最常见的一个变量,其表示是否过滤掉显示不完全的控件,即onlySufficientlyVisibletrue时表示只在显示完全的控件中搜索目标,为false时表示在所有控件中搜索目标。具体代码为下面的addChildren函数:

 

 

private void addChildren(ArrayList<View> views, ViewGroup viewGroup, boolean onlySufficientlyVisible) {
		if(viewGroup != null){
			for (int i = 0; i < viewGroup.getChildCount(); i++) {
				final View child = viewGroup.getChildAt(i);

				if(onlySufficientlyVisible && isViewSufficientlyShown(child))
					views.add(child);

				else if(!onlySufficientlyVisible)
					views.add(child);

				if (child instanceof ViewGroup) {
					addChildren(views, (ViewGroup) child, onlySufficientlyVisible);
				}
			}
		}
	}
 从上面的代码可以看出,当onlySufficientlyVisibletrue时,robotium会对控件的可见不可见进行检查。不过这里的可见不可见不是指VisibleInvisibleRobotium过滤Invisible控件的方法是RobotiumUtils.removeInvisibleViews,原理是利用view.isShown()方法),而是指由于界面滚动而导致的没有显示或显示不完全。继续看RobotiumSufficientlyVisible是怎么判断的:

 

 

public final boolean isViewSufficientlyShown(View view){
		final int[] xyView = new int[2];
		final int[] xyParent = new int[2];

		if(view == null)
			return false;

		final float viewHeight = view.getHeight();
		final View parent = getScrollOrListParent(view);
		view.getLocationOnScreen(xyView);

		if(parent == null){
			xyParent[1] = 0;
		}
		else{
			parent.getLocationOnScreen(xyParent);
		}

		if(xyView[1] + (viewHeight/2.0f) > getScrollListWindowHeight(view))
			return false;

		else if(xyView[1] + (viewHeight/2.0f) < xyParent[1])
			return false;

		return true;
	}
    
    代码中getScrollOrListParent是获取控件所属的ListView或ScrollView,可能是控件本身也可能是空。getScrollListWindowHeight函数用于获取控件所属的ListView或ScrollView最下面边界的Y坐标。因此
xyView[1] + (viewHeight/2.0f) > getScrollListWindowHeight(view)

这个判断就表示控件有超过一半的面积被隐藏在了父控件的下方,而

(xyView[1] + (viewHeight/2.0f) < xyParent[1]

 

则表示控件有超过一半的面积被隐藏在了父控件的上方,这两种情况都被Robotium判断为不满足SufficientlyVisible的(不过好像没有判断横向的?)。

 

根据onlySufficientlyVisible过滤掉相应控件后,robotium便完成了控件列表的生成工作,之后的搜索就可直接在列表中进行查找了。

 

有的时候要搜索指定类型的控件,可以按照类型对控件列表进行再一次的过滤,ViewFetcher中的代码如下:

   

    public <T extends View> ArrayList<T> getCurrentViews(Class<T> classToFilterBy, View parent) {
              ArrayList<T> filteredViews = new ArrayList<T>();
              List<View> allViews = getViews(parent, true);
              for(View view : allViews){
                     if (view != null && classToFilterBy.isAssignableFrom(view.getClass())) {
                            filteredViews.add(classToFilterBy.cast(view));
                     }
              }
              allViews = null;
              return filteredViews;
       }
 

 

可以看到,robotium直接利用了Class. isAssignableFrom进行类型的匹配。

 

  4.文本搜索

获得了控件列表,可以开始搜索指定的目标控件了,先从我们最常用的文本搜索开始,看看robotium的搜索流程。搜索过程的代码主要位于Searcher.java中,主要功能在两个searchFor函数中实现,通过嵌套完成目标的搜索。

第一层

 

	public <T extends TextView> T searchFor(final Class<T> viewClass, final String regex, int expectedMinimumNumberOfMatches, final long timeout, final boolean scroll, final boolean onlyVisible) {
		//修正非法的expectedMinimumNumberOfMatches
		if(expectedMinimumNumberOfMatches < 1) {
			expectedMinimumNumberOfMatches = 1;
		}

		//定义一个Callable给下层searchFor使用,可以直接获取到符合条件的控件列表
		final Callable<Collection<T>> viewFetcherCallback = new Callable<Collection<T>>() {
			@SuppressWarnings("unchecked")
			public Collection<T> call() throws Exception {
				sleeper.sleep();
				//从当前的Android View中获取到符合viewClass的控件列表
				ArrayList<T> viewsToReturn = viewFetcher.getCurrentViews(viewClass);

				
				if(onlyVisible){
					//过滤掉Invisible的控件
					viewsToReturn = RobotiumUtils.removeInvisibleViews(viewsToReturn);
				}
		 
				//robotium支持在webView中查找网页控件,因此若目标控件是TextView或是TextView的子类,
				//会把网页中的文本框也加到控件列表中。
				if(viewClass.isAssignableFrom(TextView.class)) {
					viewsToReturn.addAll((Collection<? extends T>) webUtils.getTextViewsFromWebView());
				}
				return viewsToReturn;
			}
		};

		try {
			//调用下层searchFor继续搜索
			return searchFor(viewFetcherCallback, regex, expectedMinimumNumberOfMatches, timeout, scroll);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
 

 

   这个函数的主要功能有二,一是对非法的expectedMinimumNumberOfMatches进行修正,二是为下一层searchFor提供一个Callable,里面定义好了控件列表的获取过程。

 

   1)      expectedMinimumNumberOfMatches:这个参数表示搜索目标最小发现数目,当一个界面中有多个控件满足搜索条件,通过此参数可以指定想要获取的是第几个。

 

  2)      Callable<Collection<T>> viewFetcherCallback:定义了控件列表(即搜索范围)的获取过程。首先利用前面提到的viewFetcher.getCurrentViews(viewClass)获取一个初步的列表;再通过RobotiumUtils.removeInvisibleViews(viewsToReturn)过滤掉不可见控件;最后由于Robotium支持webView内部搜索(Robotium的名字貌似也是来源于Selenium),所以当搜索目标是一个TextView时,Robotium还会调用webUtils.getTextViewsFromWebView()把网页中的文本框加入到搜索范围中。

 

第二层

 

	public <T extends TextView> T searchFor(Callable<Collection<T>> viewFetcherCallback, String regex, int expectedMinimumNumberOfMatches, long timeout, boolean scroll) throws Exception {
		final long endTime = SystemClock.uptimeMillis() + timeout;	
		Collection<T> views;

		while (true) {
			
			final boolean timedOut = timeout > 0 && SystemClock.uptimeMillis() > endTime;

			if(timedOut){
				logMatchesFound(regex);
				return null;
			}
 
			//获取符合条件的控件列表
			views = viewFetcherCallback.call();

			for(T view : views){
				if (RobotiumUtils.getNumberOfMatches(regex, view, uniqueTextViews) == expectedMinimumNumberOfMatches) {
					uniqueTextViews.clear();
					return view;
				}
			}
			if(scroll && !scroller.scrollDown()){
				logMatchesFound(regex);
				return null; 
			}
			if(!scroll){
				logMatchesFound(regex);
				return null; 
			}
		}
	}
 

 

    这一层的主要功能就是循环在控件列表中找到含有指定文本的控件,直至超时或发现了   expectedMinimumNumberOfMatches数目的目标控件,这个过程中需要注意的有四点:

1)    uniqueTextViews:为了防止找到的控件存在重复,此处用了一个uniqueTextViews集合来存储搜索到的结果。

   2)    文本的匹配:直接利用了Pattern进行正则匹配,但比对的内容不只包括view.getText(),还包括 view.getError()以及view.getHint()

   3)    自动滚动:当开启了scroll选项,并且在当前的界面没有找到足够的目标时,Robotium会自动滚动界面 (不过好像只会向下?):

if(scroll && !scroller.scrollDown()

   4)     滚动时robotium只会滚动drawingTime最大的控件(通过ViewFetcher.getFreshestView()),所以一个界面中有两个可滚动控件时,robotium只会滚动其中一个。

 

    暂时先记到这里,滚动就留待下一篇吧。

分享到:
评论

相关推荐

    robotium-solo-5.0.1.jar

    robotium 最新版本5.0.1 robotium-solo-5.0.1.jar

    robotium-solo-5.0.1-sources.zip

    Robotium Solo 5.0.1 版本的源码提供了深入理解其工作原理的机会,这对于开发者和测试工程师来说是极其宝贵的资源。这个压缩包文件"robotium-solo-5.0.1-sources.zip"包含了 Robotium Solo 5.0.1 版本的所有源代码,...

    spring5.0.1源码包

    虽然Spring Boot不在这个源码包中,但Spring 5.0.1源码有助于理解Spring Boot的工作原理,因为它们共享很多基础组件。 通过研究Spring 5.0.1的源码,开发者不仅可以深入理解框架的工作机制,还能学习到优秀的软件...

    ExtJs-5.0.1

    ExtJs5.0.1官方源码,方便找不到历史版本代码的同学。

    PotaPoco 5.0.1源码

    PotaPoco 5.0.1最新版源码。 如:Insert时返回的不是true,而是1了。

    ffmpeg-5.0.1

    FFmpeg的强大之处在于其灵活性和可扩展性,用户可以根据需求自定义参数,实现复杂的多媒体处理任务。同时,FFmpeg还广泛应用于服务器端的直播系统、视频处理软件以及自动化脚本中,是多媒体处理领域不可或缺的工具。

    robotium-solo-5.2.1.jar

    robotium 最新版本5.0.1 robotium-solo-5.0.1.jar

    android5.0.1-api21 官网源码

    《深入解析Android 5.0.1 (API 21) 源码》 Android 5.0.1 (API 21) 是Google在2014年底推出的一个重要版本,它带来了诸多新特性、性能优化以及用户体验的提升。这次更新不仅涉及系统的底层框架,还对用户界面和应用...

    Elasticsearch-5.0.1-src源码

    Elasticsearch是一款开源的全文搜索引擎,它以其高效、可扩展性以及...通过对Elasticsearch 5.0.1源码的深入研究,开发者不仅可以理解其工作原理,还能根据需求进行定制化开发,提升搜索和分析解决方案的效率和质量。

    74CMS_v5.0.1后台RCE分析1

    【74CMS_v5.0.1后台RCE分析】主要涉及的是74CMS内容管理系统的一个远程代码执行(Remote Code Execution, RCE)漏洞。这个漏洞发生在系统的后台管理部分,需要用户具有管理员权限才能利用。漏洞的触发点在于系统允许...

    android-5.0.1-sources

    源码解析可以帮助我们理解View的绘制流程、触摸事件处理,以及动画引擎的工作方式,有助于创建更具视觉吸引力的应用。 五、系统服务与权限管理 Android系统服务如电源管理、网络管理、位置服务等,都在源码中有...

    ACDSee5.0.1精简美化

    在图像管理方面,ACDSee5.0.1具有强大的分类和搜索功能。用户可以创建自定义的文件夹、相册和类别,通过标签和元数据对图片进行整理。同时,其内置的智能搜索功能可以根据文件名、日期、甚至图像内容来快速定位到...

    74cms 骑士人才系统 v5.0.1 正式版

    总的来说,74cms骑士人才系统v5.0.1正式版是一个功能完备、易于定制的人才招聘解决方案,适用于各类企业。通过其高效的执行、丰富的功能和灵活的设计,企业能够更好地管理和运营自己的招聘流程,吸引并留住优秀的...

    VNC-5.0.1.rar

    VNC-5.0.1.rar VNC-5.0.1.rar VNC-5.0.1.rar VNC-5.0.1.rar

    phpyun_v5.0.1.beta.0703_云人才系统_求职_PHPYun_beta_phpyunv5.0.1_

    《PHPYun_v5.0.1 Beta版:云人才系统的深度解析》 PHPYun_v5.0.1.beta.0703,作为一款备受瞩目的云人才系统,旨在为求职者与雇主提供高效、便捷的人力资源管理解决方案。这款系统集成了多种功能,不仅能满足企业的...

    Net实战商用源码---最新TourEx旅游系统v5.0.1多城市版源码 旅游网源码.rar

    【Net实战商用源码---最新TourEx旅游系统v5.0.1多城市版源码 旅游网源码.rar】这个压缩包文件包含了基于.Net技术开发的TourEx旅游系统的一个完整版本,主要用于构建多城市版的在线旅游服务平台。在深入探讨这个系统...

    robotium-solo-3.4.1.jar

    robotium-solo-3.4.1.jar,

    loraWAN网关源码V5.0.1

    源码中会包含网关的配置、初始化、数据包解析、传输以及与其他网络组件交互的代码。可能还包括错误处理和日志记录功能。 3. **多通道操作**:LoRaWAN允许网关同时监听多个频率,以提高网络容量。在V5.0.1中,源码...

    ACDSee 5.0.1 含注册码

    《ACDSee 5.0.1:经典图像查看与管理工具详解》 ACDSee,作为一款全球广泛使用的图像查看和管理软件,其5.0.1版本因其丰富的功能和用户友好的界面,深受广大用户的喜爱。这款软件在早期的数字图像处理领域占据了...

    spring-5.0.1jar包

    首先,Spring框架的核心特性之一是依赖注入(Dependency Injection,DI),它使得组件间的依赖关系得以解耦,增强了代码的可测试性和可维护性。在Spring 5.0.1中,依赖注入机制更加成熟,开发者可以通过注解(@...

Global site tag (gtag.js) - Google Analytics