HierarchyView的实现原理和Android设备无法使用HierarchyView的解决方法
此为转帖,原文请参见:http://www.cnblogs.com/coding-way/p/4294225.html
最近在看一个老外写的东西,发现里面有个类,使用这个类可以让任何设备使用HierarchyView。
众所周知,市面上卖的Android设备,一般都不能使用HierarchyView,所以借此机会,了解一下HierarchyView的实现原理,并学习一下老外的解决方法。
HierarchyView的源码在/sdk/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer中,但貌似不全,
所以直接反编译/prebuilts/devtools/tools/lib/hierarchyviewer2lib.jar和/prebuilts/devtools/tools/lib/hierarchyviewer2.jar。
当对设备使用HierarchyView时,HierarchyView会给设备发送一个startViewServer的命令,下面源码时其调用顺序:
HierarchyViewerDirector.class
public void populateDeviceSelectionModel() { IDevice[] devices = DeviceBridge.getDevices(); for (IDevice device : devices) deviceConnected(device); } public void deviceConnected(final IDevice device) { executeInBackground("Connecting device", new Object() { public void run() { if (!device.isOnline()) return; IHvDevice hvDevice; synchronized (HierarchyViewerDirector.mDevicesLock) { hvDevice = (IHvDevice)HierarchyViewerDirector.this.mDevices.get(device); if (hvDevice == null) { hvDevice = HvDeviceFactory.create(device); hvDevice.initializeViewDebug(); hvDevice.addWindowChangeListener(HierarchyViewerDirector.getDirector()); HierarchyViewerDirector.this.mDevices.put(device, hvDevice); } else { hvDevice.initializeViewDebug(); } } DeviceSelectionModel.getModel().addDevice(hvDevice); HierarchyViewerDirector.this.focusChanged(device); } }); }
ViewServerDevice.class
public boolean initializeViewDebug() { if (!this.mDevice.isOnline()) { return false; } DeviceBridge.setupDeviceForward(this.mDevice); return reloadWindows(); } public boolean reloadWindows() { if ((!DeviceBridge.isViewServerRunning(this.mDevice)) && (!DeviceBridge.startViewServer(this.mDevice))) { Log.e("ViewServerDevice", "Unable to debug device: " + this.mDevice.getName()); DeviceBridge.removeDeviceForward(this.mDevice); return false; } this.mViewServerInfo = DeviceBridge.loadViewServerInfo(this.mDevice); if (this.mViewServerInfo == null) { return false; } this.mWindows = DeviceBridge.loadWindows(this, this.mDevice); return true; }
DeviceBridge.class
public static boolean startViewServer(IDevice device) { return startViewServer(device, 4939); } public static boolean startViewServer(IDevice device, int port) { boolean[] result = new boolean[1]; try { if (device.isOnline()) device.executeShellCommand(buildStartServerShellCommand(port), new BooleanResultReader(result)); } catch (TimeoutException e) { Log.e("hierarchyviewer", "Timeout starting view server on device " + device); } catch (IOException e) { Log.e("hierarchyviewer", "Unable to start view server on device " + device); } catch (AdbCommandRejectedException e) { Log.e("hierarchyviewer", "Adb rejected command to start view server on device " + device); } catch (ShellCommandUnresponsiveException e) { Log.e("hierarchyviewer", "Unable to execute command to start view server on device " + device); } return result[0]; } private static String buildStartServerShellCommand(int port) { return String.format("service call window %d i32 %d", new Object[] { Integer.valueOf(1), Integer.valueOf(port) }); }
从代码中可以看到,最终HierarchyView会让设备执行service命令,最终执行的命令是这样:
shell@device:/ $ service call window 1 i32 4939
这行命令其实是向android.view.IWindowManager发送一个CODE为1,值为4939的parcel。
其实就是调用WindowManagerService中的startViewServer方法,并把4939作为参数传入,接下来看看WindowManagerService.startViewServer的源码:
public boolean startViewServer(int port) { if (isSystemSecure()) { return false; } if (!checkCallingPermission(Manifest.permission.DUMP, "startViewServer")) { return false; } if (port < 1024) { return false; } if (mViewServer != null) { if (!mViewServer.isRunning()) { try { return mViewServer.start(); } catch (IOException e) { Slog.w(TAG, "View server did not start"); } } return false; } try { mViewServer = new ViewServer(this, port); return mViewServer.start(); } catch (IOException e) { Slog.w(TAG, "View server did not start"); } return false; } private boolean isSystemSecure() { return "1".equals(SystemProperties.get(SYSTEM_SECURE, "1")) && "0".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); }
里面会做一些权限检查,然后会调用ViewServer.start(),关键就在ViewServer里,先吧ViewServer完整的代码贴上:
可以看到,ViewServer实现Runnable,接下来看看start的实现:
boolean start() throws IOException { if (mThread != null) { return false; } mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost()); mThread = new Thread(this, "Remote View Server [port=" + mPort + "]"); mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS); mThread.start(); return true; } public void run() { while (Thread.currentThread() == mThread) { // Any uncaught exception will crash the system process try { Socket client = mServer.accept(); if (mThreadPool != null) { mThreadPool.submit(new ViewServerWorker(client)); } else { try { client.close(); } catch (IOException e) { e.printStackTrace(); } } } catch (Exception e) { Slog.w(LOG_TAG, "Connection error: ", e); } } }
这个Server启动后,使用之前传进来的端口号(4939)创建个ServerSocket,然后在独立的线程里监听这个端口是否有客户端连接请求,有的话传给ViewServerWorker去处理:
class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener { private Socket mClient; private boolean mNeedWindowListUpdate; private boolean mNeedFocusedWindowUpdate; public ViewServerWorker(Socket client) { mClient = client; mNeedWindowListUpdate = false; mNeedFocusedWindowUpdate = false; } public void run() { BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024); final String request = in.readLine(); String command; String parameters; int index = request.indexOf(‘ ‘); if (index == -1) { command = request; parameters = ""; } else { command = request.substring(0, index); parameters = request.substring(index + 1); } boolean result; if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) { result = writeValue(mClient, VALUE_PROTOCOL_VERSION); } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) { result = writeValue(mClient, VALUE_SERVER_VERSION); } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) { result = mWindowManager.viewServerListWindows(mClient); } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) { result = mWindowManager.viewServerGetFocusedWindow(mClient); } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) { result = windowManagerAutolistLoop(); } else { result = mWindowManager.viewServerWindowCommand(mClient, command, parameters); } if (!result) { Slog.w(LOG_TAG, "An error occurred with the command: " + command); } } catch(IOException e) { Slog.w(LOG_TAG, "Connection error: ", e); } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if (mClient != null) { try { mClient.close(); } catch (IOException e) { e.printStackTrace(); } } } } public void windowsChanged() { synchronized(this) { mNeedWindowListUpdate = true; notifyAll(); } } public void focusChanged() { synchronized(this) { mNeedFocusedWindowUpdate = true; notifyAll(); } } private boolean windowManagerAutolistLoop() { mWindowManager.addWindowChangeListener(this); BufferedWriter out = null; try { out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream())); while (!Thread.interrupted()) { boolean needWindowListUpdate = false; boolean needFocusedWindowUpdate = false; synchronized (this) { while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) { wait(); } if (mNeedWindowListUpdate) { mNeedWindowListUpdate = false; needWindowListUpdate = true; } if (mNeedFocusedWindowUpdate) { mNeedFocusedWindowUpdate = false; needFocusedWindowUpdate = true; } } if (needWindowListUpdate) { out.write("LIST UPDATE\n"); out.flush(); } if (needFocusedWindowUpdate) { out.write("ACTION_FOCUS UPDATE\n"); out.flush(); } } } catch (Exception e) { // Ignore } finally { if (out != null) { try { out.close(); } catch (IOException e) { // Ignore } } mWindowManager.removeWindowChangeListener(this); } return true; } }
从代码中可以看到,HierarchyView通过Socket向设备发送命令,ViewServerWorker来解析处理命令,并把需要返回的值通过socket再发给HierarchyView。
至此,HierarchyView的大致原理已经了解,发现只要我们自己创建个ServerSocket,并且监听4939端口,然后模仿ViewServer处理相应命令就可以让设备使用HierarchyView了。
老外就是用的这个方法。所以我们就不用重复造轮子了。
接下来看看老外的解决方法:
使用方法如下:
public class MyActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set content view, etc. ViewServer.get(this).addWindow(this); } public void onDestroy() { super.onDestroy(); ViewServer.get(this).removeWindow(this); } public void onResume() { super.onResume(); ViewServer.get(this).setFocusedWindow(this); } }
使用时要注意:app要添加INTERNET权限,并且android:debugable要为true,eclipse或者studio直接run到手机都是debugable的,所以这点不用担心。
好了,祝大家春节快乐!
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。