[原创]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 the116 * server wasn't started.117 *118 * @see #start()119 * @see #isRunning()120 * @see WindowManagerService#stopViewServer()121 */122 boolean stop() {123 if (mThread != null) {124 125 mThread.interrupt();126 if (mThreadPool != null) {127 try {128 mThreadPool.shutdownNow();129 } catch (SecurityException e) {130 Slog.w(LOG_TAG, "Could not stop all view server threads");131 }132 }133 mThreadPool = null;134 mThread = null;135 try {136 mServer.close();137 mServer = null;138 return true;139 } catch (IOException e) {140 Slog.w(LOG_TAG, "Could not close the view server");141 }142 }143 return false;144 }145 146 /**147 * Indicates whether the server is currently running.148 *149 * @return True if the server is running, false otherwise.150 *151 * @see #start()152 * @see #stop()153 * @see WindowManagerService#isViewServerRunning() 154 */155 boolean isRunning() {156 return mThread != null && mThread.isAlive();157 }158 159 /**160 * Main server loop.161 */162 public void run() {163 while (Thread.currentThread() == mThread) {164 // Any uncaught exception will crash the system process165 try {166 Socket client = mServer.accept();167 if (mThreadPool != null) {168 mThreadPool.submit(new ViewServerWorker(client));169 } else {170 try {171 client.close();172 } catch (IOException e) {173 e.printStackTrace();174 }175 }176 } catch (Exception e) {177 Slog.w(LOG_TAG, "Connection error: ", e);178 }179 }180 }181 182 private static boolean writeValue(Socket client, String value) {183 boolean result;184 BufferedWriter out = null;185 try {186 OutputStream clientStream = client.getOutputStream();187 out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);188 out.write(value);189 out.write("\n");190 out.flush();191 result = true;192 } catch (Exception e) {193 result = false;194 } finally {195 if (out != null) {196 try {197 out.close();198 } catch (IOException e) {199 result = false;200 }201 }202 }203 return result;204 }205 206 class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener {207 private Socket mClient;208 private boolean mNeedWindowListUpdate;209 private boolean mNeedFocusedWindowUpdate;210 211 public ViewServerWorker(Socket client) {212 mClient = client;213 mNeedWindowListUpdate = false;214 mNeedFocusedWindowUpdate = false;215 }216 217 public void run() {218 219 BufferedReader in = null;220 try {221 in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);222 223 final String request = in.readLine();224 225 String command;226 String parameters;227 228 int index = request.indexOf(' ');229 if (index == -1) {230 command = request;231 parameters = "";232 } else {233 command = request.substring(0, index);234 parameters = request.substring(index + 1);235 }236 237 boolean result;238 if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) {239 result = writeValue(mClient, VALUE_PROTOCOL_VERSION);240 } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) {241 result = writeValue(mClient, VALUE_SERVER_VERSION);242 } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {243 result = mWindowManager.viewServerListWindows(mClient);244 } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {245 result = mWindowManager.viewServerGetFocusedWindow(mClient);246 } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {247 result = windowManagerAutolistLoop();248 } else {249 result = mWindowManager.viewServerWindowCommand(mClient,250 command, parameters);251 }252 253 if (!result) {254 Slog.w(LOG_TAG, "An error occurred with the command: " + command);255 }256 } catch(IOException e) {257 Slog.w(LOG_TAG, "Connection error: ", e);258 } finally {259 if (in != null) {260 try {261 in.close();262 263 } catch (IOException e) {264 e.printStackTrace();265 }266 }267 if (mClient != null) {268 try {269 mClient.close();270 } catch (IOException e) {271 e.printStackTrace();272 }273 }274 }275 }276 277 public void windowsChanged() {278 synchronized(this) {279 mNeedWindowListUpdate = true;280 notifyAll();281 }282 }283 284 public void focusChanged() {285 synchronized(this) {286 mNeedFocusedWindowUpdate = true;287 notifyAll();288 }289 }290 291 private boolean windowManagerAutolistLoop() {292 mWindowManager.addWindowChangeListener(this);293 BufferedWriter out = null;294 try {295 out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));296 while (!Thread.interrupted()) {297 boolean needWindowListUpdate = false;298 boolean needFocusedWindowUpdate = false;299 synchronized (this) {300 while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) {301 wait();302 }303 if (mNeedWindowListUpdate) {304 mNeedWindowListUpdate = false;305 needWindowListUpdate = true;306 }307 if (mNeedFocusedWindowUpdate) {308 mNeedFocusedWindowUpdate = false;309 needFocusedWindowUpdate = true;310 }311 }312 if (needWindowListUpdate) {313 out.write("LIST UPDATE\n");314 out.flush();315 }316 if (needFocusedWindowUpdate) {317 out.write("ACTION_FOCUS UPDATE\n");318 out.flush();319 }320 }321 } catch (Exception e) {322 // Ignore323 } finally {324 if (out != null) {325 try {326 out.close();327 } catch (IOException e) {328 // Ignore329 }330 }331 mWindowManager.removeWindowChangeListener(this);332 }333 return true;334 }335 }336 }ViewServer.java
可以看到,ViewServer实现Runnable,接下来看看start的实现:
boolean start() throws IOException { if (mThread != null) { return false; } mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost()); mThread = new Thread(this, "Remote View Server [port=" + mPort + "]"); mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS); mThread.start(); return true; } public void run() { while (Thread.currentThread() == mThread) { // Any uncaught exception will crash the system process try { Socket client = mServer.accept(); if (mThreadPool != null) { mThreadPool.submit(new ViewServerWorker(client)); } else { try { client.close(); } catch (IOException e) { e.printStackTrace(); } } } catch (Exception e) { Slog.w(LOG_TAG, "Connection error: ", e); } } }
这个Server启动后,使用之前传进来的端口号(4939)创建个ServerSocket,然后在独立的线程里监听这个端口是否有客户端连接请求,有的话传给ViewServerWorker去处理:
class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener { private Socket mClient; private boolean mNeedWindowListUpdate; private boolean mNeedFocusedWindowUpdate; public ViewServerWorker(Socket client) { mClient = client; mNeedWindowListUpdate = false; mNeedFocusedWindowUpdate = false; } public void run() { BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024); final String request = in.readLine(); String command; String parameters; int index = request.indexOf(' '); if (index == -1) { command = request; parameters = ""; } else { command = request.substring(0, index); parameters = request.substring(index + 1); } boolean result; if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) { result = writeValue(mClient, VALUE_PROTOCOL_VERSION); } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) { result = writeValue(mClient, VALUE_SERVER_VERSION); } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) { result = mWindowManager.viewServerListWindows(mClient); } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) { result = mWindowManager.viewServerGetFocusedWindow(mClient); } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) { result = windowManagerAutolistLoop(); } else { result = mWindowManager.viewServerWindowCommand(mClient, command, parameters); } if (!result) { Slog.w(LOG_TAG, "An error occurred with the command: " + command); } } catch(IOException e) { Slog.w(LOG_TAG, "Connection error: ", e); } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if (mClient != null) { try { mClient.close(); } catch (IOException e) { e.printStackTrace(); } } } } public void windowsChanged() { synchronized(this) { mNeedWindowListUpdate = true; notifyAll(); } } public void focusChanged() { synchronized(this) { mNeedFocusedWindowUpdate = true; notifyAll(); } } private boolean windowManagerAutolistLoop() { mWindowManager.addWindowChangeListener(this); BufferedWriter out = null; try { out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream())); while (!Thread.interrupted()) { boolean needWindowListUpdate = false; boolean needFocusedWindowUpdate = false; synchronized (this) { while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) { wait(); } if (mNeedWindowListUpdate) { mNeedWindowListUpdate = false; needWindowListUpdate = true; } if (mNeedFocusedWindowUpdate) { mNeedFocusedWindowUpdate = false; needFocusedWindowUpdate = true; } } if (needWindowListUpdate) { out.write("LIST UPDATE\n"); out.flush(); } if (needFocusedWindowUpdate) { out.write("ACTION_FOCUS UPDATE\n"); out.flush(); } } } catch (Exception e) { // Ignore } finally { if (out != null) { try { out.close(); } catch (IOException e) { // Ignore } } mWindowManager.removeWindowChangeListener(this); } return true; } }
从代码中可以看到,HierarchyView通过Socket向设备发送命令,ViewServerWorker来解析处理命令,并把需要返回的值通过socket再发给HierarchyView。
至此,HierarchyView的大致原理已经了解,发现只要我们自己创建个ServerSocket,并且监听4939端口,然后模仿ViewServer处理相应命令就可以让设备使用HierarchyView了。
老外就是用的这个方法。所以我们就不用重复造轮子了。
接下来看看老外的解决方法:
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 * 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 facility118 private static final String LOG_TAG = "ViewServer";119 120 private static final String VALUE_PROTOCOL_VERSION = "4";121 private static final String VALUE_SERVER_VERSION = "4";122 123 // Protocol commands124 // Returns the protocol version125 private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL";126 // Returns the server version127 private static final String COMMAND_SERVER_VERSION = "SERVER";128 // Lists all of the available windows in the system129 private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST";130 // Keeps a connection open and notifies when the list of windows changes131 private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST";132 // Returns the focused window133 private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS";134 135 private ServerSocket mServer;136 private final int mPort;137 138 private Thread mThread;139 private ExecutorService mThreadPool;140 141 private final ListThis 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 *
android:debuggable
158 * flag set in its manifest, the server returned by this method will159 * be a dummy object that does not do anything. This allows you to use160 * the same code in debug and release versions of your application.161 *162 * @param context A Context used to check whether the application is163 * debuggable, this can be the application context164 */165 public static ViewServer get(Context context) {166 ApplicationInfo info = context.getApplicationInfo();167 if (BUILD_TYPE_USER.equals(Build.TYPE) &&168 (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {169 if (sServer == null) {170 sServer = new ViewServer(ViewServer.VIEW_SERVER_DEFAULT_PORT);171 }172 173 if (!sServer.isRunning()) {174 try {175 sServer.start();176 } catch (IOException e) {177 Log.d(LOG_TAG, "Error:", e);178 }179 }180 } else {181 sServer = new NoopViewServer();182 }183 184 return sServer;185 }186 187 private ViewServer() {188 mPort = -1;189 }190 191 /**192 * Creates a new ViewServer associated with the specified window manager on the193 * specified local port. The server is not started by default.194 *195 * @param port The port for the server to listen to.196 * @see #start()197 */198 private ViewServer(int port) {199 mPort = port;200 }201 202 /**203 * Starts the server.204 *205 * @return True if the server was successfully created, or false if it already exists.206 * @throws java.io.IOException If the server cannot be created.207 * @see #stop()208 * @see #isRunning()209 */210 public boolean start() throws IOException {211 if (mThread != null) {212 return false;213 }214 215 mThread = new Thread(this, "Local View Server [port=" + mPort + "]");216 mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS);217 mThread.start();218 219 return true;220 }221 222 /**223 * Stops the server.224 *225 * @return True if the server was stopped, false if an error occurred or if the226 * server wasn't started.227 * @see #start()228 * @see #isRunning()229 */230 public boolean stop() {231 if (mThread != null) {232 mThread.interrupt();233 if (mThreadPool != null) {234 try {235 mThreadPool.shutdownNow();236 } catch (SecurityException e) {237 Log.w(LOG_TAG, "Could not stop all view server threads");238 }239 }240 241 mThreadPool = null;242 mThread = null;243 244 try {245 mServer.close();246 mServer = null;247 return true;248 } catch (IOException e) {249 Log.w(LOG_TAG, "Could not close the view server");250 }251 }252 253 mWindowsLock.writeLock().lock();254 try {255 mWindows.clear();256 } finally {257 mWindowsLock.writeLock().unlock();258 }259 260 mFocusLock.writeLock().lock();261 try {262 mFocusedWindow = null;263 } finally {264 mFocusLock.writeLock().unlock();265 }266 267 return false;268 }269 270 /**271 * Indicates whether the server is currently running.272 *273 * @return True if the server is running, false otherwise.274 * @see #start()275 * @see #stop()276 */277 public boolean isRunning() {278 return mThread != null && mThread.isAlive();279 }280 281 /**282 * Invoke this method to register a new view hierarchy.283 *284 * @param activity The activity whose view hierarchy/window to register285 * @see #addWindow(android.view.View, String)286 * @see #removeWindow(android.app.Activity)287 */288 public void addWindow(Activity activity) {289 String name = activity.getTitle().toString();290 if (TextUtils.isEmpty(name)) {291 name = activity.getClass().getCanonicalName() +292 "/0x" + System.identityHashCode(activity);293 } else {294 name += "(" + activity.getClass().getCanonicalName() + ")";295 }296 addWindow(activity.getWindow().getDecorView(), name);297 }298 299 /**300 * Invoke this method to unregister a view hierarchy.301 *302 * @param activity The activity whose view hierarchy/window to unregister303 * @see #addWindow(android.app.Activity)304 * @see #removeWindow(android.view.View)305 */306 public void removeWindow(Activity activity) {307 removeWindow(activity.getWindow().getDecorView());308 }309 310 /**311 * Invoke this method to register a new view hierarchy.312 *313 * @param view A view that belongs to the view hierarchy/window to register314 * @name name The name of the view hierarchy/window to register315 * @see #removeWindow(android.view.View)316 */317 public void addWindow(View view, String name) {318 mWindowsLock.writeLock().lock();319 try {320 mWindows.put(view.getRootView(), name);321 } finally {322 mWindowsLock.writeLock().unlock();323 }324 fireWindowsChangedEvent();325 }326 327 /**328 * Invoke this method to unregister a view hierarchy.329 *330 * @param view A view that belongs to the view hierarchy/window to unregister331 * @see #addWindow(android.view.View, String)332 */333 public void removeWindow(View view) {334 mWindowsLock.writeLock().lock();335 try {336 mWindows.remove(view.getRootView());337 } finally {338 mWindowsLock.writeLock().unlock();339 }340 fireWindowsChangedEvent();341 }342 343 /**344 * Invoke this method to change the currently focused window.345 *346 * @param activity The activity whose view hierarchy/window hasfocus,347 * or null to remove focus348 */349 public void setFocusedWindow(Activity activity) {350 setFocusedWindow(activity.getWindow().getDecorView());351 }352 353 /**354 * Invoke this method to change the currently focused window.355 *356 * @param view A view that belongs to the view hierarchy/window that has focus,357 * or null to remove focus358 */359 public void setFocusedWindow(View view) {360 mFocusLock.writeLock().lock();361 try {362 mFocusedWindow = view == null ? null : view.getRootView();363 } finally {364 mFocusLock.writeLock().unlock();365 }366 fireFocusChangedEvent();367 }368 369 /**370 * Main server loop.371 */372 public void run() {373 try {374 InetAddress address = InetAddress.getLocalHost();375 mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, address);376 } catch (Exception e) {377 Log.w(LOG_TAG, "Starting ServerSocket error: ", e);378 }379 380 while (mServer != null && Thread.currentThread() == mThread) {381 // Any uncaught exception will crash the system process382 try {383 Socket client = mServer.accept();384 if (mThreadPool != null) {385 mThreadPool.submit(new ViewServerWorker(client));386 } else {387 try {388 client.close();389 } catch (IOException e) {390 e.printStackTrace();391 }392 }393 } catch (Exception e) {394 Log.w(LOG_TAG, "Connection error: ", e);395 }396 }397 }398 399 private static boolean writeValue(Socket client, String value) {400 boolean result;401 BufferedWriter out = null;402 try {403 OutputStream clientStream = client.getOutputStream();404 out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);405 out.write(value);406 out.write("\n");407 out.flush();408 result = true;409 } catch (Exception e) {410 result = false;411 } finally {412 if (out != null) {413 try {414 out.close();415 } catch (IOException e) {416 result = false;417 }418 }419 }420 return result;421 }422 423 private void fireWindowsChangedEvent() {424 for (WindowListener listener : mListeners) {425 listener.windowsChanged();426 }427 }428 429 private void fireFocusChangedEvent() {430 for (WindowListener listener : mListeners) {431 listener.focusChanged();432 }433 }434 435 private void addWindowListener(WindowListener listener) {436 if (!mListeners.contains(listener)) {437 mListeners.add(listener);438 }439 }440 441 private void removeWindowListener(WindowListener listener) {442 mListeners.remove(listener);443 }444 445 private interface WindowListener {446 void windowsChanged();447 448 void focusChanged();449 }450 451 452 private class ViewServerWorker implements Runnable, WindowListener {453 private Socket mClient;454 private boolean mNeedWindowListUpdate;455 private boolean mNeedFocusedWindowUpdate;456 457 private final Object[] mLock = new Object[0];458 459 public ViewServerWorker(Socket client) {460 mClient = client;461 mNeedWindowListUpdate = false;462 mNeedFocusedWindowUpdate = false;463 }464 465 public void run() {466 BufferedReader in = null;467 try {468 in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);469 470 final String request = in.readLine();471 472 Log.i("Command", "===>" + request);473 474 String command;475 String parameters;476 477 int index = request.indexOf(' ');478 if (index == -1) {479 command = request;480 parameters = "";481 } else {482 command = request.substring(0, index);483 parameters = request.substring(index + 1);484 }485 486 boolean result;487 if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) {488 result = writeValue(mClient, VALUE_PROTOCOL_VERSION);489 } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) {490 result = writeValue(mClient, VALUE_SERVER_VERSION);491 } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {492 result = listWindows(mClient);493 } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {494 result = getFocusedWindow(mClient);495 } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {496 result = windowManagerAutolistLoop();497 } else {498 result = windowCommand(mClient, command, parameters);499 }500 501 if (!result) {502 Log.w(LOG_TAG, "An error occurred with the command: " + command);503 }504 } catch (IOException e) {505 Log.w(LOG_TAG, "Connection error: ", e);506 } finally {507 if (in != null) {508 try {509 in.close();510 511 } catch (IOException e) {512 e.printStackTrace();513 }514 }515 if (mClient != null) {516 try {517 mClient.close();518 } catch (IOException e) {519 e.printStackTrace();520 }521 }522 }523 }524 525 private boolean windowCommand(Socket client, String command, String parameters) {526 boolean success = true;527 BufferedWriter out = null;528 529 try {530 // Find the hash code of the window531 int index = parameters.indexOf(' ');532 if (index == -1) {533 index = parameters.length();534 }535 final String code = parameters.substring(0, index);536 int hashCode = (int) Long.parseLong(code, 16);537 538 // Extract the command's parameter after the window description539 if (index < parameters.length()) {540 parameters = parameters.substring(index + 1);541 } else {542 parameters = "";543 }544 545 final View window = findWindow(hashCode);546 if (window == null) {547 return false;548 }549 550 // call stuff551 final Method dispatch = ViewDebug.class.getDeclaredMethod("dispatchCommand",552 View.class, String.class, String.class, OutputStream.class);553 dispatch.setAccessible(true);554 dispatch.invoke(null, window, command, parameters,555 new UncloseableOutputStream(client.getOutputStream()));556 557 if (!client.isOutputShutdown()) {558 out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));559 out.write("DONE\n");560 out.flush();561 }562 563 } catch (Exception e) {564 Log.w(LOG_TAG, "Could not send command " + command +565 " with parameters " + parameters, e);566 success = false;567 } finally {568 if (out != null) {569 try {570 out.close();571 } catch (IOException e) {572 success = false;573 }574 }575 }576 577 return success;578 }579 580 private View findWindow(int hashCode) {581 if (hashCode == -1) {582 View window = null;583 mWindowsLock.readLock().lock();584 try {585 window = mFocusedWindow;586 } finally {587 mWindowsLock.readLock().unlock();588 }589 return window;590 }591 592 593 mWindowsLock.readLock().lock();594 try {595 for (Entry
使用方法如下:
public class MyActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set content view, etc. ViewServer.get(this).addWindow(this); } public void onDestroy() { super.onDestroy(); ViewServer.get(this).removeWindow(this); } public void onResume() { super.onResume(); ViewServer.get(this).setFocusedWindow(this); } }
使用时要注意:app要添加INTERNET权限,并且android:debugable要为true,eclipse或者studio直接run到手机都是debugable的,所以这点不用担心。
好了,祝大家春节快乐!
转载于:https://www.cnblogs.com/coding-way/p/4294225.html
更多相关文章
- android input子系统之-常用命令及技巧
- 用cmd 命令更改Android(安卓)的默认虚拟机的地址的方法
- 20.Android(安卓)设备工具DeviceUtil
- OpenGL ES基础篇
- android抓取网络通讯包
- Android蓝牙栈bluez使用方法
- GBD调试谷歌拼音输入法准备工作
- android安装和卸载的adb命令
- Android(安卓)adb 环境变量配置的坑 adb 不是内部或外部命令