HierarchyView的实现原理和Android设备无法使用HierarchyView的解决方法

声明:由于本人一直用eng版的真机调试,所以此方法没有用过,记录在这里,有机会验证

-------------------------------------------------------------------

转:http://www.cnblogs.com/coding-way/p/4294225.html

 

最近在看一个老外写的东西,发现里面有个类,使用这个类可以让任何设备使用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完整的代码贴上:

 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的,所以这点不用担心。

posted @ 2015-09-10 16:13  carlo-z  阅读(983)  评论(0编辑  收藏  举报