小米手机怎样开启hierarchyviewer.bat

更多频道内容在这里查看
爱奇艺用户将能永久保存播放记录
过滤短视频
暂无长视频(电视剧、纪录片、动漫、综艺、电影)播放记录,
使用您的微博帐号登录,即刻尊享微博用户专属服务。
使用您的QQ帐号登录,即刻尊享QQ用户专属服务。
使用您的人人帐号登录,即刻尊享人人用户专属服务。
按住视频可进行拖动
&正在加载...
请选择打赏金额:
收藏成功,可进入
查看所有收藏列表
当前浏览器仅支持手动复制代码
视频地址:
flash地址:
html代码:
通用代码:
通用代码可同时支持电脑和移动设备的分享播放
用爱奇艺APP或微信扫一扫,在手机上继续观看
当前播放时间:
一键下载至手机
限爱奇艺安卓6.0以上版本
使用微信扫一扫,扫描左侧二维码,下载爱奇艺移动APP
其他安装方式:手机浏览器输入短链接http://71.am/udn
下载安装包到本机:
设备搜寻中...
请确保您要连接的设备(仅限安卓)登录了同一爱奇艺账号 且安装并开启不低于V6.0以上版本的爱奇艺客户端
连接失败!
请确保您要连接的设备(仅限安卓)登录了同一爱奇艺账号 且安装并开启不低于V6.0以上版本的爱奇艺客户端
部安卓(Android)设备,请点击进行选择
请您在手机端下载爱奇艺移动APP(仅支持安卓客户端)
使用微信扫一扫,下载爱奇艺移动APP
其他安装方式:手机浏览器输入短链接http://71.am/udn
下载安装包到本机:
爱奇艺云推送
请您在手机端登录爱奇艺移动APP(仅支持安卓客户端)
使用微信扫一扫,下载爱奇艺移动APP
180秒后更新
打开爱奇艺移动APP,点击“我的-扫一扫”,扫描左侧二维码进行登录
没有安装爱奇艺视频最新客户端?
30秒后自动关闭
android开发入门之HierarchyViewer">android开发入门之HierarchyViewer
播放量数据:快去看看谁在和你一起看视频吧~
您使用浏览器不支持直接复制的功能,建议您使用Ctrl+C或右键全选进行地址复制
安装爱奇艺视频客户端,
马上开始为您下载本片
5秒后自动消失
&li data-elem="tabtitle" data-seq="{{seq}}"& &a href="javascript:void(0);"& &span>{{start}}-{{end}}&/span& &/a& &/li&
&li data-downloadSelect-elem="item" data-downloadSelect-selected="false" data-downloadSelect-tvid="{{tvid}}"& &a href="javascript:void(0);"&{{pd}}&/a&
选择您要下载的《
色情低俗内容
血腥暴力内容
广告或欺诈内容
侵犯了我的权力
还可以输入
您使用浏览器不支持直接复制的功能,建议您使用Ctrl+C或右键全选进行地址复制Posts - 63,
Articles - 0,
Comments - 77
09:39 by 知平软件, ... 阅读,
HierarchyViewer是Android SDK包中一个非常好用的工具,你在 android-sdks/tools目录下可以找到它。通过HierarchyViewer,即使没有应用的源代码,我们也可以非常直观地浏览Activity中控件的层次结构图,以及每个控件的属性和截图,这对于测试人员编写自动化测试用例是极有帮助的。这个系列的文章,我们将通过阅读和解析HierarchyViewer的代码,来了解HierarchyViewer是如何工作的,也可以加深Android提供给开发者的各种接口的了解。本系列文章代码基于android4.0的源代码,还没有下载源代码的同学快去下载吧,旅程这就开始了。
本文首先并不直接从源代码阅读开始,而是demo和解释HierarchyViewer的主要工作原理,这可是作者从源代码中抽取的精华啊:)。看完本文,你就可以写一个自己简单的HierarchyViewer了。我们主要讲解如下几个部分:
1,如何连接ViewServer
2,如何获取活动的Activities
3,如何获取Activity的控件树
4,如何获取截图
如何连接ViewServer
ViewServer是Android通过4939端口提供的服务,HierarchyViewer主要是通过它来获取获取Activity信息的, HierarchyViewer主要做下面3件事情来连接ViewServer。这需要用到Adb,HierarchyViewer中是直接通过api来调用Adb的,而这里我们先使用命令行adb来实现同样的功能。
(1)Forword端口。就是把Android设备上的4939端口映射到PC的某端口上,这样,向PC的该端口号发包都会转发到Android设备的4939端口上。
首先,输入命令列出所有Android设备
adb devices
假设我们有多台设备连接在PC上,该命令的输出为:
List of devices attached
emulator-5554 device
emulator-5556 device
以设备emulator-5556为例,接下来我们把它的4939端口映射到PC的4939端口上:
adb -s emulator-5556 forward tcp:4939 tcp:4939
如果连接了多台Android设备,HierarchyViewer将把下一台Android设备的4939端口映射到PC的4940端口,以此类推。
(2)打开ViewServer服务。
首先,需要判断ViewServer是否打开:
adb -s emulator-5556 shell service call window 3
如果返回值是"Result: Parcel(00000 '........')",说明ViewServer没有打开,那么需要用下面的命令打开ViewServer:
adb -s emulator-5556 shell service call window 1 i32 4939
反之,关闭ViewServer的命令是:
adb -s emulator-5556 shell service call window 2 i32 4939
(3)连接ViewServer,既然ViewServer已经打开,那么下一步我们就需要连接它了。由于我们已经把设备emulator-端口映射为PC的4939端口上,所以我们需要连接的是127.0.0.1:4939。这需要写一些java代码:
import java.net.*;
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", );
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
} catch ( Exception e ) {
e.printStackTrace();
out和in用于发送命令和接受返回数据,需要注意的是,HierarchyViewer和ViewServer的通信采用短连接,所以每发送一次命令,需要重新建立一次连接,所以以上代码需要反复调用。
如何获取活动的Activity
在打开HierarchyViewer时,会显示每个设备当前活动的Activity列表,如下图:
这是怎么实现的呢? 这需要向ViewerServer发送"LIST"命令,看下面的代码:
//send &LIST& command
out.write("LIST");
out.newLine();
out.flush();
//receive response from viewserver
String context="";
while ((line = in.readLine()) != null) {
if ("DONE.".equalsIgnoreCase(line)) { //$NON-NLS-1$
context+=line+"\r\n";
我们可以获取到类似如下的列表
44fd1b78 com.android.internal.service.wallpaper.ImageWallpaper
4507aa28 com.android.launcher/com.android.launcher2.Launcher
com.tencent.mobileqq/com.tencent.mobileqq.activity.HomeActivity
450b8d18 com.tencent.mobileqq/com.tencent.mobileqq.activity.NotificationActivity
com.tencent.mobileqq/com.tencent.mobileqq.activity.NotificationActivity
com.tencent.mobileqq/com.tencent.mobileqq.activity.UpgradeActivity
450efef0 com.tencent.mobileqq/com.tencent.mobileqq.activity.UpgradeActivity
TrackingView
StatusBarExpanded
44fe0bb0 StatusBar
44f09250 Keyguard
注意,每行前面的16进制数字,那是一个hashcode,我们在进一步请求该Activity对应的控件树时要用到该hashcode。
如何获取Activity的控件树 选中一个Activity后,HierarchyViewer将获取它的控件并显示为层次图:
获取控件树信息的命令是DUMP,后面要接对应的Activity的hash code,如果使用ffffffff作为参数,那么就是取最前端的Activity。我们以com.android.launcher2.Launcher为例,它的hash code是4507aa28,看代码:
//out.write("DUMP ffffffff");
out.write("DUMP 4507aa28");
out.newLine();
out.flush();
String context1="";
while ((line = in.readLine()) != null) {
if ("DONE.".equalsIgnoreCase(line)) { //$NON-NLS-1$
context1+=line+"\r\n";
返回的控件树被保存文本context1中,一般文本的内容都非常大,这里我不把它全部打印出来,我们只取其中一行来看:
android.widget.FrameLayout@44edba90 mForeground=52,android.graphics.drawable.NinePatchDrawable@44edc1e0 mForegroundInPadding=5,false mForegroundPaddingBottom=1,0 mForegroundPaddingLeft=1,0 mForegroundPaddingRight=1,0 mForegroundPaddingTop=1,0 mMeasureAllChildren=5,false mForegroundGravity=2,55 getDescendantFocusability()=24,FOCUS_BEFORE_DESCENDANTS getPersistentDrawingCache()=9,SCROLLING isAlwaysDrawnWithCacheEnabled()=4,true isAnimationCacheEnabled()=4,true isChildrenDrawingOrderEnabled()=5,false isChildrenDrawnWithCacheEnabled()=5,false mMinWidth=1,0 mPaddingBottom=1,0 mPaddingLeft=1,0 mPaddingRight=1,0 mPaddingTop=2,38 mMinHeight=1,0 mMeasuredWidth=3,480 mMeasuredHeight=3,800 mLeft=1,0 mPrivateFlags_DRAWING_CACHE_INVALID=3,0x0 mPrivateFlags_DRAWN=4,0x20 mPrivateFlags=8, mID=10,id/content mRight=3,480 mScrollX=1,0 mScrollY=1,0 mTop=1,0 mBottom=3,800 mUserPaddingBottom=1,0 mUserPaddingRight=1,0 mViewFlags=9, getBaseline()=2,-1 getHeight()=3,800 layout_bottomMargin=1,0 layout_leftMargin=1,0 layout_rightMargin=1,0 layout_topMargin=1,0 layout_height=12,MATCH_PARENT layout_width=12,MATCH_PARENT getTag()=4,null getVisibility()=7,VISIBLE getWidth()=3,480 hasFocus()=5,false isClickable()=5,false isDrawingCacheEnabled()=5,false isEnabled()=4,true isFocusable()=5,false isFocusableInTouchMode()=5,false isFocused()=5,false isHapticFeedbackEnabled()=4,true isInTouchMode()=4,true isOpaque()=5,false isSelected()=5,false isSoundEffectsEnabled()=4,true willNotCacheDrawing()=5,false willNotDraw()=5,false
返回的文本中的每一行是Activity中的一个控件,里面包含了该控件的所有信息,HierarchyViewer正是通过解析这些信息并把它们显示在属性列表中的。需要注意每行的开始处都包含一个&控件类型@hash code&的字段,如android.widget.FrameLayout@44edba90 ,这个字段在获取该控件的屏幕截图时将被用到。
HierarchyViewer是怎么把这个文本解析成层次图的呢? 原来,每行前面都有若干空格的缩进,比如缩进5个空格表示该控件在第六层,那么往上找,最近的缩进4个空格的控件就是它的父控件。在该系列后面的文章中,我们将具体阅读HierarchyViewer是怎么解析该文本,又是如何显示层次图的。
如何获取截图
在层次图上选中控件时,HierarchyViewer会显示该控件的截图:
获取截图的命令是CAPTURE,需要传递Activity的hashcode和控件的hashcode作为参数,看下面的代码:
import org.eclipse.swt.graphics.I
import org.eclipse.swt.widgets.D
out.write("CAPTURE 4507aa28 android.widget.FrameLayout@44edba90");
out.newLine();
out.flush();
Image image = new Image(Display.getDefault(), socket.getInputStream());
到此为止,我相信大家已经对HierarchyViewer的主要实现机制有了基本的了解,接下来我们就要真正开始阅读HierarchyViewer的代码了,后面几章的内容大概是:
使用Eclipse阅读和调试HierarchyViewer
HierarchyViewer的后台代码导读
HierarchyViewer的前台代码导读
本文为公司原创作品,转载请注明出处。问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
打开虚拟机,然后从windows中选择显示的activity,然后显示一个树
但是点击其中的节点,本应该显示内容(比如截图或文字)但是显示的是一个空的气泡,这是肿么了?
我的姿势不对么?
console报的是这种错误:Unable to capture data for node android.widget.Button@XXXXXXXXX
另外据GOOGLE文档解释,红色的点是:Red: For this part of the render time, this View is the slowest one in the tree. For example, a red dot for the draw time means that this View takes the most time to draw of all the View objects in the tree.
也就是说,在第一个位置上(measure)只能有一个红色的点,但是我的树里有时候会有多个红色的点在measure位置上,这是因为hierarchy view的BUG么?
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
建议你用sdk自动的tools, 先打开模拟器 or android手机,
再打开 **\tools\hierarchyviewer工具
我试了一下,一切正常。
你的问题可能是eclipse环境or plugin的问题导致的。
同步到新浪微博
分享到微博?
Hi,欢迎来到 SegmentFault 技术社区!⊙▽⊙ 在这里,你可以提出编程相关的疑惑,关注感兴趣的问题,对认可的回答投赞同票;大家会帮你解决编程的问题,和你探讨技术更新,为你的回答投上赞同票。
明天提醒我
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:声明:由于本人一直用eng版的真机调试,所以此方法没有用过,记录在这里,有机会验证
-------------------------------------------------------------------
最近在看一个老外写的东西,发现里面有个类,使用这个类可以让任何设备使用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())
IHvDevice hvD
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);
hvDevice.initializeViewDebug();
DeviceSelectionModel.getModel().addDevice(hvDevice);
HierarchyViewerDirector.this.focusChanged(device);
ViewServerDevice.class
public boolean initializeViewDebug()
if (!this.mDevice.isOnline()) {
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);
this.mViewServerInfo = DeviceBridge.loadViewServerInfo(this.mDevice);
if (this.mViewServerInfo == null) {
this.mWindows = DeviceBridge.loadWindows(this, this.mDevice);
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];
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()) {
if (!checkCallingPermission(Manifest.permission.DUMP, "startViewServer")) {
if (port & 1024) {
if (mViewServer != null) {
if (!mViewServer.isRunning()) {
return mViewServer.start();
} catch (IOException e) {
Slog.w(TAG, "View server did not start");
mViewServer = new ViewServer(this, port);
return mViewServer.start();
} catch (IOException e) {
Slog.w(TAG, "View server did not start");
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) {
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();
public void run() {
while (Thread.currentThread() == mThread) {
// Any uncaught exception will crash the system process
Socket client = mServer.accept();
if (mThreadPool != null) {
mThreadPool.submit(new ViewServerWorker(client));
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 mC
private boolean mNeedWindowListU
private boolean mNeedFocusedWindowU
public ViewServerWorker(Socket client) {
mNeedWindowListUpdate =
mNeedFocusedWindowUpdate =
public void run() {
BufferedReader in =
in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);
final String request = in.readLine();
int index = request.indexOf(' ');
if (index == -1) {
parameters = "";
command = request.substring(0, index);
parameters = request.substring(index + 1);
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();
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) {
in.close();
} catch (IOException e) {
e.printStackTrace();
if (mClient != null) {
mClient.close();
} catch (IOException e) {
e.printStackTrace();
public void windowsChanged() {
synchronized(this) {
mNeedWindowListUpdate =
notifyAll();
public void focusChanged() {
synchronized(this) {
mNeedFocusedWindowUpdate =
notifyAll();
private boolean windowManagerAutolistLoop() {
mWindowManager.addWindowChangeListener(this);
BufferedWriter out =
out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));
while (!Thread.interrupted()) {
boolean needWindowListUpdate =
boolean needFocusedWindowUpdate =
synchronized (this) {
while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) {
if (mNeedWindowListUpdate) {
mNeedWindowListUpdate =
needWindowListUpdate =
if (mNeedFocusedWindowUpdate) {
mNeedFocusedWindowUpdate =
needFocusedWindowUpdate =
if (needWindowListUpdate) {
out.write("LIST UPDATE\n");
out.flush();
if (needFocusedWindowUpdate) {
out.write("ACTION_FOCUS UPDATE\n");
out.flush();
} catch (Exception e) {
} finally {
if (out != null) {
out.close();
} catch (IOException e) {
mWindowManager.removeWindowChangeListener(this);
从代码中可以看到,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.
import android.app.A
import android.content.C
import android.content.pm.ApplicationI
import android.os.B
import android.text.TextU
import android.util.L
import android.view.V
import android.view.ViewD
import java.io.BufferedR
import java.io.BufferedW
import java.io.IOE
import java.io.InputStreamR
import java.io.OutputS
import java.io.OutputStreamW
import java.lang.reflect.M
import java.net.InetA
import java.net.ServerS
import java.net.S
import java.util.HashM
import java.util.L
import java.util.Map.E
import java.util.concurrent.CopyOnWriteArrayL
import java.util.concurrent.ExecutorS
import java.util.concurrent.E
import java.util.concurrent.locks.ReentrantReadWriteL
* &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&To use this view server, your application must require the INTERNET
* permission.&/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&
* 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);
* In a similar fashion, you can use this API with an InputMethodService:
* 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);
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 mS
private final int mP
private Thread mT
private ExecutorService mThreadP
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 mFocusedW
private final ReentrantReadWriteLock mFocusLock = new ReentrantReadWriteLock();
private static ViewServer sS
* 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.
* 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()) {
sServer.start();
} catch (IOException e) {
Log.d(LOG_TAG, "Error:", e);
sServer = new NoopViewServer();
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) {
* 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) {
mThreadPool.shutdownNow();
} catch (SecurityException e) {
Log.w(LOG_TAG, "Could not stop all view server threads");
mThreadPool = null;
mThread = null;
mServer.close();
mServer = null;
return true;
} catch (IOException e) {
Log.w(LOG_TAG, "Could not close the view server");
mWindowsLock.writeLock().lock();
mWindows.clear();
} finally {
mWindowsLock.writeLock().unlock();
mFocusLock.writeLock().lock();
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);
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();
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();
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();
mFocusedWindow = view == null ? null : view.getRootView();
} finally {
mFocusLock.writeLock().unlock();
fireFocusChangedEvent();
* Main server loop.
public void run() {
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
Socket client = mServer.accept();
if (mThreadPool != null) {
mThreadPool.submit(new ViewServerWorker(client));
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) {
BufferedWriter out = null;
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) {
out.close();
} catch (IOException e) {
result = false;
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 mC
private boolean mNeedWindowListU
private boolean mNeedFocusedWindowU
private final Object[] mLock = new Object[0];
public ViewServerWorker(Socket client) {
mNeedWindowListUpdate = false;
mNeedFocusedWindowUpdate = false;
public void run() {
BufferedReader in = null;
in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);
final String request = in.readLine();
Log.i("Command", "===&" + request);
int index = request.indexOf(' ');
if (index == -1) {
parameters = "";
command = request.substring(0, index);
parameters = request.substring(index + 1);
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();
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) {
in.close();
} catch (IOException e) {
e.printStackTrace();
if (mClient != null) {
mClient.close();
} catch (IOException e) {
e.printStackTrace();
private boolean windowCommand(Socket client, String command, String parameters) {
boolean success = true;
BufferedWriter out = null;
// 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);
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) {
out.close();
} catch (IOException e) {
success = false;
private View findWindow(int hashCode) {
if (hashCode == -1) {
View window = null;
mWindowsLock.readLock().lock();
window = mFocusedW
} finally {
mWindowsLock.readLock().unlock();
mWindowsLock.readLock().lock();
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;
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) {
out.close();
} catch (IOException e) {
result = false;
private boolean getFocusedWindow(Socket client) {
boolean result = true;
String focusName = null;
BufferedWriter out = null;
OutputStream clientStream = client.getOutputStream();
out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
View focusedWindow = null;
mFocusLock.readLock().lock();
focusedWindow = mFocusedW
} finally {
mFocusLock.readLock().unlock();
if (focusedWindow != null) {
mWindowsLock.readLock().lock();
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) {
out.close();
} catch (IOException e) {
result = false;
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;
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) {
out.close();
} catch (IOException e) {
removeWindowListener(this);
return true;
private static class UncloseableOutputStream extends OutputStream {
private final OutputStream mS
UncloseableOutputStream(OutputStream 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() {
public boolean start() throws IOException {
return false;
public boolean stop() {
return false;
public boolean isRunning() {
return false;
public void addWindow(Activity activity) {
public void removeWindow(Activity activity) {
public void addWindow(View view, String name) {
public void removeWindow(View view) {
public void setFocusedWindow(Activity activity) {
public void setFocusedWindow(View view) {
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的,所以这点不用担心。
阅读(...) 评论()

我要回帖

更多关于 hierarchy viewer 的文章

 

随机推荐