1,导读
如果您有androidSDK里的monitor + Hierarchy View 工具,则推荐您使用这个UI工具。
否则,可以用本文中提到的,通过adb的方式来获取View里的画面内容。
AndroidStudio SDK里提供了一个monitor工具(新版本sdk obsolete掉了,需要通过特殊方法下载),里面内置的Hierarchy View窗口可以查看一些Window和View/子View的画面,可以方便我们分析一些画面相关问题。
应该是有方法,可以通过adb dump的方式,来获取Window里的View/子View的绘制内容!
问了DeepSeek,以及阅读了相关系统源码后,终于可以实现期望的效果。
DeepSeek的提示如下,可以忽略此处,比较冗长:
https://blog.csdn.net/Railshiqian/article/details/156202129
下图是monitor工具-HierarchyView查看Launcher activity画面的截图:

2,通过adb获取Window/View画面的方法
我们用的方法,是通过adb工具,跟system_server进程里的ViewServer建立连接,然后通过一些命令,来获取png文件。
以下流程,是此功能的时序原理:
adb连接到system_server的ViewService对象->
-> ViewService通过参数,选择某个窗口Window
-> 通过Binder通信,调用到App进程 -> ViewRootImpl-class W extends IWindow.Stub
-> 调用executeCommand方法输出Window信息,View信息和png数据
2.1 通过adb跟ViewServer工具建立连接
android系统的ViewServer监听了4939端口,我们可以通过此端口来进行通信。
首先通过adb进行主机端口和android设备端口映射:
java
命令:
adb forward tcp:4939 tcp:4939
确认是否已建立连接,有输出则代表连接已建立:
java
命令:
adb shell service call window 3
输出:
Result: Parcel( 00000000 00000001 '........')
2.2 获取Window列表和Window相关的View树结构
我们通过以下命令,获取可以dump的window列表:
java
echo "LIST" | nc localhost 4939
输出为:
e56af8b com.android.car.cluster.home/com.android.car.cluster.home.ClusterHomeActivity
8738156 com.android.car.cluster.home/com.android.car.cluster.home.FakeFreeNavigationActivity
77bc29e com.android.car.cluster.home/com.android.car.cluster.home.ClusterMusicActivity
b8ae3e5 com.android.car.cluster.osdouble/com.android.car.cluster.osdouble.ClusterOsDoubleActivity
915b393 com.android.car.carlauncher/com.android.car.carlauncher.CarLauncher
c02ee0f com.android.car.carlauncher/com.android.car.carlauncher.AppGridActivity
78d034a InputMethod
dc2d390
7648fb TopCarSystemBar
3379692 SystemUIOverlayWindow
6cea9d5 HeadsUpNotification
f9d30e2 BottomCarSystemBar
a282872 ScreenDecorOverlay
ff053ca ScreenDecorOverlayBottom
f54d52b InputMethod
DONE.
然后我们选择其中一个Window(以7648fb TopCarSystemBar为例),获取此Window对应的View树结构:
java
echo "DUMP 7648fb" | nc localhost 4939
输出较长,我们截取前几个ViewGroup的信息:
com.android.systemui.navigationbar.views.NavigationBarFrame@66fb06a padding:mForegroundPaddingBottom=1,0 padding:mForegroundPaddingLeft=1,0 padding:mForegroundPaddingRight=1,0 padding:mForegroundPaddingTop=1,0 measurement:mMeasureAllChildren=5,false drawing:getClipChildren()=4,true drawing:getClipToPadding()=4,true focus:getDescendantFocusability()=24,FOCUS_BEFORE_DESCENDANTS drawing:getPersistentDrawingCache()=9,SCROLLING focus:getTouchscreenBlocksFocus()=5,false drawing:isChildrenDrawingOrderEnabled()=5,false layout:mChildCountWithTransientState=1,0 mGroupFlags=8,0x224053 events:mLastTouchDownIndex=2,-1 events:mLastTouchDownTime=1,0 events:mLastTouchDownX=3,0.0 events:mLastTouchDownY=3,0.0 drawing:getAlpha()=3,1.0 layout:getBaseline()=2,-1 accessibility:getContentDescription()=4,null focus:getDefaultFocusHighlightEnabled()=4,true drawing:getElevation()=3,0.0 getFilterTouchesWhenObscured()=5,false getFitsSystemWindows()=5,false focus:getFocusable()=14,FOCUSABLE_AUTO layout:getHeight()=2,76 accessibility:getImportantForAccessibility()=3,yes getImportantForAutofill()=4,auto getImportantForContentCapture()=4,auto accessibility:getLabelFor()=2,-1 drawing:getLayerType()=4,NONE layout:getLayoutDirection()=22,RESOLVED_DIRECTION_LTR layout_flags=9,0x1840028 layout_horizontalWeight=3,0.0 layout_mFitInsetsSides_LEFT=3,0x1 layout_mFitInsetsSides_TOP=3,0x2 layout_mFitInsetsSides_RIGHT=3,0x4 layout_mFitInsetsSides_BOTTOM=3,0x8 layout_mFitInsetsSides=2,15 layout_mFitInsetsTypes=1,0 layout_privateFlags_FIT_INSETS_CONTROLLED=10,0x10000000 layout_privateFlags_INTERCEPT_GLOBAL_DRAG_AND_DROP=10,0x80000000 layout_privateFlags=11,-1879048192 layout_type=21,STATUS_BAR_ADDITIONAL layout_verticalWeight=3,0.0 layout_x=1,0 layout_y=1,0 layout:layout_height=2,76 layout:layout_width=12,MATCH_PARENT layout:getLocationOnScreen_x()=1,0 layout:getLocationOnScreen_y()=1,0 measurement:getMeasuredHeightAndState()=2,76 measurement:getMeasuredWidthAndState()=4,1848 drawing:getPivotX()=5,924.0 drawing:getPivotY()=4,38.0 layout:getRawLayoutDirection()=3,LTR text:getRawTextAlignment()=7,GRAVITY text:getRawTextDirection()=7,INHERIT drawing:getRotation()=3,0.0 drawing:getRotationX()=3,0.0 drawing:getRotationY()=3,0.0 drawing:getScaleX()=3,1.0 drawing:getScaleY()=3,1.0 getScrollBarStyle()=14,INSIDE_OVERLAY drawing:getSolidColor()=1,0 accessibility:getStateDescription()=4,null getTag()=4,null text:getTextAlignment()=7,GRAVITY text:getTextDirection()=12,FIRST_STRONG drawing:getTransitionAlpha()=3,1.0 getTransitionName()=4,null drawing:getTranslationX()=3,0.0 drawing:getTranslationY()=3,0.0 drawing:getTranslationZ()=3,0.0 getVisibility()=7,VISIBLE layout:getWidth()=4,1848 drawing:getX()=3,0.0 drawing:getY()=3,0.0 drawing:getZ()=3,0.0 focus:hasFocus()=5,false drawing:hasOverlappingRendering()=4,true drawing:hasShadow()=5,false layout:hasTransientState()=5,false accessibility:isAccessibilityDataSensitive()=5,false isActivated()=5,false isClickable()=5,false drawing:isDrawingCacheEnabled()=5,false isEnabled()=4,true focus:isFocusable()=5,false focus:isFocusableInTouchMode()=5,false focus:isFocused()=5,false focus:isFocusedByDefault()=5,false drawing:isForceDarkAllowed()=4,true isHapticFeedbackEnabled()=4,true drawing:isHardwareAccelerated()=4,true isHovered()=5,false isInTouchMode()=4,true focus:isKeyboardNavigationCluster()=5,false layout:isLayoutRtl()=5,false drawing:isOpaque()=5,false isPressed()=5,false isSelected()=5,false isSoundEffectsEnabled()=4,true drawing:willNotCacheDrawing()=5,false drawing:willNotDraw()=4,true bg_=4,null layout:mBottom=2,76 drawing:mClipBounds=4,null theme:com.android.systemui:style/Theme.SystemUI()=6,forced theme:android:style/Theme.DeviceDefault.Light.DarkActionBar()=6,forced fg_=4,null mID=21,id/car_top_bar_window layout:mLeft=1,0 measurement:mMeasuredHeight=2,76 measurement:mMeasuredWidth=4,1848 measurement:mMinHeight=1,0 measurement:mMinWidth=1,0 padding:mPaddingBottom=1,0 padding:mPaddingLeft=1,0 padding:mPaddingRight=1,0 padding:mPaddingTop=1,0 mPrivateFlags=9,0x10088B0 layout:mRight=4,1848 scrolling:mScrollX=1,0 scrolling:mScrollY=1,0 mSystemUiVisibility=3,0x0 layout:mTop=1,0 padding:mUserPaddingBottom=1,0 padding:mUserPaddingEnd=11,-2147483648 padding:mUserPaddingLeft=1,0 padding:mUserPaddingRight=1,0 padding:mUserPaddingStart=11,-2147483648 mViewFlags=10,0x18000090
com.android.systemui.car.systembar.CarSystemBarView@6d5948f layout:mBaselineAligned=4,true layout:mBaselineAlignedChildIndex=2,-1 measurement:mBaselineChildTop=1,0 measurement:mGravity=8,0x800033 measurement:mOrientation=1,1 measurement:mTotalLength=2,76 layout:mUseLargestChild=5,false layout:mWeightSum=4,-1.0 drawing:getClipChildren()=4,true drawing:getClipToPadding()=4,true focus:getDescendantFocusability()=24,FOCUS_BEFORE_DESCENDANTS drawing:getPersistentDrawingCache()=9,SCROLLING focus:getTouchscreenBlocksFocus()=5,false drawing:isChildrenDrawingOrderEnabled()=5,false layout:mChildCountWithTransientState=1,0 mGroupFlags=8,0x224053 events:mLastTouchDownIndex=2,-1 events:mLastTouchDownTime=1,0 events:mLastTouchDownX=3,0.0 events:mLastTouchDownY=3,0.0 drawing:getAlpha()=3,1.0 layout:getBaseline()=2,-1 accessibility:getContentDescription()=4,null focus:getDefaultFocusHighlightEnabled()=4,true drawing:getElevation()=3,0.0 getFilterTouchesWhenObscured()=5,false getFitsSystemWindows()=5,false focus:getFocusable()=13,NOT_FOCUSABLE layout:getHeight()=2,76 accessibility:getImportantForAccessibility()=4,auto getImportantForAutofill()=4,auto getImportantForContentCapture()=4,auto accessibility:getLabelFor()=2,-1 drawing:getLayerType()=4,NONE layout:getLayoutDirection()=22,RESOLVED_DIRECTION_LTR layout:layout_bottomMargin=1,0 layout:layout_endMargin=11,-2147483648 layout:layout_leftMargin=1,0 layout:layout_mMarginFlags=4,0x0C layout:layout_rightMargin=1,0 layout:layout_startMargin=11,-2147483648 layout:layout_topMargin=1,0 layout:layout_height=12,MATCH_PARENT layout:layout_width=12,MATCH_PARENT layout:getLocationOnScreen_x()=1,0 layout:getLocationOnScreen_y()=1,0 measurement:getMeasuredHeightAndState()=2,76 measurement:getMeasuredWidthAndState()=4,1848 drawing:getPivotX()=5,924.0 drawing:getPivotY()=4,38.0 layout:getRawLayoutDirection()=7,INHERIT text:getRawTextAlignment()=7,GRAVITY text:getRawTextDirection()=7,INHERIT drawing:getRotation()=3,0.0 drawing:getRotationX()=3,0.0 drawing:getRotationY()=3,0.0 drawing:getScaleX()=3,1.0 drawing:getScaleY()=3,1.0 getScrollBarStyle()=14,INSIDE_OVERLAY drawing:getSolidColor()=1,0 accessibility:getStateDescription()=4,null getTag()=4,null text:getTextAlignment()=7,GRAVITY text:getTextDirection()=12,FIRST_STRONG drawing:getTransitionAlpha()=3,1.0 getTransitionName()=4,null drawing:getTranslationX()=3,0.0 drawing:getTranslationY()=3,0.0 drawing:getTranslationZ()=3,0.0 getVisibility()=7,VISIBLE layout:getWidth()=4,1848 drawing:getX()=3,0.0 drawing:getY()=3,0.0 drawing:getZ()=3,0.0 focus:hasFocus()=5,false drawing:hasOverlappingRendering()=4,true drawing:hasShadow()=5,false layout:hasTransientState()=5,false accessibility:isAccessibilityDataSensitive()=5,false isActivated()=5,false isClickable()=4,true drawing:isDrawingCacheEnabled()=5,false isEnabled()=4,true focus:isFocusable()=5,false focus:isFocusableInTouchMode()=5,false focus:isFocused()=5,false focus:isFocusedByDefault()=5,false drawing:isForceDarkAllowed()=4,true isHapticFeedbackEnabled()=4,true drawing:isHardwareAccelerated()=4,true isHovered()=5,false isInTouchMode()=4,true focus:isKeyboardNavigationCluster()=5,false layout:isLayoutRtl()=5,false drawing:isOpaque()=4,true isPressed()=5,false isSelected()=5,false isSoundEffectsEnabled()=4,true drawing:willNotCacheDrawing()=5,false drawing:willNotDraw()=4,true bg_state_mUseColor=9,-16777216 layout:mBottom=2,76 drawing:mClipBounds=4,null theme:com.android.systemui:style/Theme.SystemUI()=6,forced theme:android:style/Theme.DeviceDefault.Light.DarkActionBar()=6,forced fg_=4,null mID=14,id/car_top_bar layout:mLeft=1,0 measurement:mMeasuredHeight=2,76 measurement:mMeasuredWidth=4,1848 measurement:mMinHeight=1,0 measurement:mMinWidth=1,0 padding:mPaddingBottom=1,0 padding:mPaddingLeft=1,0 padding:mPaddingRight=1,0 padding:mPaddingTop=1,0 mPrivateFlags=9,0x1808830 layout:mRight=4,1848 scrolling:mScrollX=1,0 scrolling:mScrollY=1,0 mSystemUiVisibility=3,0x0 layout:mTop=1,0 padding:mUserPaddingBottom=1,0 padding:mUserPaddingEnd=11,-2147483648 padding:mUserPaddingLeft=1,0 padding:mUserPaddingRight=1,0 padding:mUserPaddingStart=11,-2147483648 mViewFlags=10,0x18004080
......
DONE.
DONE
2.3 获取View的绘制内容并保存到png文件
java
命令如下, 7648fb是window的id,NavigationBarFrame@66fb06a是View的ID :
echo "CAPTURE 7648fb com.android.systemui.navigationbar.views.NavigationBarFrame@66fb06a" | nc localhost 4939 |tee 1.png
或
echo "CAPTURE 7648fb com.android.systemui.navigationbar.views.NavigationBarFrame@66fb06a" | nc localhost 4939 > 1.png
1.png的内容为,确认是我们认为的状态栏的图像:

3,原理分析和系统源码导读
3.1 通过adb跟system_server里的ViewServer功能建立连接
android-15.0.0_r17/frameworks/base/services/core/java/com/android/server/wm/ViewServer.java
java
class ViewServer implements Runnable {
/**
* The default port used to start view servers.
*/
public static final int VIEW_SERVER_DEFAULT_PORT = 4939;
......
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);
}
...
} ...
}
3.2 找到目标Window并通过binder通信调用到app端
android-15.0.0_r17/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
java
boolean viewServerWindowCommand(Socket client, String command, String parameters) {
......
// Any uncaught exception will crash the system process
try {
// Find the hashcode 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 WindowState window = findWindow(hashCode);
if (window == null) {
return false;
}
data = Parcel.obtain();
data.writeInterfaceToken("android.view.IWindow");
data.writeString(command);
data.writeString(parameters);
data.writeInt(1);
ParcelFileDescriptor.fromSocket(client).writeToParcel(data, 0);
reply = Parcel.obtain();
final IBinder binder = window.mClient.asBinder();
// TODO: GET THE TRANSACTION CODE IN A SAFER MANNER
// 通过binder调用到App进程ViewRootImpl的executeCommand方法
binder.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
} ......
return success;
}
3.3 App进程执行command命令
android-15.0.0_r17/frameworks/base/core/java/android/view/ViewRootImpl.java
java
@Override
public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
final View view = viewAncestor.mView;
if (view != null) {
......
OutputStream clientStream = null;
try {
clientStream = new ParcelFileDescriptor.AutoCloseOutputStream(out);
ViewDebug.dispatchCommand(view, command, parameters, clientStream);
} ......
}
}
}
3.4 App进程将View画面保存到png文件中
android-15.0.0_r17/frameworks/base/core/java/android/view/ViewDebug.java
java
public static void capture(View root, final OutputStream clientStream, View captureView)
throws IOException {
Bitmap b = performViewCapture(captureView, false);
if (b == null) {
Log.w("View", "Failed to create capture bitmap!");
// Send an empty one so that it doesn't get stuck waiting for
// something.
b = Bitmap.createBitmap(root.getResources().getDisplayMetrics(),
1, 1, Bitmap.Config.ARGB_8888);
}
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(clientStream, 32 * 1024);
b.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();
} ......
}
给个赞吧~
---------------------------------------------------end line--------------------------------------------------------