標籤:
此為轉帖,原文請參見: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命令,最終執行的命令是這樣:
[email protected]:/ $ 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.java
可以看到,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的,所以這點不用擔心。
好了,祝大家春節快樂!
HierarchyView的實現原理和Android裝置無法使用HierarchyView的解決方案