[原创]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实现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的,所以这点不用担心。
好了,祝大家春节快乐!