[原创]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完整的代码贴上:
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package com.android.server.wm; import android.util.Slog; import java.net.ServerSocket;
import java.net.Socket;
import java.net.InetAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter; /**
* The ViewServer is local socket server that can be used to communicate with the
* views of the opened windows. Communication with the views is ensured by the
* {@link com.android.server.wm.WindowManagerService} and is a cross-process operation.
*
* {@hide}
*/
class ViewServer implements Runnable {
/**
* The default port used to start view servers.
*/
public static final int VIEW_SERVER_DEFAULT_PORT = 4939; private static final int VIEW_SERVER_MAX_CONNECTIONS = 10; // Debug facility
private static final String LOG_TAG = "ViewServer"; private static final String VALUE_PROTOCOL_VERSION = "4";
private static final String VALUE_SERVER_VERSION = "4"; // Protocol commands
// Returns the protocol version
private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL";
// Returns the server version
private static final String COMMAND_SERVER_VERSION = "SERVER";
// Lists all of the available windows in the system
private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST";
// Keeps a connection open and notifies when the list of windows changes
private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST";
// Returns the focused window
private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS"; private ServerSocket mServer;
private Thread mThread; private final WindowManagerService mWindowManager;
private final int mPort; private ExecutorService mThreadPool; /**
* Creates a new ViewServer associated with the specified window manager on the
* specified local port. The server is not started by default.
*
* @param windowManager The window manager used to communicate with the views.
* @param port The port for the server to listen to.
*
* @see #start()
*/
ViewServer(WindowManagerService windowManager, int port) {
mWindowManager = windowManager;
mPort = port;
} /**
* Starts the server.
*
* @return True if the server was successfully created, or false if it already exists.
* @throws IOException If the server cannot be created.
*
* @see #stop()
* @see #isRunning()
* @see WindowManagerService#startViewServer(int)
*/
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;
} /**
* Stops the server.
*
* @return True if the server was stopped, false if an error occured or if the
* server wasn't started.
*
* @see #start()
* @see #isRunning()
* @see WindowManagerService#stopViewServer()
*/
boolean stop() {
if (mThread != null) { mThread.interrupt();
if (mThreadPool != null) {
try {
mThreadPool.shutdownNow();
} catch (SecurityException e) {
Slog.w(LOG_TAG, "Could not stop all view server threads");
}
}
mThreadPool = null;
mThread = null;
try {
mServer.close();
mServer = null;
return true;
} catch (IOException e) {
Slog.w(LOG_TAG, "Could not close the view server");
}
}
return false;
} /**
* Indicates whether the server is currently running.
*
* @return True if the server is running, false otherwise.
*
* @see #start()
* @see #stop()
* @see WindowManagerService#isViewServerRunning()
*/
boolean isRunning() {
return mThread != null && mThread.isAlive();
} /**
* Main server loop.
*/
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);
}
}
} private static boolean writeValue(Socket client, String value) {
boolean result;
BufferedWriter out = null;
try {
OutputStream clientStream = client.getOutputStream();
out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
out.write(value);
out.write("\n");
out.flush();
result = true;
} catch (Exception e) {
result = false;
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
result = false;
}
}
}
return result;
} 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;
}
}
}
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了。
老外就是用的这个方法。所以我们就不用重复造轮子了。
接下来看看老外的解决方法:
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package com.server; import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewDebug; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock; /**
* <p>This class can be used to enable the use of HierarchyViewer inside an
* application. HierarchyViewer is an Android SDK tool that can be used
* to inspect and debug the user interface of running applications. For
* security reasons, HierarchyViewer does not work on production builds
* (for instance phones bought in store.) By using this class, you can
* make HierarchyViewer work on any device. You must be very careful
* however to only enable HierarchyViewer when debugging your
* application.</p>
* <p/>
* <p>To use this view server, your application must require the INTERNET
* permission.</p>
* <p/>
* <p>The recommended way to use this API is to register activities when
* they are created, and to unregister them when they get destroyed:</p>
* <p/>
* <pre>
* 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);
* }
* }
* </pre>
* <p/>
* <p>
* In a similar fashion, you can use this API with an InputMethodService:
* </p>
* <p/>
* <pre>
* public class MyInputMethodService extends InputMethodService {
* public void onCreate() {
* super.onCreate();
* View decorView = getWindow().getWindow().getDecorView();
* String name = "MyInputMethodService";
* ViewServer.get(this).addWindow(decorView, name);
* }
*
* public void onDestroy() {
* super.onDestroy();
* View decorView = getWindow().getWindow().getDecorView();
* ViewServer.get(this).removeWindow(decorView);
* }
*
* public void onStartInput(EditorInfo attribute, boolean restarting) {
* super.onStartInput(attribute, restarting);
* View decorView = getWindow().getWindow().getDecorView();
* ViewServer.get(this).setFocusedWindow(decorView);
* }
* }
* </pre>
*/
public class ViewServer implements Runnable {
/**
* The default port used to start view servers.
*/
private static final int VIEW_SERVER_DEFAULT_PORT = 4939;
private static final int VIEW_SERVER_MAX_CONNECTIONS = 10;
private static final String BUILD_TYPE_USER = "user"; // Debug facility
private static final String LOG_TAG = "ViewServer"; private static final String VALUE_PROTOCOL_VERSION = "4";
private static final String VALUE_SERVER_VERSION = "4"; // Protocol commands
// Returns the protocol version
private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL";
// Returns the server version
private static final String COMMAND_SERVER_VERSION = "SERVER";
// Lists all of the available windows in the system
private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST";
// Keeps a connection open and notifies when the list of windows changes
private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST";
// Returns the focused window
private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS"; private ServerSocket mServer;
private final int mPort; private Thread mThread;
private ExecutorService mThreadPool; private final List<WindowListener> mListeners =
new CopyOnWriteArrayList<WindowListener>(); private final HashMap<View, String> mWindows = new HashMap<View, String>();
private final ReentrantReadWriteLock mWindowsLock = new ReentrantReadWriteLock(); private View mFocusedWindow;
private final ReentrantReadWriteLock mFocusLock = new ReentrantReadWriteLock(); private static ViewServer sServer; /**
* Returns a unique instance of the ViewServer. This method should only be
* called from the main thread of your application. The server will have
* the same lifetime as your process.
* <p/>
* If your application does not have the <code>android:debuggable</code>
* flag set in its manifest, the server returned by this method will
* be a dummy object that does not do anything. This allows you to use
* the same code in debug and release versions of your application.
*
* @param context A Context used to check whether the application is
* debuggable, this can be the application context
*/
public static ViewServer get(Context context) {
ApplicationInfo info = context.getApplicationInfo();
if (BUILD_TYPE_USER.equals(Build.TYPE) &&
(info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
if (sServer == null) {
sServer = new ViewServer(ViewServer.VIEW_SERVER_DEFAULT_PORT);
} if (!sServer.isRunning()) {
try {
sServer.start();
} catch (IOException e) {
Log.d(LOG_TAG, "Error:", e);
}
}
} else {
sServer = new NoopViewServer();
} return sServer;
} private ViewServer() {
mPort = -1;
} /**
* Creates a new ViewServer associated with the specified window manager on the
* specified local port. The server is not started by default.
*
* @param port The port for the server to listen to.
* @see #start()
*/
private ViewServer(int port) {
mPort = port;
} /**
* Starts the server.
*
* @return True if the server was successfully created, or false if it already exists.
* @throws java.io.IOException If the server cannot be created.
* @see #stop()
* @see #isRunning()
*/
public boolean start() throws IOException {
if (mThread != null) {
return false;
} mThread = new Thread(this, "Local View Server [port=" + mPort + "]");
mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS);
mThread.start(); return true;
} /**
* Stops the server.
*
* @return True if the server was stopped, false if an error occurred or if the
* server wasn't started.
* @see #start()
* @see #isRunning()
*/
public boolean stop() {
if (mThread != null) {
mThread.interrupt();
if (mThreadPool != null) {
try {
mThreadPool.shutdownNow();
} catch (SecurityException e) {
Log.w(LOG_TAG, "Could not stop all view server threads");
}
} mThreadPool = null;
mThread = null; try {
mServer.close();
mServer = null;
return true;
} catch (IOException e) {
Log.w(LOG_TAG, "Could not close the view server");
}
} mWindowsLock.writeLock().lock();
try {
mWindows.clear();
} finally {
mWindowsLock.writeLock().unlock();
} mFocusLock.writeLock().lock();
try {
mFocusedWindow = null;
} finally {
mFocusLock.writeLock().unlock();
} return false;
} /**
* Indicates whether the server is currently running.
*
* @return True if the server is running, false otherwise.
* @see #start()
* @see #stop()
*/
public boolean isRunning() {
return mThread != null && mThread.isAlive();
} /**
* Invoke this method to register a new view hierarchy.
*
* @param activity The activity whose view hierarchy/window to register
* @see #addWindow(android.view.View, String)
* @see #removeWindow(android.app.Activity)
*/
public void addWindow(Activity activity) {
String name = activity.getTitle().toString();
if (TextUtils.isEmpty(name)) {
name = activity.getClass().getCanonicalName() +
"/0x" + System.identityHashCode(activity);
} else {
name += "(" + activity.getClass().getCanonicalName() + ")";
}
addWindow(activity.getWindow().getDecorView(), name);
} /**
* Invoke this method to unregister a view hierarchy.
*
* @param activity The activity whose view hierarchy/window to unregister
* @see #addWindow(android.app.Activity)
* @see #removeWindow(android.view.View)
*/
public void removeWindow(Activity activity) {
removeWindow(activity.getWindow().getDecorView());
} /**
* Invoke this method to register a new view hierarchy.
*
* @param view A view that belongs to the view hierarchy/window to register
* @name name The name of the view hierarchy/window to register
* @see #removeWindow(android.view.View)
*/
public void addWindow(View view, String name) {
mWindowsLock.writeLock().lock();
try {
mWindows.put(view.getRootView(), name);
} finally {
mWindowsLock.writeLock().unlock();
}
fireWindowsChangedEvent();
} /**
* Invoke this method to unregister a view hierarchy.
*
* @param view A view that belongs to the view hierarchy/window to unregister
* @see #addWindow(android.view.View, String)
*/
public void removeWindow(View view) {
mWindowsLock.writeLock().lock();
try {
mWindows.remove(view.getRootView());
} finally {
mWindowsLock.writeLock().unlock();
}
fireWindowsChangedEvent();
} /**
* Invoke this method to change the currently focused window.
*
* @param activity The activity whose view hierarchy/window hasfocus,
* or null to remove focus
*/
public void setFocusedWindow(Activity activity) {
setFocusedWindow(activity.getWindow().getDecorView());
} /**
* Invoke this method to change the currently focused window.
*
* @param view A view that belongs to the view hierarchy/window that has focus,
* or null to remove focus
*/
public void setFocusedWindow(View view) {
mFocusLock.writeLock().lock();
try {
mFocusedWindow = view == null ? null : view.getRootView();
} finally {
mFocusLock.writeLock().unlock();
}
fireFocusChangedEvent();
} /**
* Main server loop.
*/
public void run() {
try {
InetAddress address = InetAddress.getLocalHost();
mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, address);
} catch (Exception e) {
Log.w(LOG_TAG, "Starting ServerSocket error: ", e);
} while (mServer != null && 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) {
Log.w(LOG_TAG, "Connection error: ", e);
}
}
} private static boolean writeValue(Socket client, String value) {
boolean result;
BufferedWriter out = null;
try {
OutputStream clientStream = client.getOutputStream();
out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
out.write(value);
out.write("\n");
out.flush();
result = true;
} catch (Exception e) {
result = false;
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
result = false;
}
}
}
return result;
} private void fireWindowsChangedEvent() {
for (WindowListener listener : mListeners) {
listener.windowsChanged();
}
} private void fireFocusChangedEvent() {
for (WindowListener listener : mListeners) {
listener.focusChanged();
}
} private void addWindowListener(WindowListener listener) {
if (!mListeners.contains(listener)) {
mListeners.add(listener);
}
} private void removeWindowListener(WindowListener listener) {
mListeners.remove(listener);
} private interface WindowListener {
void windowsChanged(); void focusChanged();
} private class ViewServerWorker implements Runnable, WindowListener {
private Socket mClient;
private boolean mNeedWindowListUpdate;
private boolean mNeedFocusedWindowUpdate; private final Object[] mLock = new Object[0]; 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(); Log.i("Command", "===>" + request); 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 = listWindows(mClient);
} else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {
result = getFocusedWindow(mClient);
} else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {
result = windowManagerAutolistLoop();
} else {
result = windowCommand(mClient, command, parameters);
} if (!result) {
Log.w(LOG_TAG, "An error occurred with the command: " + command);
}
} catch (IOException e) {
Log.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();
}
}
}
} private boolean windowCommand(Socket client, String command, String parameters) {
boolean success = true;
BufferedWriter out = null; try {
// Find the hash code of the window
int index = parameters.indexOf(' ');
if (index == -1) {
index = parameters.length();
}
final String code = parameters.substring(0, index);
int hashCode = (int) Long.parseLong(code, 16); // Extract the command's parameter after the window description
if (index < parameters.length()) {
parameters = parameters.substring(index + 1);
} else {
parameters = "";
} final View window = findWindow(hashCode);
if (window == null) {
return false;
} // call stuff
final Method dispatch = ViewDebug.class.getDeclaredMethod("dispatchCommand",
View.class, String.class, String.class, OutputStream.class);
dispatch.setAccessible(true);
dispatch.invoke(null, window, command, parameters,
new UncloseableOutputStream(client.getOutputStream())); if (!client.isOutputShutdown()) {
out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
out.write("DONE\n");
out.flush();
} } catch (Exception e) {
Log.w(LOG_TAG, "Could not send command " + command +
" with parameters " + parameters, e);
success = false;
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
success = false;
}
}
} return success;
} private View findWindow(int hashCode) {
if (hashCode == -1) {
View window = null;
mWindowsLock.readLock().lock();
try {
window = mFocusedWindow;
} finally {
mWindowsLock.readLock().unlock();
}
return window;
} mWindowsLock.readLock().lock();
try {
for (Entry<View, String> entry : mWindows.entrySet()) {
if (System.identityHashCode(entry.getKey()) == hashCode) {
return entry.getKey();
}
}
} finally {
mWindowsLock.readLock().unlock();
} return null;
} private boolean listWindows(Socket client) {
boolean result = true;
BufferedWriter out = null; try {
mWindowsLock.readLock().lock(); OutputStream clientStream = client.getOutputStream();
out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024); for (Entry<View, String> entry : mWindows.entrySet()) {
out.write(Integer.toHexString(System.identityHashCode(entry.getKey())));
out.write(' ');
out.append(entry.getValue());
out.write('\n');
} out.write("DONE.\n");
out.flush();
} catch (Exception e) {
result = false;
} finally {
mWindowsLock.readLock().unlock(); if (out != null) {
try {
out.close();
} catch (IOException e) {
result = false;
}
}
} return result;
} private boolean getFocusedWindow(Socket client) {
boolean result = true;
String focusName = null; BufferedWriter out = null;
try {
OutputStream clientStream = client.getOutputStream();
out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024); View focusedWindow = null; mFocusLock.readLock().lock();
try {
focusedWindow = mFocusedWindow;
} finally {
mFocusLock.readLock().unlock();
} if (focusedWindow != null) {
mWindowsLock.readLock().lock();
try {
focusName = mWindows.get(mFocusedWindow);
} finally {
mWindowsLock.readLock().unlock();
} out.write(Integer.toHexString(System.identityHashCode(focusedWindow)));
out.write(' ');
out.append(focusName);
}
out.write('\n');
out.flush();
} catch (Exception e) {
result = false;
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
result = false;
}
}
} return result;
} public void windowsChanged() {
synchronized (mLock) {
mNeedWindowListUpdate = true;
mLock.notifyAll();
}
} public void focusChanged() {
synchronized (mLock) {
mNeedFocusedWindowUpdate = true;
mLock.notifyAll();
}
} private boolean windowManagerAutolistLoop() {
addWindowListener(this);
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));
while (!Thread.interrupted()) {
boolean needWindowListUpdate = false;
boolean needFocusedWindowUpdate = false;
synchronized (mLock) {
while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) {
mLock.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("FOCUS UPDATE\n");
out.flush();
}
}
} catch (Exception e) {
Log.w(LOG_TAG, "Connection error: ", e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
// Ignore
}
}
removeWindowListener(this);
}
return true;
}
} private static class UncloseableOutputStream extends OutputStream {
private final OutputStream mStream; UncloseableOutputStream(OutputStream stream) {
mStream = stream;
} public void close() throws IOException {
// Don't close the stream
} public boolean equals(Object o) {
return mStream.equals(o);
} public void flush() throws IOException {
mStream.flush();
} public int hashCode() {
return mStream.hashCode();
} public String toString() {
return mStream.toString();
} public void write(byte[] buffer, int offset, int count)
throws IOException {
mStream.write(buffer, offset, count);
} public void write(byte[] buffer) throws IOException {
mStream.write(buffer);
} public void write(int oneByte) throws IOException {
mStream.write(oneByte);
}
} /**
* 一个空的ViewServer类
*/
private static class NoopViewServer extends ViewServer {
private NoopViewServer() {
} @Override
public boolean start() throws IOException {
return false;
} @Override
public boolean stop() {
return false;
} @Override
public boolean isRunning() {
return false;
} @Override
public void addWindow(Activity activity) {
} @Override
public void removeWindow(Activity activity) {
} @Override
public void addWindow(View view, String name) {
} @Override
public void removeWindow(View view) {
} @Override
public void setFocusedWindow(Activity activity) {
} @Override
public void setFocusedWindow(View view) {
} @Override
public void run() {
}
}
}
解决问题的类
使用方法如下:
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的,所以这点不用担心。
好了,祝大家春节快乐!
[原创]HierarchyView的实现原理和Android设备无法使用HierarchyView的解决方法的更多相关文章
- HierarchyView的实现原理和Android设备无法使用HierarchyView的解决方法
声明:由于本人一直用eng版的真机调试,所以此方法没有用过,记录在这里,有机会验证 ---------------------------------------------------------- ...
- Android SDK下载失败的解决方法
Android SDK下载失败的解决方法 图1 在下载过程中,Android SDK Manager Log中出现下面出错信息: Preparing toinstall archives Downlo ...
- android studio 更新 Gradle错误解决方法(Gradle sync failed)
android studio 更新 Gradle错误解决方法 Android Studio每次更新版本都会更新Gradle这个插件,但由于长城的问题每次更新都是失败,又是停止在Refreshing ...
- Android webview 写入cookie的解决方法以及一些属性设置
原文地址:https://www.2cto.com/kf/201703/616868.html Android webview 写入cookie的解决方法以及一些属性设置,webview怎么设置写入C ...
- 【转】ubuntu连接android设备(附最简单方法)
原文网址:http://blog.csdn.net/maosidiaoxian/article/details/22661725 在ubuntu下连接android设备,虽然不用像windows那样安 ...
- ubuntu连接android设备(附最简单方法)
在ubuntu下连接android设备,虽然不用像windows那样安装驱动,然而却会遇见一个错误:输入adb shell,会提示insufficient permissions for device ...
- 通过扫码打开IOS的App Store下载APP(Android版暂时没找到解决方法)
项目需求:扫码根据不同平台下载不同版本的APP.主要是ios和Android. 网上找了很多,前面判断平台的代码很容易找到,但是后面的就有些坑了.有的人的是根本跑不通.有的是代码补全. 下面是 微信扫 ...
- 【原创】安装LoadRunner12.53 版本时出现Critical error的解决方法
步骤: 1.在官网上下载LoadRunner12.53正版,只不过要注册,然后官网会给个序列号. 2.安装成功之后,快捷键已创建,打开Virtual User Generator时,提示如下错误: 此 ...
- Android无法连接adb的解决方法
今天在折腾乐蛙时发现无法链接ADB了,但是手机却显示USB调试模式! 然后想起了大蛋曾经告诉我CM的解决方法,于是你懂得,俺差点就把菊花给卖了呢(/Д`)~゚。 adb shell rm -r /da ...
随机推荐
- Html5前端框架
BootCss bootcss.com amazeui http://amazeui.org/
- LeadTools Android 入门教学——运行第一个Android Demo
LeadTools 有很多Windows平台下的Demo,非常全面,但是目前开发手机应用的趋势也越来越明显,LeadTools也给大家提供了10个Android的Demo,这篇文章将会教你如何运行第一 ...
- muduo库的简单使用-echo服务的编写
muduo库的简单使用 muduo是一个基于事件驱动的非阻塞网络库,采用C++和Boost库编写. 它的使用方法很简单,参考这篇文章:TCP网络编程本质论 里面有这么几句: 我认为,TCP 网络编程最 ...
- 整站HTTPS后的跨域请求 CORS是否还有效?
| 导语 手Q马上就要全量https了,很多业务都有跨域ajax请求的需求,原来使用的CORS头在HTTPS环境中还继续能用吗?我搜遍了谷歌.百度,都没看到有明确的答案,那么就自己来尝试一下吧. 关 ...
- Alfred 使用简介
1.安装(不说了去 Google 吧) 2.基础快捷键:option+space 3.打开应用程序:Alfred 几乎是一切程序的入口,你再也不需要找妈妈要开始菜单了.用快捷键呼出Alfred,输入任 ...
- 什么是automatic variable?
看代码符号$?搞不清楚是什么? 看代码. $share = Get-WmiObject -Class Win32_Share -ComputerName $Server.name -Credent ...
- RMAN duplciate 准备时,需要检查的target数据库参数内容
SQL> show parameter audit_file_dest; NAME TYPE VALUE------ ...
- Revit如何设置尺寸标注的箭头样式
在尺寸标注类型属性中,有一名称为"记号标记"的属性,该属性控制线性标注的箭头样式,如图所示,可以从下"记号标记"下拉列表中选择需要的样式进行设置,但是有时候该下 ...
- samba权限之easy举例说明--原创
实验环境RHEL5.0,samba3.023rc-2 一.何为browsealbe=no? 如图lingdao目录的权限为777 如图ling目录的共享设置和用户的ID和组 当用户lingdao_01 ...
- ELK——安装 logstash 2.2.0、elasticsearch 2.2.0 和 Kibana 3.0
本文内容 Elasticsearch logstash Kibana 参考资料 本文介绍安装 logstash 2.2.0 和 elasticsearch 2.2.0,操作系统环境版本是 CentOS ...