[原创]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  * <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 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<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 be
154      * called from the main thread of your application. The server will have
155      * 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 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<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                         // 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的,所以这点不用担心。

好了,祝大家春节快乐

 

posted @ 2015-02-16 16:02  周柯文  阅读(13124)  评论(9编辑  收藏  举报