[原创]HierarchyView的实现原理和Android设备无法使用HierarchyView的解决方法

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

  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 the
116      *         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 process
165             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                 // Ignore
323             } finally {
324                 if (out != null) {
325                     try {
326                         out.close();
327                     } catch (IOException e) {
328                         // Ignore
329                     }
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了。

老外就是用的这个方法。所以我们就不用重复造轮子了

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

  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  * 

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.

55 *

56 *

To use this view server, your application must require the INTERNET 57 * permission.

58 *

59 *

The recommended way to use this API is to register activities when 60 * they are created, and to unregister them when they get destroyed:

61 *

62 *

 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  * 
81 *

82 *

83 * In a similar fashion, you can use this API with an InputMethodService: 84 *

85 *

86 *

 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  * 
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 facility 118 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 commands 124 // Returns the protocol version 125 private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL"; 126 // Returns the server version 127 private static final String COMMAND_SERVER_VERSION = "SERVER"; 128 // Lists all of the available windows in the system 129 private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST"; 130 // Keeps a connection open and notifies when the list of windows changes 131 private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST"; 132 // Returns the focused window 133 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 mListeners = 142 new CopyOnWriteArrayList(); 143 144 private final HashMap mWindows = new HashMap(); 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 be 154 * called from the main thread of your application. The server will have 155 * the same lifetime as your process. 156 *

157 * If your application does not have the android:debuggable 158 * flag set in its manifest, the server returned by this method will 159 * be a dummy object that does not do anything. This allows you to use 160 * the same code in debug and release versions of your application. 161 * 162 * @param context A Context used to check whether the application is 163 * debuggable, this can be the application context 164 */ 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 the 193 * 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 the 226 * 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 register 285 * @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 unregister 303 * @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 register 314 * @name name The name of the view hierarchy/window to register 315 * @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 unregister 331 * @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 focus 348 */ 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 focus 358 */ 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 process 382 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 window 531 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 description 539 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 stuff 551 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 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 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 // Ignore 742 } 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 stream 759 } 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 @Override 799 public boolean start() throws IOException { 800 return false; 801 } 802 803 @Override 804 public boolean stop() { 805 return false; 806 } 807 808 @Override 809 public boolean isRunning() { 810 return false; 811 } 812 813 @Override 814 public void addWindow(Activity activity) { 815 } 816 817 @Override 818 public void removeWindow(Activity activity) { 819 } 820 821 @Override 822 public void addWindow(View view, String name) { 823 } 824 825 @Override 826 public void removeWindow(View view) { 827 } 828 829 @Override 830 public void setFocusedWindow(Activity activity) { 831 } 832 833 @Override 834 public void setFocusedWindow(View view) { 835 } 836 837 @Override 838 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的,所以这点不用担心。

好了,祝大家春节快乐

 

转载于:https://www.cnblogs.com/coding-way/p/4294225.html

你可能感兴趣的:([原创]HierarchyView的实现原理和Android设备无法使用HierarchyView的解决方法)