最近在看一个老外写的东西,发现里面有个类,使用这个类可以让任何设备使用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 *81 * 82 *This class can be used to enable the use of HierarchyViewer inside an
48 * application. HierarchyViewer is an Android SDK tool that can be used 49 * to inspect and debug the user interface of running applications. For 50 * security reasons, HierarchyViewer does not work on production builds 51 * (for instance phones bought in store.) By using this class, you can 52 * make HierarchyViewer work on any device. You must be very careful 53 * however to only enable HierarchyViewer when debugging your 54 * application. 55 * 56 *To use this view server, your application must require the INTERNET
57 * permission. 58 * 59 *The recommended way to use this API is to register activities when
60 * they are created, and to unregister them when they get destroyed: 61 * 62 *63 * public class MyActivity extends Activity { 64 * public void onCreate(Bundle savedInstanceState) { 65 * super.onCreate(savedInstanceState); 66 * // Set content view, etc. 67 * ViewServer.get(this).addWindow(this); 68 * } 69 * 70 * public void onDestroy() { 71 * super.onDestroy(); 72 * ViewServer.get(this).removeWindow(this); 73 * } 74 * 75 * public void onResume() { 76 * super.onResume(); 77 * ViewServer.get(this).setFocusedWindow(this); 78 * } 79 * } 80 *
83 * In a similar fashion, you can use this API with an InputMethodService: 84 * 85 * 86 *
87 * public class MyInputMethodService extends InputMethodService { 88 * public void onCreate() { 89 * super.onCreate(); 90 * View decorView = getWindow().getWindow().getDecorView(); 91 * String name = "MyInputMethodService"; 92 * ViewServer.get(this).addWindow(decorView, name); 93 * } 94 * 95 * public void onDestroy() { 96 * super.onDestroy(); 97 * View decorView = getWindow().getWindow().getDecorView(); 98 * ViewServer.get(this).removeWindow(decorView); 99 * } 100 * 101 * public void onStartInput(EditorInfo attribute, boolean restarting) { 102 * super.onStartInput(attribute, restarting); 103 * View decorView = getWindow().getWindow().getDecorView(); 104 * ViewServer.get(this).setFocusedWindow(decorView); 105 * } 106 * } 107 * 108 */ 109 public class ViewServer implements Runnable { 110 /** 111 * The default port used to start view servers. 112 */ 113 private static final int VIEW_SERVER_DEFAULT_PORT = 4939; 114 private static final int VIEW_SERVER_MAX_CONNECTIONS = 10; 115 private static final String BUILD_TYPE_USER = "user"; 116 117 // Debug facility 118 private static final String LOG_TAG = "ViewServer"; 119 120 private static final String VALUE_PROTOCOL_VERSION = "4"; 121 private static final String VALUE_SERVER_VERSION = "4"; 122 123 // Protocol commands 124 // Returns the protocol version 125 private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL"; 126 // Returns the server version 127 private static final String COMMAND_SERVER_VERSION = "SERVER"; 128 // Lists all of the available windows in the system 129 private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST"; 130 // Keeps a connection open and notifies when the list of windows changes 131 private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST"; 132 // Returns the focused window 133 private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS"; 134 135 private ServerSocket mServer; 136 private final int mPort; 137 138 private Thread mThread; 139 private ExecutorService mThreadPool; 140 141 private final List
android:debuggable
158 * flag set in its manifest, the server returned by this method will
159 * be a dummy object that does not do anything. This allows you to use
160 * the same code in debug and release versions of your application.
161 *
162 * @param context A Context used to check whether the application is
163 * debuggable, this can be the application context
164 */
165 public static ViewServer get(Context context) {
166 ApplicationInfo info = context.getApplicationInfo();
167 if (BUILD_TYPE_USER.equals(Build.TYPE) &&
168 (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
169 if (sServer == null) {
170 sServer = new ViewServer(ViewServer.VIEW_SERVER_DEFAULT_PORT);
171 }
172
173 if (!sServer.isRunning()) {
174 try {
175 sServer.start();
176 } catch (IOException e) {
177 Log.d(LOG_TAG, "Error:", e);
178 }
179 }
180 } else {
181 sServer = new NoopViewServer();
182 }
183
184 return sServer;
185 }
186
187 private ViewServer() {
188 mPort = -1;
189 }
190
191 /**
192 * Creates a new ViewServer associated with the specified window manager on the
193 * specified local port. The server is not started by default.
194 *
195 * @param port The port for the server to listen to.
196 * @see #start()
197 */
198 private ViewServer(int port) {
199 mPort = port;
200 }
201
202 /**
203 * Starts the server.
204 *
205 * @return True if the server was successfully created, or false if it already exists.
206 * @throws java.io.IOException If the server cannot be created.
207 * @see #stop()
208 * @see #isRunning()
209 */
210 public boolean start() throws IOException {
211 if (mThread != null) {
212 return false;
213 }
214
215 mThread = new Thread(this, "Local View Server [port=" + mPort + "]");
216 mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS);
217 mThread.start();
218
219 return true;
220 }
221
222 /**
223 * Stops the server.
224 *
225 * @return True if the server was stopped, false if an error occurred or if the
226 * server wasn't started.
227 * @see #start()
228 * @see #isRunning()
229 */
230 public boolean stop() {
231 if (mThread != null) {
232 mThread.interrupt();
233 if (mThreadPool != null) {
234 try {
235 mThreadPool.shutdownNow();
236 } catch (SecurityException e) {
237 Log.w(LOG_TAG, "Could not stop all view server threads");
238 }
239 }
240
241 mThreadPool = null;
242 mThread = null;
243
244 try {
245 mServer.close();
246 mServer = null;
247 return true;
248 } catch (IOException e) {
249 Log.w(LOG_TAG, "Could not close the view server");
250 }
251 }
252
253 mWindowsLock.writeLock().lock();
254 try {
255 mWindows.clear();
256 } finally {
257 mWindowsLock.writeLock().unlock();
258 }
259
260 mFocusLock.writeLock().lock();
261 try {
262 mFocusedWindow = null;
263 } finally {
264 mFocusLock.writeLock().unlock();
265 }
266
267 return false;
268 }
269
270 /**
271 * Indicates whether the server is currently running.
272 *
273 * @return True if the server is running, false otherwise.
274 * @see #start()
275 * @see #stop()
276 */
277 public boolean isRunning() {
278 return mThread != null && mThread.isAlive();
279 }
280
281 /**
282 * Invoke this method to register a new view hierarchy.
283 *
284 * @param activity The activity whose view hierarchy/window to register
285 * @see #addWindow(android.view.View, String)
286 * @see #removeWindow(android.app.Activity)
287 */
288 public void addWindow(Activity activity) {
289 String name = activity.getTitle().toString();
290 if (TextUtils.isEmpty(name)) {
291 name = activity.getClass().getCanonicalName() +
292 "/0x" + System.identityHashCode(activity);
293 } else {
294 name += "(" + activity.getClass().getCanonicalName() + ")";
295 }
296 addWindow(activity.getWindow().getDecorView(), name);
297 }
298
299 /**
300 * Invoke this method to unregister a view hierarchy.
301 *
302 * @param activity The activity whose view hierarchy/window to unregister
303 * @see #addWindow(android.app.Activity)
304 * @see #removeWindow(android.view.View)
305 */
306 public void removeWindow(Activity activity) {
307 removeWindow(activity.getWindow().getDecorView());
308 }
309
310 /**
311 * Invoke this method to register a new view hierarchy.
312 *
313 * @param view A view that belongs to the view hierarchy/window to register
314 * @name name The name of the view hierarchy/window to register
315 * @see #removeWindow(android.view.View)
316 */
317 public void addWindow(View view, String name) {
318 mWindowsLock.writeLock().lock();
319 try {
320 mWindows.put(view.getRootView(), name);
321 } finally {
322 mWindowsLock.writeLock().unlock();
323 }
324 fireWindowsChangedEvent();
325 }
326
327 /**
328 * Invoke this method to unregister a view hierarchy.
329 *
330 * @param view A view that belongs to the view hierarchy/window to unregister
331 * @see #addWindow(android.view.View, String)
332 */
333 public void removeWindow(View view) {
334 mWindowsLock.writeLock().lock();
335 try {
336 mWindows.remove(view.getRootView());
337 } finally {
338 mWindowsLock.writeLock().unlock();
339 }
340 fireWindowsChangedEvent();
341 }
342
343 /**
344 * Invoke this method to change the currently focused window.
345 *
346 * @param activity The activity whose view hierarchy/window hasfocus,
347 * or null to remove focus
348 */
349 public void setFocusedWindow(Activity activity) {
350 setFocusedWindow(activity.getWindow().getDecorView());
351 }
352
353 /**
354 * Invoke this method to change the currently focused window.
355 *
356 * @param view A view that belongs to the view hierarchy/window that has focus,
357 * or null to remove focus
358 */
359 public void setFocusedWindow(View view) {
360 mFocusLock.writeLock().lock();
361 try {
362 mFocusedWindow = view == null ? null : view.getRootView();
363 } finally {
364 mFocusLock.writeLock().unlock();
365 }
366 fireFocusChangedEvent();
367 }
368
369 /**
370 * Main server loop.
371 */
372 public void run() {
373 try {
374 InetAddress address = InetAddress.getLocalHost();
375 mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, address);
376 } catch (Exception e) {
377 Log.w(LOG_TAG, "Starting ServerSocket error: ", e);
378 }
379
380 while (mServer != null && Thread.currentThread() == mThread) {
381 // Any uncaught exception will crash the system process
382 try {
383 Socket client = mServer.accept();
384 if (mThreadPool != null) {
385 mThreadPool.submit(new ViewServerWorker(client));
386 } else {
387 try {
388 client.close();
389 } catch (IOException e) {
390 e.printStackTrace();
391 }
392 }
393 } catch (Exception e) {
394 Log.w(LOG_TAG, "Connection error: ", e);
395 }
396 }
397 }
398
399 private static boolean writeValue(Socket client, String value) {
400 boolean result;
401 BufferedWriter out = null;
402 try {
403 OutputStream clientStream = client.getOutputStream();
404 out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
405 out.write(value);
406 out.write("\n");
407 out.flush();
408 result = true;
409 } catch (Exception e) {
410 result = false;
411 } finally {
412 if (out != null) {
413 try {
414 out.close();
415 } catch (IOException e) {
416 result = false;
417 }
418 }
419 }
420 return result;
421 }
422
423 private void fireWindowsChangedEvent() {
424 for (WindowListener listener : mListeners) {
425 listener.windowsChanged();
426 }
427 }
428
429 private void fireFocusChangedEvent() {
430 for (WindowListener listener : mListeners) {
431 listener.focusChanged();
432 }
433 }
434
435 private void addWindowListener(WindowListener listener) {
436 if (!mListeners.contains(listener)) {
437 mListeners.add(listener);
438 }
439 }
440
441 private void removeWindowListener(WindowListener listener) {
442 mListeners.remove(listener);
443 }
444
445 private interface WindowListener {
446 void windowsChanged();
447
448 void focusChanged();
449 }
450
451
452 private class ViewServerWorker implements Runnable, WindowListener {
453 private Socket mClient;
454 private boolean mNeedWindowListUpdate;
455 private boolean mNeedFocusedWindowUpdate;
456
457 private final Object[] mLock = new Object[0];
458
459 public ViewServerWorker(Socket client) {
460 mClient = client;
461 mNeedWindowListUpdate = false;
462 mNeedFocusedWindowUpdate = false;
463 }
464
465 public void run() {
466 BufferedReader in = null;
467 try {
468 in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);
469
470 final String request = in.readLine();
471
472 Log.i("Command", "===>" + request);
473
474 String command;
475 String parameters;
476
477 int index = request.indexOf(' ');
478 if (index == -1) {
479 command = request;
480 parameters = "";
481 } else {
482 command = request.substring(0, index);
483 parameters = request.substring(index + 1);
484 }
485
486 boolean result;
487 if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) {
488 result = writeValue(mClient, VALUE_PROTOCOL_VERSION);
489 } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) {
490 result = writeValue(mClient, VALUE_SERVER_VERSION);
491 } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {
492 result = listWindows(mClient);
493 } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {
494 result = getFocusedWindow(mClient);
495 } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {
496 result = windowManagerAutolistLoop();
497 } else {
498 result = windowCommand(mClient, command, parameters);
499 }
500
501 if (!result) {
502 Log.w(LOG_TAG, "An error occurred with the command: " + command);
503 }
504 } catch (IOException e) {
505 Log.w(LOG_TAG, "Connection error: ", e);
506 } finally {
507 if (in != null) {
508 try {
509 in.close();
510
511 } catch (IOException e) {
512 e.printStackTrace();
513 }
514 }
515 if (mClient != null) {
516 try {
517 mClient.close();
518 } catch (IOException e) {
519 e.printStackTrace();
520 }
521 }
522 }
523 }
524
525 private boolean windowCommand(Socket client, String command, String parameters) {
526 boolean success = true;
527 BufferedWriter out = null;
528
529 try {
530 // Find the hash code of the window
531 int index = parameters.indexOf(' ');
532 if (index == -1) {
533 index = parameters.length();
534 }
535 final String code = parameters.substring(0, index);
536 int hashCode = (int) Long.parseLong(code, 16);
537
538 // Extract the command's parameter after the window description
539 if (index < parameters.length()) {
540 parameters = parameters.substring(index + 1);
541 } else {
542 parameters = "";
543 }
544
545 final View window = findWindow(hashCode);
546 if (window == null) {
547 return false;
548 }
549
550 // call stuff
551 final Method dispatch = ViewDebug.class.getDeclaredMethod("dispatchCommand",
552 View.class, String.class, String.class, OutputStream.class);
553 dispatch.setAccessible(true);
554 dispatch.invoke(null, window, command, parameters,
555 new UncloseableOutputStream(client.getOutputStream()));
556
557 if (!client.isOutputShutdown()) {
558 out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
559 out.write("DONE\n");
560 out.flush();
561 }
562
563 } catch (Exception e) {
564 Log.w(LOG_TAG, "Could not send command " + command +
565 " with parameters " + parameters, e);
566 success = false;
567 } finally {
568 if (out != null) {
569 try {
570 out.close();
571 } catch (IOException e) {
572 success = false;
573 }
574 }
575 }
576
577 return success;
578 }
579
580 private View findWindow(int hashCode) {
581 if (hashCode == -1) {
582 View window = null;
583 mWindowsLock.readLock().lock();
584 try {
585 window = mFocusedWindow;
586 } finally {
587 mWindowsLock.readLock().unlock();
588 }
589 return window;
590 }
591
592
593 mWindowsLock.readLock().lock();
594 try {
595 for (Entry
使用方法如下:
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的,所以这点不用担心。