[原创]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  * 

This class can be used to enable the use of HierarchyViewer inside an 48 * application. HierarchyViewer is an Android SDK tool that can be used 49 * to inspect and debug the user interface of running applications. For 50 * security reasons, HierarchyViewer does not work on production builds 51 * (for instance phones bought in store.) By using this class, you can 52 * make HierarchyViewer work on any device. You must be very careful 53 * however to only enable HierarchyViewer when debugging your 54 * application.

55 *

56 *

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

58 *

59 *

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

61 *

62 *

 63  * public class MyActivity extends Activity { 64  *     public void onCreate(Bundle savedInstanceState) { 65  *         super.onCreate(savedInstanceState); 66  *         // Set content view, etc. 67  *         ViewServer.get(this).addWindow(this); 68  *     } 69  * 70  *     public void onDestroy() { 71  *         super.onDestroy(); 72  *         ViewServer.get(this).removeWindow(this); 73  *     } 74  * 75  *     public void onResume() { 76  *         super.onResume(); 77  *         ViewServer.get(this).setFocusedWindow(this); 78  *     } 79  * } 80  * 
81 *

82 *

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

85 *

86 *

 87  * public class MyInputMethodService extends InputMethodService { 88  *     public void onCreate() { 89  *         super.onCreate(); 90  *         View decorView = getWindow().getWindow().getDecorView(); 91  *         String name = "MyInputMethodService"; 92  *         ViewServer.get(this).addWindow(decorView, name); 93  *     } 94  * 95  *     public void onDestroy() { 96  *         super.onDestroy(); 97  *         View decorView = getWindow().getWindow().getDecorView(); 98  *         ViewServer.get(this).removeWindow(decorView); 99  *     }100  *101  *     public void onStartInput(EditorInfo attribute, boolean restarting) {102  *         super.onStartInput(attribute, restarting);103  *         View decorView = getWindow().getWindow().getDecorView();104  *         ViewServer.get(this).setFocusedWindow(decorView);105  *     }106  * }107  * 
108 */109 public class ViewServer implements Runnable {110 /**111 * The default port used to start view servers.112 */113 private static final int VIEW_SERVER_DEFAULT_PORT = 4939;114 private static final int VIEW_SERVER_MAX_CONNECTIONS = 10;115 private static final String BUILD_TYPE_USER = "user";116 117 // Debug 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 List mListeners =142 new CopyOnWriteArrayList();143 144 private final HashMap mWindows = new HashMap();145 private final ReentrantReadWriteLock mWindowsLock = new ReentrantReadWriteLock();146 147 private View mFocusedWindow;148 private final ReentrantReadWriteLock mFocusLock = new ReentrantReadWriteLock();149 150 private static ViewServer sServer;151 152 /**153 * Returns a unique instance of the ViewServer. This method should only be154 * called from the main thread of your application. The server will have155 * the same lifetime as your process.156 *

157 * If your application does not have the android:debuggable158 * 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 entry : mWindows.entrySet()) {596 if (System.identityHashCode(entry.getKey()) == hashCode) {597 return entry.getKey();598 }599 }600 } finally {601 mWindowsLock.readLock().unlock();602 }603 604 return null;605 }606 607 private boolean listWindows(Socket client) {608 boolean result = true;609 BufferedWriter out = null;610 611 try {612 mWindowsLock.readLock().lock();613 614 OutputStream clientStream = client.getOutputStream();615 out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);616 617 for (Entry entry : mWindows.entrySet()) {618 out.write(Integer.toHexString(System.identityHashCode(entry.getKey())));619 out.write(' ');620 out.append(entry.getValue());621 out.write('\n');622 }623 624 out.write("DONE.\n");625 out.flush();626 } catch (Exception e) {627 result = false;628 } finally {629 mWindowsLock.readLock().unlock();630 631 if (out != null) {632 try {633 out.close();634 } catch (IOException e) {635 result = false;636 }637 }638 }639 640 return result;641 }642 643 private boolean getFocusedWindow(Socket client) {644 boolean result = true;645 String focusName = null;646 647 BufferedWriter out = null;648 try {649 OutputStream clientStream = client.getOutputStream();650 out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);651 652 View focusedWindow = null;653 654 mFocusLock.readLock().lock();655 try {656 focusedWindow = mFocusedWindow;657 } finally {658 mFocusLock.readLock().unlock();659 }660 661 if (focusedWindow != null) {662 mWindowsLock.readLock().lock();663 try {664 focusName = mWindows.get(mFocusedWindow);665 } finally {666 mWindowsLock.readLock().unlock();667 }668 669 out.write(Integer.toHexString(System.identityHashCode(focusedWindow)));670 out.write(' ');671 out.append(focusName);672 }673 out.write('\n');674 out.flush();675 } catch (Exception e) {676 result = false;677 } finally {678 if (out != null) {679 try {680 out.close();681 } catch (IOException e) {682 result = false;683 }684 }685 }686 687 return result;688 }689 690 public void windowsChanged() {691 synchronized (mLock) {692 mNeedWindowListUpdate = true;693 mLock.notifyAll();694 }695 }696 697 public void focusChanged() {698 synchronized (mLock) {699 mNeedFocusedWindowUpdate = true;700 mLock.notifyAll();701 }702 }703 704 private boolean windowManagerAutolistLoop() {705 addWindowListener(this);706 BufferedWriter out = null;707 try {708 out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));709 while (!Thread.interrupted()) {710 boolean needWindowListUpdate = false;711 boolean needFocusedWindowUpdate = false;712 synchronized (mLock) {713 while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) {714 mLock.wait();715 }716 if (mNeedWindowListUpdate) {717 mNeedWindowListUpdate = false;718 needWindowListUpdate = true;719 }720 if (mNeedFocusedWindowUpdate) {721 mNeedFocusedWindowUpdate = false;722 needFocusedWindowUpdate = true;723 }724 }725 if (needWindowListUpdate) {726 out.write("LIST UPDATE\n");727 out.flush();728 }729 if (needFocusedWindowUpdate) {730 out.write("FOCUS UPDATE\n");731 out.flush();732 }733 }734 } catch (Exception e) {735 Log.w(LOG_TAG, "Connection error: ", e);736 } finally {737 if (out != null) {738 try {739 out.close();740 } catch (IOException e) {741 // Ignore742 }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 stream759 }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 @Override799 public boolean start() throws IOException {800 return false;801 }802 803 @Override804 public boolean stop() {805 return false;806 }807 808 @Override809 public boolean isRunning() {810 return false;811 }812 813 @Override814 public void addWindow(Activity activity) {815 }816 817 @Override818 public void removeWindow(Activity activity) {819 }820 821 @Override822 public void addWindow(View view, String name) {823 }824 825 @Override826 public void removeWindow(View view) {827 }828 829 @Override830 public void setFocusedWindow(Activity activity) {831 }832 833 @Override834 public void setFocusedWindow(View view) {835 }836 837 @Override838 public void run() {839 }840 }841 }

解决问题的类

 

使用方法如下:

public class MyActivity extends Activity {      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          // Set content view, etc.          ViewServer.get(this).addWindow(this);      }       public void onDestroy() {          super.onDestroy();          ViewServer.get(this).removeWindow(this);      }       public void onResume() {          super.onResume();          ViewServer.get(this).setFocusedWindow(this);      }  }

 

使用时要注意:app要添加INTERNET权限,并且android:debugable要为true,eclipse或者studio直接run到手机都是debugable的,所以这点不用担心。

好了,祝大家春节快乐!

 

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