最近在看一个老外写的东西,发现里面有个类,使用这个类可以让任何设备使用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完整的代码贴上:

HierarchyView的实现原理和Android设备无法使用HierarchyView的解决方法
  1 /*  2  * Copyright (C) 2007 The Android Open Source Project  3  *  4  * Licensed under the Apache License, Version 2.0 (the "License");  5  * you may not use this file except in compliance with the License.  6  * You may obtain a copy of the License at  7  *  8  *      http://www.apache.org/licenses/LICENSE-2.0  9  * 10  * Unless required by applicable law or agreed to in writing, software 11  * distributed under the License is distributed on an "AS IS" BASIS, 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13  * See the License for the specific language governing permissions and 14  * limitations under the License. 15  */ 16  17 package com.android.server.wm; 18  19  20 import android.util.Slog; 21  22 import java.net.ServerSocket; 23 import java.net.Socket; 24 import java.net.InetAddress; 25 import java.util.concurrent.ExecutorService; 26 import java.util.concurrent.Executors; 27 import java.io.IOException; 28 import java.io.BufferedReader; 29 import java.io.InputStreamReader; 30 import java.io.OutputStream; 31 import java.io.BufferedWriter; 32 import java.io.OutputStreamWriter; 33  34 /** 35  * The ViewServer is local socket server that can be used to communicate with the 36  * views of the opened windows. Communication with the views is ensured by the 37  * {@link com.android.server.wm.WindowManagerService} and is a cross-process operation. 38  * 39  * {@hide} 40  */ 41 class ViewServer implements Runnable { 42     /** 43      * The default port used to start view servers. 44      */ 45     public static final int VIEW_SERVER_DEFAULT_PORT = 4939; 46  47     private static final int VIEW_SERVER_MAX_CONNECTIONS = 10; 48  49     // Debug facility 50     private static final String LOG_TAG = "ViewServer"; 51  52     private static final String VALUE_PROTOCOL_VERSION = "4"; 53     private static final String VALUE_SERVER_VERSION = "4"; 54  55     // Protocol commands 56     // Returns the protocol version 57     private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL"; 58     // Returns the server version 59     private static final String COMMAND_SERVER_VERSION = "SERVER"; 60     // Lists all of the available windows in the system 61     private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST"; 62     // Keeps a connection open and notifies when the list of windows changes 63     private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST"; 64     // Returns the focused window 65     private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS"; 66  67     private ServerSocket mServer; 68     private Thread mThread; 69  70     private final WindowManagerService mWindowManager; 71     private final int mPort; 72  73     private ExecutorService mThreadPool; 74  75     /** 76      * Creates a new ViewServer associated with the specified window manager on the 77      * specified local port. The server is not started by default. 78      * 79      * @param windowManager The window manager used to communicate with the views. 80      * @param port The port for the server to listen to. 81      * 82      * @see #start() 83      */ 84     ViewServer(WindowManagerService windowManager, int port) { 85         mWindowManager = windowManager; 86         mPort = port; 87     } 88  89     /** 90      * Starts the server. 91      * 92      * @return True if the server was successfully created, or false if it already exists. 93      * @throws IOException If the server cannot be created. 94      * 95      * @see #stop() 96      * @see #isRunning() 97      * @see WindowManagerService#startViewServer(int) 98      */ 99     boolean start() throws IOException {100         if (mThread != null) {101             return false;102         }103 104         mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost());105         mThread = new Thread(this, "Remote View Server [port=" + mPort + "]");106         mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS);107         mThread.start();108 109         return true;110     }111 112     /**113      * Stops the server.114      *115      * @return True if the server was stopped, false if an error occured or if the116      *         server wasn't started.117      *118      * @see #start()119      * @see #isRunning()120      * @see WindowManagerService#stopViewServer()121      */122     boolean stop() {123         if (mThread != null) {124 125             mThread.interrupt();126             if (mThreadPool != null) {127                 try {128                     mThreadPool.shutdownNow();129                 } catch (SecurityException e) {130                     Slog.w(LOG_TAG, "Could not stop all view server threads");131                 }132             }133             mThreadPool = null;134             mThread = null;135             try {136                 mServer.close();137                 mServer = null;138                 return true;139             } catch (IOException e) {140                 Slog.w(LOG_TAG, "Could not close the view server");141             }142         }143         return false;144     }145 146     /**147      * Indicates whether the server is currently running.148      *149      * @return True if the server is running, false otherwise.150      *151      * @see #start()152      * @see #stop()153      * @see WindowManagerService#isViewServerRunning()  154      */155     boolean isRunning() {156         return mThread != null && mThread.isAlive();157     }158 159     /**160      * Main server loop.161      */162     public void run() {163         while (Thread.currentThread() == mThread) {164             // Any uncaught exception will crash the system process165             try {166                 Socket client = mServer.accept();167                 if (mThreadPool != null) {168                     mThreadPool.submit(new ViewServerWorker(client));169                 } else {170                     try {171                         client.close();172                     } catch (IOException e) {173                         e.printStackTrace();174                     }175                 }176             } catch (Exception e) {177                 Slog.w(LOG_TAG, "Connection error: ", e);178             }179         }180     }181 182     private static boolean writeValue(Socket client, String value) {183         boolean result;184         BufferedWriter out = null;185         try {186             OutputStream clientStream = client.getOutputStream();187             out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);188             out.write(value);189             out.write("\n");190             out.flush();191             result = true;192         } catch (Exception e) {193             result = false;194         } finally {195             if (out != null) {196                 try {197                     out.close();198                 } catch (IOException e) {199                     result = false;200                 }201             }202         }203         return result;204     }205 206     class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener {207         private Socket mClient;208         private boolean mNeedWindowListUpdate;209         private boolean mNeedFocusedWindowUpdate;210 211         public ViewServerWorker(Socket client) {212             mClient = client;213             mNeedWindowListUpdate = false;214             mNeedFocusedWindowUpdate = false;215         }216 217         public void run() {218 219             BufferedReader in = null;220             try {221                 in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);222 223                 final String request = in.readLine();224 225                 String command;226                 String parameters;227 228                 int index = request.indexOf(' ');229                 if (index == -1) {230                     command = request;231                     parameters = "";232                 } else {233                     command = request.substring(0, index);234                     parameters = request.substring(index + 1);235                 }236 237                 boolean result;238                 if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) {239                     result = writeValue(mClient, VALUE_PROTOCOL_VERSION);240                 } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) {241                     result = writeValue(mClient, VALUE_SERVER_VERSION);242                 } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {243                     result = mWindowManager.viewServerListWindows(mClient);244                 } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {245                     result = mWindowManager.viewServerGetFocusedWindow(mClient);246                 } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {247                     result = windowManagerAutolistLoop();248                 } else {249                     result = mWindowManager.viewServerWindowCommand(mClient,250                             command, parameters);251                 }252 253                 if (!result) {254                     Slog.w(LOG_TAG, "An error occurred with the command: " + command);255                 }256             } catch(IOException e) {257                 Slog.w(LOG_TAG, "Connection error: ", e);258             } finally {259                 if (in != null) {260                     try {261                         in.close();262 263                     } catch (IOException e) {264                         e.printStackTrace();265                     }266                 }267                 if (mClient != null) {268                     try {269                         mClient.close();270                     } catch (IOException e) {271                         e.printStackTrace();272                     }273                 }274             }275         }276 277         public void windowsChanged() {278             synchronized(this) {279                 mNeedWindowListUpdate = true;280                 notifyAll();281             }282         }283 284         public void focusChanged() {285             synchronized(this) {286                 mNeedFocusedWindowUpdate = true;287                 notifyAll();288             }289         }290 291         private boolean windowManagerAutolistLoop() {292             mWindowManager.addWindowChangeListener(this);293             BufferedWriter out = null;294             try {295                 out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));296                 while (!Thread.interrupted()) {297                     boolean needWindowListUpdate = false;298                     boolean needFocusedWindowUpdate = false;299                     synchronized (this) {300                         while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) {301                             wait();302                         }303                         if (mNeedWindowListUpdate) {304                             mNeedWindowListUpdate = false;305                             needWindowListUpdate = true;306                         }307                         if (mNeedFocusedWindowUpdate) {308                             mNeedFocusedWindowUpdate = false;309                             needFocusedWindowUpdate = true;310                         }311                     }312                     if (needWindowListUpdate) {313                         out.write("LIST UPDATE\n");314                         out.flush();315                     }316                     if (needFocusedWindowUpdate) {317                         out.write("ACTION_FOCUS UPDATE\n");318                         out.flush();319                     }320                 }321             } catch (Exception e) {322                 // Ignore323             } finally {324                 if (out != null) {325                     try {326                         out.close();327                     } catch (IOException e) {328                         // Ignore329                     }330                 }331                 mWindowManager.removeWindowChangeListener(this);332             }333             return true;334         }335     }336 }
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了。

老外就是用的这个方法HierarchyView的实现原理和Android设备无法使用HierarchyView的解决方法。所以我们就不用重复造轮子了HierarchyView的实现原理和Android设备无法使用HierarchyView的解决方法

接下来看看老外的解决方法:

HierarchyView的实现原理和Android设备无法使用HierarchyView的解决方法
  1 /*  2  * Copyright (C) 2011 The Android Open Source Project  3  *  4  * Licensed under the Apache License, Version 2.0 (the "License");  5  * you may not use this file except in compliance with the License.  6  * You may obtain a copy of the License at  7  *  8  *      http://www.apache.org/licenses/LICENSE-2.0  9  * 10  * Unless required by applicable law or agreed to in writing, software 11  * distributed under the License is distributed on an "AS IS" BASIS, 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13  * See the License for the specific language governing permissions and 14  * limitations under the License. 15  */ 16  17 package com.server; 18  19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.pm.ApplicationInfo; 22 import android.os.Build; 23 import android.text.TextUtils; 24 import android.util.Log; 25 import android.view.View; 26 import android.view.ViewDebug; 27  28 import java.io.BufferedReader; 29 import java.io.BufferedWriter; 30 import java.io.IOException; 31 import java.io.InputStreamReader; 32 import java.io.OutputStream; 33 import java.io.OutputStreamWriter; 34 import java.lang.reflect.Method; 35 import java.net.InetAddress; 36 import java.net.ServerSocket; 37 import java.net.Socket; 38 import java.util.HashMap; 39 import java.util.List; 40 import java.util.Map.Entry; 41 import java.util.concurrent.CopyOnWriteArrayList; 42 import java.util.concurrent.ExecutorService; 43 import java.util.concurrent.Executors; 44 import java.util.concurrent.locks.ReentrantReadWriteLock; 45  46 /** 47  * <p>This class can be used to enable the use of HierarchyViewer inside an 48  * application. HierarchyViewer is an Android SDK tool that can be used 49  * to inspect and debug the user interface of running applications. For 50  * security reasons, HierarchyViewer does not work on production builds 51  * (for instance phones bought in store.) By using this class, you can 52  * make HierarchyViewer work on any device. You must be very careful 53  * however to only enable HierarchyViewer when debugging your 54  * application.</p> 55  * <p/> 56  * <p>To use this view server, your application must require the INTERNET 57  * permission.</p> 58  * <p/> 59  * <p>The recommended way to use this API is to register activities when 60  * they are created, and to unregister them when they get destroyed:</p> 61  * <p/> 62  * <pre> 63  * public class MyActivity extends Activity { 64  *     public void onCreate(Bundle savedInstanceState) { 65  *         super.onCreate(savedInstanceState); 66  *         // Set content view, etc. 67  *         ViewServer.get(this).addWindow(this); 68  *     } 69  * 70  *     public void onDestroy() { 71  *         super.onDestroy(); 72  *         ViewServer.get(this).removeWindow(this); 73  *     } 74  * 75  *     public void onResume() { 76  *         super.onResume(); 77  *         ViewServer.get(this).setFocusedWindow(this); 78  *     } 79  * } 80  * </pre> 81  * <p/> 82  * <p> 83  * In a similar fashion, you can use this API with an InputMethodService: 84  * </p> 85  * <p/> 86  * <pre> 87  * public class MyInputMethodService extends InputMethodService { 88  *     public void onCreate() { 89  *         super.onCreate(); 90  *         View decorView = getWindow().getWindow().getDecorView(); 91  *         String name = "MyInputMethodService"; 92  *         ViewServer.get(this).addWindow(decorView, name); 93  *     } 94  * 95  *     public void onDestroy() { 96  *         super.onDestroy(); 97  *         View decorView = getWindow().getWindow().getDecorView(); 98  *         ViewServer.get(this).removeWindow(decorView); 99  *     }100  *101  *     public void onStartInput(EditorInfo attribute, boolean restarting) {102  *         super.onStartInput(attribute, restarting);103  *         View decorView = getWindow().getWindow().getDecorView();104  *         ViewServer.get(this).setFocusedWindow(decorView);105  *     }106  * }107  * </pre>108  */109 public class ViewServer implements Runnable {110     /**111      * The default port used to start view servers.112      */113     private static final int VIEW_SERVER_DEFAULT_PORT = 4939;114     private static final int VIEW_SERVER_MAX_CONNECTIONS = 10;115     private static final String BUILD_TYPE_USER = "user";116 117     // Debug facility118     private static final String LOG_TAG = "ViewServer";119 120     private static final String VALUE_PROTOCOL_VERSION = "4";121     private static final String VALUE_SERVER_VERSION = "4";122 123     // Protocol commands124     // Returns the protocol version125     private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL";126     // Returns the server version127     private static final String COMMAND_SERVER_VERSION = "SERVER";128     // Lists all of the available windows in the system129     private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST";130     // Keeps a connection open and notifies when the list of windows changes131     private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST";132     // Returns the focused window133     private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS";134 135     private ServerSocket mServer;136     private final int mPort;137 138     private Thread mThread;139     private ExecutorService mThreadPool;140 141     private final List<WindowListener> mListeners =142             new CopyOnWriteArrayList<WindowListener>();143 144     private final HashMap<View, String> mWindows = new HashMap<View, String>();145     private final ReentrantReadWriteLock mWindowsLock = new ReentrantReadWriteLock();146 147     private View mFocusedWindow;148     private final ReentrantReadWriteLock mFocusLock = new ReentrantReadWriteLock();149 150     private static ViewServer sServer;151 152     /**153      * Returns a unique instance of the ViewServer. This method should only be154      * called from the main thread of your application. The server will have155      * the same lifetime as your process.156      * <p/>157      * If your application does not have the <code>android:debuggable</code>158      * flag set in its manifest, the server returned by this method will159      * be a dummy object that does not do anything. This allows you to use160      * the same code in debug and release versions of your application.161      *162      * @param context A Context used to check whether the application is163      *                debuggable, this can be the application context164      */165     public static ViewServer get(Context context) {166         ApplicationInfo info = context.getApplicationInfo();167         if (BUILD_TYPE_USER.equals(Build.TYPE) &&168                 (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {169             if (sServer == null) {170                 sServer = new ViewServer(ViewServer.VIEW_SERVER_DEFAULT_PORT);171             }172 173             if (!sServer.isRunning()) {174                 try {175                     sServer.start();176                 } catch (IOException e) {177                     Log.d(LOG_TAG, "Error:", e);178                 }179             }180         } else {181             sServer = new NoopViewServer();182         }183 184         return sServer;185     }186 187     private ViewServer() {188         mPort = -1;189     }190 191     /**192      * Creates a new ViewServer associated with the specified window manager on the193      * specified local port. The server is not started by default.194      *195      * @param port The port for the server to listen to.196      * @see #start()197      */198     private ViewServer(int port) {199         mPort = port;200     }201 202     /**203      * Starts the server.204      *205      * @return True if the server was successfully created, or false if it already exists.206      * @throws java.io.IOException If the server cannot be created.207      * @see #stop()208      * @see #isRunning()209      */210     public boolean start() throws IOException {211         if (mThread != null) {212             return false;213         }214 215         mThread = new Thread(this, "Local View Server [port=" + mPort + "]");216         mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS);217         mThread.start();218 219         return true;220     }221 222     /**223      * Stops the server.224      *225      * @return True if the server was stopped, false if an error occurred or if the226      * server wasn't started.227      * @see #start()228      * @see #isRunning()229      */230     public boolean stop() {231         if (mThread != null) {232             mThread.interrupt();233             if (mThreadPool != null) {234                 try {235                     mThreadPool.shutdownNow();236                 } catch (SecurityException e) {237                     Log.w(LOG_TAG, "Could not stop all view server threads");238                 }239             }240 241             mThreadPool = null;242             mThread = null;243 244             try {245                 mServer.close();246                 mServer = null;247                 return true;248             } catch (IOException e) {249                 Log.w(LOG_TAG, "Could not close the view server");250             }251         }252 253         mWindowsLock.writeLock().lock();254         try {255             mWindows.clear();256         } finally {257             mWindowsLock.writeLock().unlock();258         }259 260         mFocusLock.writeLock().lock();261         try {262             mFocusedWindow = null;263         } finally {264             mFocusLock.writeLock().unlock();265         }266 267         return false;268     }269 270     /**271      * Indicates whether the server is currently running.272      *273      * @return True if the server is running, false otherwise.274      * @see #start()275      * @see #stop()276      */277     public boolean isRunning() {278         return mThread != null && mThread.isAlive();279     }280 281     /**282      * Invoke this method to register a new view hierarchy.283      *284      * @param activity The activity whose view hierarchy/window to register285      * @see #addWindow(android.view.View, String)286      * @see #removeWindow(android.app.Activity)287      */288     public void addWindow(Activity activity) {289         String name = activity.getTitle().toString();290         if (TextUtils.isEmpty(name)) {291             name = activity.getClass().getCanonicalName() +292                     "/0x" + System.identityHashCode(activity);293         } else {294             name += "(" + activity.getClass().getCanonicalName() + ")";295         }296         addWindow(activity.getWindow().getDecorView(), name);297     }298 299     /**300      * Invoke this method to unregister a view hierarchy.301      *302      * @param activity The activity whose view hierarchy/window to unregister303      * @see #addWindow(android.app.Activity)304      * @see #removeWindow(android.view.View)305      */306     public void removeWindow(Activity activity) {307         removeWindow(activity.getWindow().getDecorView());308     }309 310     /**311      * Invoke this method to register a new view hierarchy.312      *313      * @param view A view that belongs to the view hierarchy/window to register314      * @name name The name of the view hierarchy/window to register315      * @see #removeWindow(android.view.View)316      */317     public void addWindow(View view, String name) {318         mWindowsLock.writeLock().lock();319         try {320             mWindows.put(view.getRootView(), name);321         } finally {322             mWindowsLock.writeLock().unlock();323         }324         fireWindowsChangedEvent();325     }326 327     /**328      * Invoke this method to unregister a view hierarchy.329      *330      * @param view A view that belongs to the view hierarchy/window to unregister331      * @see #addWindow(android.view.View, String)332      */333     public void removeWindow(View view) {334         mWindowsLock.writeLock().lock();335         try {336             mWindows.remove(view.getRootView());337         } finally {338             mWindowsLock.writeLock().unlock();339         }340         fireWindowsChangedEvent();341     }342 343     /**344      * Invoke this method to change the currently focused window.345      *346      * @param activity The activity whose view hierarchy/window hasfocus,347      *                 or null to remove focus348      */349     public void setFocusedWindow(Activity activity) {350         setFocusedWindow(activity.getWindow().getDecorView());351     }352 353     /**354      * Invoke this method to change the currently focused window.355      *356      * @param view A view that belongs to the view hierarchy/window that has focus,357      *             or null to remove focus358      */359     public void setFocusedWindow(View view) {360         mFocusLock.writeLock().lock();361         try {362             mFocusedWindow = view == null ? null : view.getRootView();363         } finally {364             mFocusLock.writeLock().unlock();365         }366         fireFocusChangedEvent();367     }368 369     /**370      * Main server loop.371      */372     public void run() {373         try {374             InetAddress address = InetAddress.getLocalHost();375             mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, address);376         } catch (Exception e) {377             Log.w(LOG_TAG, "Starting ServerSocket error: ", e);378         }379 380         while (mServer != null && Thread.currentThread() == mThread) {381             // Any uncaught exception will crash the system process382             try {383                 Socket client = mServer.accept();384                 if (mThreadPool != null) {385                     mThreadPool.submit(new ViewServerWorker(client));386                 } else {387                     try {388                         client.close();389                     } catch (IOException e) {390                         e.printStackTrace();391                     }392                 }393             } catch (Exception e) {394                 Log.w(LOG_TAG, "Connection error: ", e);395             }396         }397     }398 399     private static boolean writeValue(Socket client, String value) {400         boolean result;401         BufferedWriter out = null;402         try {403             OutputStream clientStream = client.getOutputStream();404             out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);405             out.write(value);406             out.write("\n");407             out.flush();408             result = true;409         } catch (Exception e) {410             result = false;411         } finally {412             if (out != null) {413                 try {414                     out.close();415                 } catch (IOException e) {416                     result = false;417                 }418             }419         }420         return result;421     }422 423     private void fireWindowsChangedEvent() {424         for (WindowListener listener : mListeners) {425             listener.windowsChanged();426         }427     }428 429     private void fireFocusChangedEvent() {430         for (WindowListener listener : mListeners) {431             listener.focusChanged();432         }433     }434 435     private void addWindowListener(WindowListener listener) {436         if (!mListeners.contains(listener)) {437             mListeners.add(listener);438         }439     }440 441     private void removeWindowListener(WindowListener listener) {442         mListeners.remove(listener);443     }444 445     private interface WindowListener {446         void windowsChanged();447 448         void focusChanged();449     }450 451 452     private class ViewServerWorker implements Runnable, WindowListener {453         private Socket mClient;454         private boolean mNeedWindowListUpdate;455         private boolean mNeedFocusedWindowUpdate;456 457         private final Object[] mLock = new Object[0];458 459         public ViewServerWorker(Socket client) {460             mClient = client;461             mNeedWindowListUpdate = false;462             mNeedFocusedWindowUpdate = false;463         }464 465         public void run() {466             BufferedReader in = null;467             try {468                 in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);469 470                 final String request = in.readLine();471 472                 Log.i("Command", "===>" + request);473 474                 String command;475                 String parameters;476 477                 int index = request.indexOf(' ');478                 if (index == -1) {479                     command = request;480                     parameters = "";481                 } else {482                     command = request.substring(0, index);483                     parameters = request.substring(index + 1);484                 }485 486                 boolean result;487                 if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) {488                     result = writeValue(mClient, VALUE_PROTOCOL_VERSION);489                 } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) {490                     result = writeValue(mClient, VALUE_SERVER_VERSION);491                 } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {492                     result = listWindows(mClient);493                 } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {494                     result = getFocusedWindow(mClient);495                 } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {496                     result = windowManagerAutolistLoop();497                 } else {498                     result = windowCommand(mClient, command, parameters);499                 }500 501                 if (!result) {502                     Log.w(LOG_TAG, "An error occurred with the command: " + command);503                 }504             } catch (IOException e) {505                 Log.w(LOG_TAG, "Connection error: ", e);506             } finally {507                 if (in != null) {508                     try {509                         in.close();510 511                     } catch (IOException e) {512                         e.printStackTrace();513                     }514                 }515                 if (mClient != null) {516                     try {517                         mClient.close();518                     } catch (IOException e) {519                         e.printStackTrace();520                     }521                 }522             }523         }524 525         private boolean windowCommand(Socket client, String command, String parameters) {526             boolean success = true;527             BufferedWriter out = null;528 529             try {530                 // Find the hash code of the window531                 int index = parameters.indexOf(' ');532                 if (index == -1) {533                     index = parameters.length();534                 }535                 final String code = parameters.substring(0, index);536                 int hashCode = (int) Long.parseLong(code, 16);537 538                 // Extract the command's parameter after the window description539                 if (index < parameters.length()) {540                     parameters = parameters.substring(index + 1);541                 } else {542                     parameters = "";543                 }544 545                 final View window = findWindow(hashCode);546                 if (window == null) {547                     return false;548                 }549 550                 // call stuff551                 final Method dispatch = ViewDebug.class.getDeclaredMethod("dispatchCommand",552                         View.class, String.class, String.class, OutputStream.class);553                 dispatch.setAccessible(true);554                 dispatch.invoke(null, window, command, parameters,555                         new UncloseableOutputStream(client.getOutputStream()));556 557                 if (!client.isOutputShutdown()) {558                     out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));559                     out.write("DONE\n");560                     out.flush();561                 }562 563             } catch (Exception e) {564                 Log.w(LOG_TAG, "Could not send command " + command +565                         " with parameters " + parameters, e);566                 success = false;567             } finally {568                 if (out != null) {569                     try {570                         out.close();571                     } catch (IOException e) {572                         success = false;573                     }574                 }575             }576 577             return success;578         }579 580         private View findWindow(int hashCode) {581             if (hashCode == -1) {582                 View window = null;583                 mWindowsLock.readLock().lock();584                 try {585                     window = mFocusedWindow;586                 } finally {587                     mWindowsLock.readLock().unlock();588                 }589                 return window;590             }591 592 593             mWindowsLock.readLock().lock();594             try {595                 for (Entry<View, String> entry : mWindows.entrySet()) {596                     if (System.identityHashCode(entry.getKey()) == hashCode) {597                         return entry.getKey();598                     }599                 }600             } finally {601                 mWindowsLock.readLock().unlock();602             }603 604             return null;605         }606 607         private boolean listWindows(Socket client) {608             boolean result = true;609             BufferedWriter out = null;610 611             try {612                 mWindowsLock.readLock().lock();613 614                 OutputStream clientStream = client.getOutputStream();615                 out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);616 617                 for (Entry<View, String> entry : mWindows.entrySet()) {618                     out.write(Integer.toHexString(System.identityHashCode(entry.getKey())));619                     out.write(' ');620                     out.append(entry.getValue());621                     out.write('\n');622                 }623 624                 out.write("DONE.\n");625                 out.flush();626             } catch (Exception e) {627                 result = false;628             } finally {629                 mWindowsLock.readLock().unlock();630 631                 if (out != null) {632                     try {633                         out.close();634                     } catch (IOException e) {635                         result = false;636                     }637                 }638             }639 640             return result;641         }642 643         private boolean getFocusedWindow(Socket client) {644             boolean result = true;645             String focusName = null;646 647             BufferedWriter out = null;648             try {649                 OutputStream clientStream = client.getOutputStream();650                 out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);651 652                 View focusedWindow = null;653 654                 mFocusLock.readLock().lock();655                 try {656                     focusedWindow = mFocusedWindow;657                 } finally {658                     mFocusLock.readLock().unlock();659                 }660 661                 if (focusedWindow != null) {662                     mWindowsLock.readLock().lock();663                     try {664                         focusName = mWindows.get(mFocusedWindow);665                     } finally {666                         mWindowsLock.readLock().unlock();667                     }668 669                     out.write(Integer.toHexString(System.identityHashCode(focusedWindow)));670                     out.write(' ');671                     out.append(focusName);672                 }673                 out.write('\n');674                 out.flush();675             } catch (Exception e) {676                 result = false;677             } finally {678                 if (out != null) {679                     try {680                         out.close();681                     } catch (IOException e) {682                         result = false;683                     }684                 }685             }686 687             return result;688         }689 690         public void windowsChanged() {691             synchronized (mLock) {692                 mNeedWindowListUpdate = true;693                 mLock.notifyAll();694             }695         }696 697         public void focusChanged() {698             synchronized (mLock) {699                 mNeedFocusedWindowUpdate = true;700                 mLock.notifyAll();701             }702         }703 704         private boolean windowManagerAutolistLoop() {705             addWindowListener(this);706             BufferedWriter out = null;707             try {708                 out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));709                 while (!Thread.interrupted()) {710                     boolean needWindowListUpdate = false;711                     boolean needFocusedWindowUpdate = false;712                     synchronized (mLock) {713                         while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) {714                             mLock.wait();715                         }716                         if (mNeedWindowListUpdate) {717                             mNeedWindowListUpdate = false;718                             needWindowListUpdate = true;719                         }720                         if (mNeedFocusedWindowUpdate) {721                             mNeedFocusedWindowUpdate = false;722                             needFocusedWindowUpdate = true;723                         }724                     }725                     if (needWindowListUpdate) {726                         out.write("LIST UPDATE\n");727                         out.flush();728                     }729                     if (needFocusedWindowUpdate) {730                         out.write("FOCUS UPDATE\n");731                         out.flush();732                     }733                 }734             } catch (Exception e) {735                 Log.w(LOG_TAG, "Connection error: ", e);736             } finally {737                 if (out != null) {738                     try {739                         out.close();740                     } catch (IOException e) {741                         // Ignore742                     }743                 }744                 removeWindowListener(this);745             }746             return true;747         }748     }749 750     private static class UncloseableOutputStream extends OutputStream {751         private final OutputStream mStream;752 753         UncloseableOutputStream(OutputStream stream) {754             mStream = stream;755         }756 757         public void close() throws IOException {758             // Don't close the stream759         }760 761         public boolean equals(Object o) {762             return mStream.equals(o);763         }764 765         public void flush() throws IOException {766             mStream.flush();767         }768 769         public int hashCode() {770             return mStream.hashCode();771         }772 773         public String toString() {774             return mStream.toString();775         }776 777         public void write(byte[] buffer, int offset, int count)778                 throws IOException {779             mStream.write(buffer, offset, count);780         }781 782         public void write(byte[] buffer) throws IOException {783             mStream.write(buffer);784         }785 786         public void write(int oneByte) throws IOException {787             mStream.write(oneByte);788         }789     }790 791     /**792      * 一个空的ViewServer类793      */794     private static class NoopViewServer extends ViewServer {795         private NoopViewServer() {796         }797 798         @Override799         public boolean start() throws IOException {800             return false;801         }802 803         @Override804         public boolean stop() {805             return false;806         }807 808         @Override809         public boolean isRunning() {810             return false;811         }812 813         @Override814         public void addWindow(Activity activity) {815         }816 817         @Override818         public void removeWindow(Activity activity) {819         }820 821         @Override822         public void addWindow(View view, String name) {823         }824 825         @Override826         public void removeWindow(View view) {827         }828 829         @Override830         public void setFocusedWindow(Activity activity) {831         }832 833         @Override834         public void setFocusedWindow(View view) {835         }836 837         @Override838         public void run() {839         }840     }841 }
解决问题的类

使用方法如下:

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的解决方法

更多相关文章

  1. Android Selector原理
  2. 使用QT调用FFMPEG库部署到Android设备、完成视频音频应用开发
  3. 在Android命令行启动程序的方法
  4. Android framework工作方式及原理
  5. android studio --terminal 命令编译项目(仅限于ubuntu平台)
  6. Android 4.4 KitKat NotificationManagerService使用详解与原理

随机推荐

  1. Android获取运营商代码
  2. 【Arcgis for android】保存地图截图到sd
  3. android 仿QQ手机版
  4. Android资源汇总贴
  5. android XML下searchable.xml
  6. Android的SDK与ADT不匹配问题
  7. Android(安卓)Fresco属性大全,中文说明
  8. Android虚拟机大屏幕设置(开发平板电脑程
  9. Android(安卓)开发集锦
  10. android与服务端通信