ViewServer源码分析

ViewServer源码分析

正常情况下,基于安全考虑,在手机没有root时,HierarchyViewer工具无法正常工作。通过在应用中接入ViewServer类,可以实现用HierarchyViewer工具对查看和调试应用的UI。

如何接入ViewServer类

需要申明权限:

参照如下方式,在应用onCreate时注册,在onDestroy时取消注册(对于Activity和Service稍有不同)

public class MyActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
        //在onCreate时,进行注册
        ViewServer.get(this).addWindow(this);
    }

    public void onDestroy() {
        super.onDestroy();
        //在onDestroy时,取消注册
        ViewServer.get(this).removeWindow(this);
    }

    public void onResume() {
        super.onResume();
        ViewServer.get(this).setFocusedWindow(this);
    }
}

对于InputMethodService,可以使用如下方式,和Activity稍有不同

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);
        //这里第一个getWindwo返回的是Dialog对象,第二个getWindow方法返回的是Window对象
        View decorView = getWindow().getWindow().getDecorView();
        ViewServer.get(this).setFocusedWindow(decorView);
    }
}

针对Activity形式window和Service形式window,ViewServer提供了重载方法addWindow(Activity)和addWindow(Activity, String)
addWindow(Activity)方法最终还是会调用到addWindow(Activity, String)方法。

源码分析

  1. ViewServer是一个Runnable子类,并且ViewServer是singleton类型的,

    public class ViewServer implements Runnable
    
  2. 先分析ViewServer是如何初始化的

    private static ViewServer sServer;
    
    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;
    }
    
    1. ROM编译类型在prop文件的ro.build.type中获得,Build.TYPE=getString(“ro.build.type”),BUILD_TYPE_USER表明手机ROM的编译形式是“user”形式的;
    2. info.flags & ApplicationInfo.FLAG_DEBUGGABLE表明apk是debug形式的。ApplicationInfo.FLAG_DEBUGGABLE属性只能在apk编译时设置而不能在AndroidManifest.xml的节点中设置属性:android:deubuggable=true。

    ViewServer的实例创建成功后,接下来是调用sServer.start()启动当前ViewServer线程

    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;
    }
    

    由于ViewServer继承Runnable的,ViewServer类必须覆写Runnable的run方法,当执行mThread.start()时,会执行到ViewServer的run方法中:

    public void run() {
        try {
            mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost());
        } 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);
            }
        }
    }
    

    run()方法中首先在指定端口上创建ServerSocket对象mServer,该ServerSocket就是用来和pc上的HierarchyViewer工具进行对象。

    mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost());
    

    ServerSocket创建成功后,开启监听,等待HierarchyViewer的socket链接,该方法是阻塞式的,

    Socket client = mServer.accept();
    

    当收到HierarchyViewer的socket链接后,阻塞结束,以该socket对象为参数通过线程池的方式启动ViewServerWorker工作线程。

    由以上分析可知,ViewSever在初始化阶段工作主要是:启动自己,然后创建ServerSocket对象,并监听指定端口,等待HierarchyViewer的

    socket链接,当收到socket连接时,启动工作线程ViewServerWorker。下一节继续分析ViewServerWorker。

  3. 下面分析ViewServerWorker。

    mThreadPool.submit(new ViewServerWorker(client));
    

    在ViewServer中的ServerSocket收到HierarchyViewer的socket连接后,会初始化并启动ViewServerWorker线程,

    ViewServerWorker的构造方法很简单,只是进行了下变量的初始化

    public ViewServerWorker(Socket client) {
        mClient = client;
        mNeedWindowListUpdate = false;
        mNeedFocusedWindowUpdate = false;
    }
    

    每一个mClient代表ViewServer和HierarchyViewer之间的一个链接:

    当ViewServerWorker被submit后,会执行ViewServerWorker的run方法:

    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 = 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 {
            .........
        }
    }
    

    由run方法的实现可知,该方法是从从socket中读取命令,执行对象的处理,在将结果写入socket中。

    PROCOTOL和SERVER命令

    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);
    }
    

    根据HierarchyViewer client端的”Command”要求,返回对应的protocol version和server version。

    LIST命令

    else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {
                result = listWindows(mClient);
    }
    

    当HierarchyViewer发送“LSIT”命令时,ViewServer端回对应收到该命令:

    //send ‘LIST’ command
    out.write("LIST");
    out.newLine();
    out.flush();
    
    //receive response from viewserver
    String context="";
    String line;
    while ((line = in.readLine()) != null) {
                if ("DONE.".equalsIgnoreCase(line)) { //$NON-NLS-1$
                    break;
                }
                context+=line+"\r\n";
    }
    

    在打开HierarchyViewer时,会显示每个设备当前活动的Activity列表,如下图:


    图1

    44fd2c87 com.android.systemui.ImageWallpaper
    4618bb17 com.android.launcher/com.android.launcher2.Launcher
    453678b9 com.my/com.my.EntryOne
    450acdc0 Keyguard
    4572a2f0 TrackingView
    45124560 StatusBarExpanded
    44feeff0 StatusBar
    44f08650 RecentsPanel
    <br />
    表一
    

    上述显示结果即来自于ViewServer的listWindows方法。

    每一列前面的8位16进制hashCode来自于
    out.write(Integer.toHexString(System.identityHashCode(entry.getKey())));
    每一列后面的字符串来自于:
    out.append(entry.getValue());

    listWindows源码:该方法重点关注for循环中的代码即可。表一显示结果就是由该for循环得到。

    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;
    }
    

    GET_FOCUS命令

    else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {
                        result = getFocusedWindow(mClient);
    }
    

    分析getFocusedWindow源码:

    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
                focusedWindow = mFocusedWindow;
            } finally {
                mFocusLock.readLock().unlock();
            }
    
            if (focusedWindow != null) {
                mWindowsLock.readLock().lock();
                try {
                    //获得focustedWindow的名字
                    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 {
            ......
        }
    
        return result;
    }
    

    该方法作用是找出当前focus window然后用和“LIST”命令相同的格式将结果写会到socket中,供HierarchyViewer使用。
    这里的所谓focus window也就是当前正在和用户交互的activity。即图1中的com.my/com.my.EntryOne

    AUTOLIST命令

    该命令用于更新winddow(listWindows方法)和focus window(getFocusedWindow方法)

    else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {
        result = windowManagerAutolistLoop();
    }
    

    windowManagerAutolistLoop()源码:
    windowManagerAutolistLoop的作用需要结合windowsChanged和focusChanged方法一起看:

    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;
    }
    

    }

    看windowsChanged方法和focusChanged的执行桟

    onCreate -> addWindow(removeWindow) -> fireWindowsChangedEvent -> windowsChanged
    
    onResume -> setFocusedWindow-> fireFocusChangedEvent -> focusChanged
    

    当收到COMMAND_WINDOW_MANAGER_AUTOLIST命令时,执行windowManagerAutolistLoop方法,该方法显示执行mLock.wait()挂起,当有windowsChanged或者focusChanged时,继续执行,向HierarchyViewer客户端发送”LIST UPDATE\n”或者”FOCUS UPDATE\n”命令,这样HierarchyViewer那边会王socket中写入”LIST”和”FOCUS”命令,并收到ViewServer的返回结果,并进行显示。

    其他命令
    其他命令默认执行windowCommand方法
    windowCommand(Socket client, String command, String parameters) {

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值