HierarchyView的實現原理和Android裝置無法使用HierarchyView的解決方案

來源:互聯網
上載者:User

標籤:

此為轉帖,原文請參見: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的解決方案

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.