uiautomator测试中scrollForward方法使用失灵

1.在4.4.2版本中滑动屏幕时scrollForward()不起作用而swipeLeft()可以滑动?

(如果想知道结果,直接看总结)


先来看下各自的源码(4.1.1版)


swipeLeft


 public boolean swipeLeft(int steps) throws UiObjectNotFoundException {
        Rect rect = getBounds();
        if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
            return false; // too small to swipe
        return getInteractionController().swipe(rect.right - SWIPE_MARGIN_LIMIT,
                rect.centerY(), rect.left + SWIPE_MARGIN_LIMIT, rect.centerY(), steps);
    }

可以看出里面调用了InteractionController.swipe()


    public boolean swipe(int downX, int downY, int upX, int upY, int steps) {
        boolean ret = false;
        int swipeSteps = steps;
        double xStep = 0;
        double yStep = 0;

        // avoid a divide by zero
        if(swipeSteps == 0)
            swipeSteps = 1;

        xStep = ((double)(upX - downX)) / swipeSteps;
        yStep = ((double)(upY - downY)) / swipeSteps;

        // first touch starts exactly at the point requested
        ret = touchDown(downX, downY);
        for(int i = 1; i < swipeSteps; i++) {
            ret &= touchMove(downX + (int)(xStep * i), downY + (int)(yStep * i));
            if(ret == false)
                break;
            // set some known constant delay between steps as without it this
            // become completely dependent on the speed of the system and results
            // may vary on different devices. This guarantees at minimum we have
            // a preset delay.
            SystemClock.sleep(5);
        }
        ret &= touchUp(upX, upY);
        return(ret);
    }

在swipe方法里在起始点touchDown,然后中间点都是touchMove,在结束点touchUp


scrollForward


public boolean scrollForward(int steps) {
        Log.d(LOG_TAG, "scrollForward() on selector = " + getSelector());
        AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
        if(node == null) {
            // Object Not Found
            return false;
        }
        Rect rect = new Rect();;
        node.getBoundsInScreen(rect);

        int downX = 0;
        int downY = 0;
        int upX = 0;
        int upY = 0;

        // scrolling is by default assumed vertically unless the object is explicitly
        // set otherwise by setAsHorizontalContainer()
        if(mIsVerticalList) {
            int swipeAreaAdjust = (int)(rect.height() * getSwipeDeadZonePercentage());
            // scroll vertically: swipe down -> up
            downX = rect.centerX();
            downY = rect.bottom - swipeAreaAdjust;
            upX = rect.centerX();
            upY = rect.top + swipeAreaAdjust;
        } else {
            int swipeAreaAdjust = (int)(rect.width() * getSwipeDeadZonePercentage());
            // scroll horizontally: swipe right -> left
            // TODO: Assuming device is not in right to left language
            downX = rect.right - swipeAreaAdjust;
            downY = rect.centerY();
            upX = rect.left + swipeAreaAdjust;
            upY = rect.centerY();
        }
        return getInteractionController().scrollSwipe(downX, downY, upX, upY, steps);
    }


源码可以看出最后调用了InteractionController.scrollSwipe,进入到该方法:


public boolean scrollSwipe(final int downX, final int downY, final int upX, final int upY,
            final int steps) {
        Log.d(LOG_TAG, "scrollSwipe (" +  downX + ", " + downY + ", " + upX + ", "
                + upY + ", " + steps +")");
        try {
            mUiAutomatorBridge.executeCommandAndWaitForAccessibilityEvent(
                    new Runnable() {
                        @Override
                        public void run() {
                            swipe(downX, downY, upX, upY, steps);
                        }
                    },
                    new Predicate<AccessibilityEvent>() {
                        @Override
                        public boolean apply(AccessibilityEvent event) {
                            return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED);
                        }
                    }, DEFAULT_SCROLL_EVENT_TIMEOUT_MILLIS);
        } catch (Exception e) {
            Log.e(LOG_TAG, "Error in scrollSwipe: " + e.getMessage());
            return false;
        }
        return true;
    }

可以看到在run方法里他也是调用了swipe方法。那为何swipeLeft可以滑动,而scrollForward却不可以?


去4.4.2版本里去看看这两个方法是否有改动。但是4.4.2源码与4.1.1的源码是一样的的,没有改动,从swipe方法的坐标入手试试。因为swipeleft方法中滑动点的坐标是从距右边框5个像素,滑倒距左边框5个像素,而scrollForward是从距右边框54像素滑到距左边框54像素(宽度*盲区百分比)的。在距离上swipeleft传给swipe的坐标轨迹要比scrollForward的距离远。然后看看界面的视图





从uiautomatorviewer中可以看出可滑动的区域为整个屏幕bounds:[0,0][540,960].而滑动时真正滚动的区域为[6,92][534,748],有6个像素的区别。下面我们通过直接调用InteractionController里的swipe方法,来验证这个想法的正确性。由于InteractionController里的方法都是定义的包内可用,不能直接通过UiObject里的getInteractionContoller得到该对象,然后调用swipe()。那么怎么办呢?好在UiDevice里有个方法swipe:


public boolean swipe(int startX, int startY, int endX, int endY, int steps) {
        Tracer.trace(startX, startY, endX, endY, steps);
        return getAutomatorBridge().getInteractionController()
                .swipe(startX, startY, endX, endY, steps);
    }

刚好也是调用InteractionController中的swipe方法,那么我们来开始代码的编写:


public void test_EnterApp() throws UiObjectNotFoundException{
        uiDevice = getUiDevice();
        uiDevice.pressHome();
        UiScrollable appList = UiUtil.findUiScrollableByScrollable(true);
        if (appList.exists()) {
            appList.setAsHorizontalList();
            //uiDevice.swipe(535, 480, 5, 480, 20);
            uiDevice.swipe(540-54, 480, 54, 480, 20);
        }
        
    }


通过上面的实验,结论是2个方法都成功的滚动了,说明不是坐标差异造成的滚动差异。这就又开始让我疑惑了,看了一下4.4.2里InteractionController中scrollSwipe改动太大了:


public boolean scrollSwipe(final int downX, final int downY, final int upX, final int upY,
            final int steps) {
        Log.d(LOG_TAG, "scrollSwipe (" +  downX + ", " + downY + ", " + upX + ", "
                + upY + ", " + steps +")");

        Runnable command = new Runnable() {
            @Override
            public void run() {
                swipe(downX, downY, upX, upY, steps);
            }
        };

        // Collect all accessibility events generated during the swipe command and get the
        // last event
        ArrayList<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
        runAndWaitForEvents(command,
                new EventCollectingPredicate(AccessibilityEvent.TYPE_VIEW_SCROLLED, events),
                Configurator.getInstance().getScrollAcknowledgmentTimeout());

        AccessibilityEvent event = getLastMatchingEvent(events,
                AccessibilityEvent.TYPE_VIEW_SCROLLED);

        if (event == null) {
            // end of scroll since no new scroll events received
            recycleAccessibilityEvents(events);
            return false;
        }

        // AdapterViews have indices we can use to check for the beginning.
        boolean foundEnd = false;
        if (event.getFromIndex() != -1 && event.getToIndex() != -1 && event.getItemCount() != -1) {
            foundEnd = event.getFromIndex() == 0 ||
                    (event.getItemCount() - 1) == event.getToIndex();
            Log.d(LOG_TAG, "scrollSwipe reached scroll end: " + foundEnd);
        } else if (event.getScrollX() != -1 && event.getScrollY() != -1) {
            // Determine if we are scrolling vertically or horizontally.
            if (downX == upX) {
                // Vertical
                foundEnd = event.getScrollY() == 0 ||
                        event.getScrollY() == event.getMaxScrollY();
                Log.d(LOG_TAG, "Vertical scrollSwipe reached scroll end: " + foundEnd);
            } else if (downY == upY) {
                // Horizontal
                foundEnd = event.getScrollX() == 0 ||
                        event.getScrollX() == event.getMaxScrollX();
                Log.d(LOG_TAG, "Horizontal scrollSwipe reached scroll end: " + foundEnd);
            }
        }
        recycleAccessibilityEvents(events);
        return !foundEnd;
    }


教训:看源码一定要看到方法一层层调用,直到谷歌在API没有的类。那个里面往往有着不被人发现的秘密。


但是该方法也没有太大的逻辑上的改动。我尝试着在其他可滚动的控件里使用scrollForward方法。比如在更换壁纸里,该方法是可用的。而且在其他手机的appLiist界面使用scrollForward也无错。




这个与上面那张applist的区别在于,scrollable为true的布局和真正滚动的是一个布局。而上面scrollable布局为整个屏幕,而真正滚动的是中间那部分应用列表。这个我是不是可以归咎与开发的问题,所以问题的根究应该在与scrollable布局的设置,我改一下定位可滚动的控件试试。首先我获得之前获得的控件的坐标:


public void test_EnterApp() throws UiObjectNotFoundException {
		uiDevice = getUiDevice();
		//uiDevice.pressHome();
		UiScrollable appList = UiUtil.findUiScrollableByScrollable(true);
		Log.i(TAG, appList.getBounds().toString());
		if (appList.exists()) {
			appList.setAsHorizontalList();
			while (true) {
				appList.scrollForward();
			}
			
		}

	}

Log输出:


01-02 02:21:47.869: I/Stress(27282): Rect(-1698, -832 - 2240, 1793)


01-02 02:32:28.474: I/QueryController(27608): Matched selector: UiSelector[SCROLLABLE=true] <<==>> 
[android.view.accessibility.AccessibilityNodeInfo@8718; boundsInParent: Rect(540, 0 - 4479, 2626); 
boundsInScreen: Rect(-1698, -832 - 2240, 1793); packageName: com.android.sprdlauncher2; className: android.view.View; 
text: null; contentDescription: null; viewIdResName: com.android.sprdlauncher2:id/workspace; checkable: false; 
checked: false; focusable: false; focused: false; selected: false; clickable: false; longClickable: true; enabled: true; 
password: false; scrollable: true; 
[ACTION_SELECT, ACTION_CLEAR_SELECTION, ACTION_LONG_CLICK, ACTION_ACCESSIBILITY_FOCUS, ACTION_SCROLL_FORWARD, ACTION_SCROLL_BACKWARD]]
01-02 02:32:28.474: D/InteractionController(27608): scrollSwipe (1847, 480, -1305, 480, 20)
01-02 02:32:28.474: I/InputDispatcher(594): Dropping event because there is no touchable window at (1847, 480).
01-02 02:32:28.474: W/InputManager(594): Input event injection from pid 27608 failed.
01-02 02:32:28.484: W/InputManager(594): Input event injection from pid 27608 failed.
01-02 02:32:28.484: W/InputManager(594): Input event injection from pid 27608 failed.
01-02 02:32:28.684: W/InteractionController(27608): runAndwaitForEvent timedout waiting for events


从日志来看,点击的区域不在屏幕可见位置,终于找到它不滚动的原因啦。瞎琢磨一通啊,终于找到原因。现在分析原因开始。


总结:

1.swipeLeft的方法调用过程是swipeLeft--->swipe,scrollForward调用过程scrollForward--->scrollSwipe--->swipe。scrollForward就卡在了第二步scrollSwipe。因为scrollSwipe里的方法点击的点需要可触摸。而swipe则不需要这样的限制。


2.在遇到这种可滚动控件是整个屏幕的,尽量使用swipeLeft。


3.分析源码时针对当前版本,因为不同版本改动很大。


4.要低调!









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值