在使用uiautomatorviewer的时候经常出现dump不到资源的问题,报错:Remote object doesn't exist!错误
现在来分析下出现的原因,先看下截图
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结束时就可以获取
示例工程下载