Error obtaining UI hierarchy

在使用uiautomatorviewer的时候经常出现dump不到资源的问题,报错:Remote object doesn't exist!错误
现在来分析下出现的原因,先看下截图


Error obtaining UI hierarchy_第1张图片

1.其实Remote object doesn't exist!这个错误是在使用adb pull /sdcard/uidump.xml 命令产生的异常

    //SyncException中
     public enum SyncError {
       ...
        /** unknown remote object during a pull */
        NO_REMOTE_OBJECT("Remote object doesn't exist!"),
       ...
    }

执行Device命令

  @Override
    public void pullFile(String remote, String local)
            throws IOException, AdbCommandRejectedException, TimeoutException, SyncException {
        SyncService sync = null;
        try {
            String targetFileName = getFileName(remote);

            Log.d(targetFileName, String.format("Downloading %1$s from device '%2$s'",
                    targetFileName, getSerialNumber()));

            sync = getSyncService();
            if (sync != null) {
                String message = String.format("Downloading file from device '%1$s'",
                        getSerialNumber());
                Log.d(LOG_TAG, message);
                //在这调用SyncService.pullFile()
                sync.pullFile(remote, local, SyncService.getNullProgressMonitor());
            } else {
                throw new IOException("Unable to open sync connection!");
            }
        ...
    }

        //SyncService中
        public void pullFile(String remoteFilepath, String localFilename,
            ISyncProgressMonitor monitor) throws TimeoutException, IOException, SyncException {
        FileStat fileStat = statFile(remoteFilepath);
        if (fileStat == null) {
            // attempts to download anyway
        } else if (fileStat.getMode() == 0) {
            //当文件不存在的时候抛出异常
            throw new SyncException(SyncError.NO_REMOTE_OBJECT);
        }
        ...
    }

2.dump命令是执行adb shell /system/bin/uiautomator dump /sdcard/uidump.xml获取资源
对应源码中DumpCommand命令

    //DumpCommand执行run()
        @Override
    public void run(String[] args) {
       ...
        UiAutomation uiAutomation = automationWrapper.getUiAutomation();
        uiAutomation.waitForIdle(1000, 1000 * 10);
        AccessibilityNodeInfo info = uiAutomation.getRootInActiveWindow();
        if (info == null) {
            System.err.println("ERROR: null root node returned by UiTestAutomationBridge.");
            return;
        }
    
        Display display =
                DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
        int rotation = display.getRotation();
        Point size = new Point();
        display.getSize(size);
        AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x, size.y);
       ...
    }

所以缕一缕,这个异常的产生是uiautomatorviewer调用dump命令后,没有dump成功,
导致uidump.xml文件不存在,然后又用pull命令导出文件,由于没有该文件,则adb中的Device抛出这个异常

3.真正获取资源是通过QueryController中UiAutomation 使用AIDL机制跨进程调用
AccessibilitySerivce服务来获取window资源的,其实Uiautomator中的UiObject也是通过这个服务来获取的。
(以下是个人理解)如果主线程looper忙碌则不会响应AccessibilityInteractionClient线程的请求,所以就dump
不到资源或者获取不到nodeInfo节点信息

    //AccessibilityInteractionController类中
       public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
            long accessibilityNodeId, Region interactiveRegion, int interactionId,
            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
            long interrogatingTid, MagnificationSpec spec) {
        Message message = mHandler.obtainMessage();
        message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID;
        message.arg1 = flags;

        SomeArgs args = SomeArgs.obtain();
        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
        args.argi3 = interactionId;
        args.arg1 = callback;
        args.arg2 = spec;
        args.arg3 = interactiveRegion;
        message.obj = args;

        // If the interrogation is performed by the same thread as the main UI
        // thread in this process, set the message as a static reference so
        // after this call completes the same thread but in the interrogating
        // client can handle the message to generate the result.
        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
            AccessibilityInteractionClient.getInstanceForThread(
                    interrogatingTid).setSameThreadMessage(message);
        } else {
            mHandler.sendMessage(message);
        }
    }

4.那么我们尝试自定义一个TextView 在onDraw中调用setText方法,让Mian线程处于忙碌状态,这样模拟获取不到资源的场景

    public class OverDrawView extends android.support.v7.widget.AppCompatTextView {

    private TextPaint mTextPaint;

    public OverDrawView(Context context) {
        this(context,null);
    }

    public OverDrawView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public OverDrawView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mTextPaint = new TextPaint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(100);
        mTextPaint.setColor(Color.RED);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        setText("overdraw");
        drawText(canvas);

    }

    private void drawText(Canvas canvas) {
        canvas.drawText("this is OverDrawView",100,100,mTextPaint);
    }
}

加入到MainActivity布局文件中

    

    

    

运行并安装apk,这时候用uiautomatorviewer获取资源就会报Remote object doesn't exist!异常
setText方法内部会调用invalidate,而invalidate又会反调ondraw方法,这样就会导致频繁绘制,main
线程忙碌状态,自然我们获取资源的AccessibilityInteractionClient线程就无法获取了

所以总结下,如果Mian线程处于忙碌状态,那么我们获取到资源的可能性就很小。而常见的动画就是频繁的调用
onDraw形成连续针实现的,那么有连续动画的地方我们有可能会获取资源失败。官方建议在做自动化的时候关闭
开发者选项中的三个动画。
如果还出现不能获取现象,我们可以打GPU呈现模式分析查看帧率,如果一直在绘制,而界面没有动画效果,那就
有肯能是开发人员在onDraw中递归调用了

4.例如SeekBar在做动画的时候就获取不到,等待seekbar结束时就可以获取

示例工程下载

Error obtaining UI hierarchy_第2张图片
QQ图片20180129192831.jpg

你可能感兴趣的:(Error obtaining UI hierarchy)