出處:http://stream-town.iteye.com/blog/2021063
創(chuàng)新互聯(lián)公司主營(yíng)觀山湖網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,app開(kāi)發(fā)定制,觀山湖h5重慶小程序開(kāi)發(fā)搭建,觀山湖網(wǎng)站營(yíng)銷(xiāo)推廣歡迎觀山湖等地區(qū)企業(yè)咨詢(xún)
自己和Android的自動(dòng)化測(cè)試已經(jīng)打了3年交道有余,卻一直沒(méi)有詳細(xì)了解一下robotium,最近終于抽出時(shí)間閱讀了其源碼,把收獲好好記錄一番。
眾所周知,Robotium是基于Android的單元測(cè)試框架Instrumentation,而robotium對(duì)于Instrumentation封裝的比較強(qiáng)的地方便是控件搜索,因此首先先來(lái)了解一下在robotium中控件的搜索原理,這部分的源碼主要位于ViewFetcher.java中。
1.mViews的獲取
要先搜索控件,必須先得到Activity的rootView。在Android中,對(duì)于一般的Activity或其對(duì)話框,其rootView叫做DecorView,其實(shí)就是Activity和Dialog外面的那層框(關(guān)于Activity或dialog的層次可以用HierarchyViewer來(lái)查看)。
雖然通過(guò)Activity類(lèi)的getWindow().getDecorView可以獲取到Activity自身的DecorView,但是無(wú)法獲取到對(duì)話框的,因此Robotium中界面控件是從WindowManagerGlobal(或WindowManagerImpl)中的mViews獲取到的。當(dāng)然mViews中不但包含DecorView,還包含同進(jìn)程內(nèi)的所有界面的根節(jié)(如懸浮框的根節(jié)點(diǎn))。mView的值的獲取過(guò)程主要如下:
1) 確定mViews所在類(lèi):android 4.2之前,獲取類(lèi)為android.view.WindowManagerImpl,4.2及之后,獲取類(lèi)為WindowManagerGlobal
Java代碼

- String windowManagerClassName;
- if (android.os.Build.VERSION.SDK_INT >= 17) {
- windowManagerClassName = "android.view.WindowManagerGlobal";
- } else {
- windowManagerClassName = "android.view.WindowManagerImpl";
- }
- windowManager = Class.forName(windowManagerClassName)
2). 獲得類(lèi)的實(shí)例:此類(lèi)是個(gè)單例類(lèi),有直接的靜態(tài)變量可以獲取到其實(shí)例, 4.2及之后的版本其變量名為sDefaultWindowManager,3.2至4.1,其變量名為sWindowManager,3.2之前,其變量名為mWindowManager。
Java代碼

- /**
- * 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開(kāi)始類(lèi)型變?yōu)锳rrayList<View>,之前為View[]
Java代碼

- 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的過(guò)濾
mViews中會(huì)包含三種類(lèi)型的View:
1) 當(dāng)前顯示的以及沒(méi)有顯示的Activity的DecorView
2) 當(dāng)前對(duì)話框的DecorView
3) 懸浮框View等其他不屬于DecorView的獨(dú)立View
在搜索控件時(shí),顯然需要在最上層界面中搜索,所以搜索范圍為:
最上層的Activity/Dialog + 懸浮框
對(duì)于懸浮框,robotium中的處理是找出mViews中不屬于DecorView類(lèi)的View,并將其所有子控件引入。
Java代碼

- 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;
- }
對(duì)于Activity/Dialog的篩選,Robotium采取對(duì)比DrawingTime的方法選出最后繪制的DecorView,其即為最上層Activity/Dialog的DecorView:
Java代碼

- /**
- * 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.控件過(guò)濾&控件列表生成
得到懸浮框的根節(jié)點(diǎn)和最上層的DecorView后,robotium會(huì)將所有View統(tǒng)一添加到一個(gè)ArrayList中生成控件列表。添加方法本身很簡(jiǎn)單,就是一個(gè)簡(jiǎn)單的遞歸,但需要注意的是此處有一個(gè)onlySufficientlyVisible的判斷。onlySufficientlyVisible是ViewFetcher中最常見(jiàn)的一個(gè)變量,其表示是否過(guò)濾掉顯示不完全的控件,即onlySufficientlyVisible為true時(shí)表示只在顯示完全的控件中搜索目標(biāo),為false時(shí)表示在所有控件中搜索目標(biāo)。具體代碼為下面的addChildren函數(shù):
Java代碼

- 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);
- }
- }
- }
- }
從上面的代碼可以看出,當(dāng)onlySufficientlyVisible為true時(shí),robotium會(huì)對(duì)控件的可見(jiàn)不可見(jiàn)進(jìn)行檢查。不過(guò)這里的可見(jiàn)不可見(jiàn)不是指Visible或Invisible(Robotium過(guò)濾Invisible控件的方法是RobotiumUtils.removeInvisibleViews,原理是利用view.isShown()方法),而是指由于界面滾動(dòng)而導(dǎo)致的沒(méi)有顯示或顯示不完全。繼續(xù)看Robotium對(duì)SufficientlyVisible是怎么判斷的:
Java代碼

- 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;
- }
代碼中g(shù)etScrollOrListParent是獲取控件所屬的ListView或ScrollView,可能是控件本身也可能是空。getScrollListWindowHeight函數(shù)用于獲取控件所屬的ListView或ScrollView最下面邊界的Y坐標(biāo)。因此
Java代碼

- xyView[1] + (viewHeight/2.0f) > getScrollListWindowHeight(view)
這個(gè)判斷就表示控件有超過(guò)一半的面積被隱藏在了父控件的下方,而
Java代碼

- (xyView[1] + (viewHeight/2.0f) < xyParent[1]
則表示控件有超過(guò)一半的面積被隱藏在了父控件的上方,這兩種情況都被Robotium判斷為不滿足SufficientlyVisible的(不過(guò)好像沒(méi)有判斷橫向的?)。
根據(jù)onlySufficientlyVisible過(guò)濾掉相應(yīng)控件后,robotium便完成了控件列表的生成工作,之后的搜索就可直接在列表中進(jìn)行查找了。
有的時(shí)候要搜索指定類(lèi)型的控件,可以按照類(lèi)型對(duì)控件列表進(jìn)行再一次的過(guò)濾,ViewFetcher中的代碼如下:
Java代碼

- 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進(jìn)行類(lèi)型的匹配。
4.文本搜索
獲得了控件列表,可以開(kāi)始搜索指定的目標(biāo)控件了,先從我們最常用的文本搜索開(kāi)始,看看robotium的搜索流程。搜索過(guò)程的代碼主要位于Searcher.java中,主要功能在兩個(gè)searchFor函數(shù)中實(shí)現(xiàn),通過(guò)嵌套完成目標(biāo)的搜索。
第一層
Java代碼

- <strong> 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;
- }
-
- //定義一個(gè)Callable給下層searchFor使用,可以直接獲取到符合條件的控件列表
- final Callable<Collection<T>> viewFetcherCallback = new Callable<Collection<T>>() {
- @SuppressWarnings("unchecked")
- public Collection<T> call() throws Exception {
- sleeper.sleep();
- //從當(dāng)前的Android View中獲取到符合viewClass的控件列表
- ArrayList<T> viewsToReturn = viewFetcher.getCurrentViews(viewClass);
-
-
- if(onlyVisible){
- //過(guò)濾掉Invisible的控件
- viewsToReturn = RobotiumUtils.removeInvisibleViews(viewsToReturn);
- }
-
- //robotium支持在webView中查找網(wǎng)頁(yè)控件,因此若目標(biāo)控件是TextView或是TextView的子類(lèi),
- //會(huì)把網(wǎng)頁(yè)中的文本框也加到控件列表中。
- if(viewClass.isAssignableFrom(TextView.class)) {
- viewsToReturn.addAll((Collection<? extends T>) webUtils.getTextViewsFromWebView());
- }
- return viewsToReturn;
- }
- };
-
- try {
- //調(diào)用下層searchFor繼續(xù)搜索
- return searchFor(viewFetcherCallback, regex, expectedMinimumNumberOfMatches, timeout, scroll);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- </strong>
這個(gè)函數(shù)的主要功能有二,一是對(duì)非法的expectedMinimumNumberOfMatches進(jìn)行修正,二是為下一層searchFor提供一個(gè)Callable,里面定義好了控件列表的獲取過(guò)程。
1) expectedMinimumNumberOfMatches:這個(gè)參數(shù)表示搜索目標(biāo)最小發(fā)現(xiàn)數(shù)目,當(dāng)一個(gè)界面中有多個(gè)控件滿足搜索條件,通過(guò)此參數(shù)可以指定想要獲取的是第幾個(gè)。
2) Callable<Collection<T>> viewFetcherCallback:定義了控件列表(即搜索范圍)的獲取過(guò)程。首先利用前面提到的viewFetcher.getCurrentViews(viewClass)獲取一個(gè)初步的列表;再通過(guò)RobotiumUtils.removeInvisibleViews(viewsToReturn)過(guò)濾掉不可見(jiàn)控件;最后由于Robotium支持webView內(nèi)部搜索(Robotium的名字貌似也是來(lái)源于Selenium),所以當(dāng)搜索目標(biāo)是一個(gè)TextView時(shí),Robotium還會(huì)調(diào)用webUtils.getTextViewsFromWebView()把網(wǎng)頁(yè)中的文本框加入到搜索范圍中。
第二層
Java代碼

- <strong> 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;
- }
- }
- }</strong>
這一層的主要功能就是循環(huán)在控件列表中找到含有指定文本的控件,直至超時(shí)或發(fā)現(xiàn)了 expectedMinimumNumberOfMatches數(shù)目的目標(biāo)控件,這個(gè)過(guò)程中需要注意的有四點(diǎn):
1) uniqueTextViews:為了防止找到的控件存在重復(fù),此處用了一個(gè)uniqueTextViews集合來(lái)存儲(chǔ)搜索到的結(jié)果。
2) 文本的匹配:直接利用了Pattern進(jìn)行正則匹配,但比對(duì)的內(nèi)容不只包括view.getText(),還包括 view.getError()以及view.getHint()
3) 自動(dòng)滾動(dòng):當(dāng)開(kāi)啟了scroll選項(xiàng),并且在當(dāng)前的界面沒(méi)有找到足夠的目標(biāo)時(shí),Robotium會(huì)自動(dòng)滾動(dòng)界面 (不過(guò)好像只會(huì)向下?):
Java代碼

- if(scroll && !scroller.scrollDown()
4) 滾動(dòng)時(shí)robotium只會(huì)滾動(dòng)drawingTime最大的控件(通過(guò)ViewFetcher.getFreshestView()),所以一個(gè)界面中有兩個(gè)可滾動(dòng)控件時(shí),robotium只會(huì)滾動(dòng)其中一個(gè)。
名稱(chēng)欄目:Robotium5.0.1源碼解析之控件搜索
分享路徑:http://sd-ha.com/article10/jsjsdo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司、網(wǎng)站收錄、網(wǎng)站制作、ChatGPT、虛擬主機(jī)、網(wǎng)站建設(shè)
廣告
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源:
創(chuàng)新互聯(lián)