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)方法。
源码分析
ViewServer是一个Runnable子类,并且ViewServer是singleton类型的,
public class ViewServer implements Runnable
先分析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; }
- ROM编译类型在prop文件的ro.build.type中获得,Build.TYPE=getString(“ro.build.type”),BUILD_TYPE_USER表明手机ROM的编译形式是“user”形式的;
- 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。
下面分析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列表,如下图:
图144fd2c87 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.EntryOneAUTOLIST命令
该命令用于更新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) {