Android 之HierarchyViewer - 4939
此文章摘抄于http://www.cnblogs.com/vowei/archive/2012/07/30/2614353.html
與大家共勉:
HierarchyViewer是Android SDK包中一個非常好用的工具,你在 android-sdks/tools目錄下可以找到它。通過HierarchyViewer,即使沒有應(yīng)用的源代碼,我們也可以非常直觀地瀏覽Activity中控件的層次結(jié)構(gòu)圖,以及每個控件的屬性和截圖,這對于測試人員編寫自動化測試用例是極有幫助的。這個系列的文章,我們將通過閱讀和解析HierarchyViewer的代碼,來了解HierarchyViewer是如何工作的,也可以加深A(yù)ndroid提供給開發(fā)者的各種接口的了解。本系列文章代碼基于android4.0的源代碼,還沒有下載源代碼的同學(xué)快去下載吧,旅程這就開始了。
本文首先并不直接從源代碼閱讀開始,而是demo和解釋HierarchyViewer的主要工作原理,這可是作者從源代碼中抽取的精華啊:)。看完本文,你就可以寫一個自己簡單的HierarchyViewer了。我們主要講解如下幾個部分:
1,如何連接ViewServer
2,如何獲取活動的Activities
3,如何獲取Activity的控件樹
4,如何獲取截圖
?
如何連接ViewServer
ViewServer是Android通過4939端口提供的服務(wù),HierarchyViewer主要是通過它來獲取獲取Activity信息的, HierarchyViewer主要做下面3件事情來連接ViewServer。這需要用到Adb,HierarchyViewer中是直接通過api來調(diào)用Adb的,而這里我們先使用命令行adb來實現(xiàn)同樣的功能。
(1)Forword端口。就是把Android設(shè)備上的4939端口映射到PC的某端口上,這樣,向PC的該端口號發(fā)包都會轉(zhuǎn)發(fā)到Android設(shè)備的4939端口上。
首先,輸入命令列出所有Android設(shè)備
| 1 | adb devices |
?
假設(shè)我們有多臺設(shè)備連接在PC上,該命令的輸出為:
| 1 2 3 | List of devices attached emulator-5554?? device emulator-5556?? device |
?
以設(shè)備emulator-5556為例,接下來我們把它的4939端口映射到PC的4939端口上:
| 1 | adb -s emulator-5556 forward tcp:4939 tcp:4939 |
如果連接了多臺Android設(shè)備,HierarchyViewer將把下一臺Android設(shè)備的4939端口映射到PC的4940端口,以此類推。
?
(2)打開ViewServer服務(wù)。
首先,需要判斷ViewServer是否打開:
| 1 | adb -s emulator-5556 shell service call window 3 |
?
如果返回值是"Result: Parcel(00000000 00000000 '........')",說明ViewServer沒有打開,那么需要用下面的命令打開ViewServer:
| 1 | adb -s emulator-5556 shell service call window 1 i32 4939 |
?
反之,關(guān)閉ViewServer的命令是:
| 1 | adb -s emulator-5556 shell service call window 2 i32 4939 |
?
(3)連接ViewServer,既然ViewServer已經(jīng)打開,那么下一步我們就需要連接它了。由于我們已經(jīng)把設(shè)備emulator-5556的4939端口映射為PC的4939端口上,所以我們需要連接的是127.0.0.1:4939。這需要寫一些java代碼:
| 1 2 3 4 5 6 7 8 9 10 11 | importjava.net.*; try{ ????Socket socket = newSocket(); ????socket.connect(newInetSocketAddress("127.0.0.1", 4939),40000); ????BufferedWriter out = newBufferedWriter(newOutputStreamWriter(socket.getOutputStream())); ????BufferedReader in = newBufferedReader(newInputStreamReader(socket.getInputStream(), "utf-8")); } } catch( Exception e ) { ??????e.printStackTrace(); } |
out和in用于發(fā)送命令和接受返回數(shù)據(jù),需要注意的是,HierarchyViewer和ViewServer的通信采用短連接,所以每發(fā)送一次命令,需要重新建立一次連接,所以以上代碼需要反復(fù)調(diào)用。
?
如何獲取活動的Activity
在打開HierarchyViewer時,會顯示每個設(shè)備當(dāng)前活動的Activity列表,如下圖:
?
這是怎么實現(xiàn)的呢? 這需要向ViewerServer發(fā)送"LIST"命令,看下面的代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //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"; } |
?
我們可以獲取到類似如下的列表
| 1 2 3 4 5 6 7 8 9 10 11 | 44fd1b78 com.android.internal.service.wallpaper.ImageWallpaper 4507aa28 com.android.launcher/com.android.launcher2.Launcher 45047328 com.tencent.mobileqq/com.tencent.mobileqq.activity.HomeActivity 450b8d18 com.tencent.mobileqq/com.tencent.mobileqq.activity.NotificationActivity 451049c0 com.tencent.mobileqq/com.tencent.mobileqq.activity.NotificationActivity 451167a8 com.tencent.mobileqq/com.tencent.mobileqq.activity.UpgradeActivity 450efef0 com.tencent.mobileqq/com.tencent.mobileqq.activity.UpgradeActivity 4502f2e0 TrackingView 4503f560 StatusBarExpanded 44fe0bb0 StatusBar 44f09250 Keyguard |
注意,每行前面的16進制數(shù)字,那是一個hashcode,我們在進一步請求該Activity對應(yīng)的控件樹時要用到該hashcode。
?
如何獲取Activity的控件樹?
選中一個Activity后,HierarchyViewer將獲取它的控件并顯示為層次圖:
?
獲取控件樹信息的命令是DUMP,后面要接對應(yīng)的Activity的hash code,如果使用ffffffff作為參數(shù),那么就是取最前端的Activity。我們以com.android.launcher2.Launcher為例,它的hash code是4507aa28,看代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | //out.write("DUMP ffffffff"); out.write("DUMP 4507aa28"); out.newLine(); out.flush(); ????????? String context1=""; line=""; while ((line = in.readLine()) != null) { ????if ("DONE.".equalsIgnoreCase(line)) { //$NON-NLS-1$ ????????break; ????} ????context1+=line+"\r\n"; } |
?
返回的控件樹被保存文本context1中,一般文本的內(nèi)容都非常大,這里我不把它全部打印出來,我們只取其中一行來看:
| 1 | 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,16911408 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,402653186 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作為參數(shù),看下面的代碼:
| 1 2 3 4 5 6 7 8 | importorg.eclipse.swt.graphics.Image; importorg.eclipse.swt.widgets.Display; out.write("CAPTURE 4507aa28 android.widget.FrameLayout@44edba90"); out.newLine(); out.flush(); Image image = newImage(Display.getDefault(), socket.getInputStream()); |
?
HierarchyViewer是Android SDK包中一個非常好用的工具,你在 android-sdks/tools目錄下可以找到它。通過HierarchyViewer,即使沒有應(yīng)用的源代碼,我們也可以非常直觀地瀏覽Activity中控件的層次結(jié)構(gòu)圖,以及每個控件的屬性和截圖,這對于測試人員編寫自動化測試用例是極有幫助的。這個系列的文章,我們將通過閱讀和解析HierarchyViewer的代碼,來了解HierarchyViewer是如何工作的,也可以加深A(yù)ndroid提供給開發(fā)者的各種接口的了解。本系列文章代碼基于android4.0的源代碼,還沒有下載源代碼的同學(xué)快去下載吧,旅程這就開始了。
本文首先并不直接從源代碼閱讀開始,而是demo和解釋HierarchyViewer的主要工作原理,這可是作者從源代碼中抽取的精華啊:)。看完本文,你就可以寫一個自己簡單的HierarchyViewer了。我們主要講解如下幾個部分:
1,如何連接ViewServer
2,如何獲取活動的Activities
3,如何獲取Activity的控件樹
4,如何獲取截圖
?
如何連接ViewServer
ViewServer是Android通過4939端口提供的服務(wù),HierarchyViewer主要是通過它來獲取獲取Activity信息的, HierarchyViewer主要做下面3件事情來連接ViewServer。這需要用到Adb,HierarchyViewer中是直接通過api來調(diào)用Adb的,而這里我們先使用命令行adb來實現(xiàn)同樣的功能。
(1)Forword端口。就是把Android設(shè)備上的4939端口映射到PC的某端口上,這樣,向PC的該端口號發(fā)包都會轉(zhuǎn)發(fā)到Android設(shè)備的4939端口上。
首先,輸入命令列出所有Android設(shè)備
| 1 | adb devices |
?
假設(shè)我們有多臺設(shè)備連接在PC上,該命令的輸出為:
| 1 2 3 | List of devices attached emulator-5554?? device emulator-5556?? device |
?
以設(shè)備emulator-5556為例,接下來我們把它的4939端口映射到PC的4939端口上:
| 1 | adb -s emulator-5556 forward tcp:4939 tcp:4939 |
如果連接了多臺Android設(shè)備,HierarchyViewer將把下一臺Android設(shè)備的4939端口映射到PC的4940端口,以此類推。
?
(2)打開ViewServer服務(wù)。
首先,需要判斷ViewServer是否打開:
| 1 | adb -s emulator-5556 shell service call window 3 |
?
如果返回值是"Result: Parcel(00000000 00000000 '........')",說明ViewServer沒有打開,那么需要用下面的命令打開ViewServer:
| 1 | adb -s emulator-5556 shell service call window 1 i32 4939 |
?
反之,關(guān)閉ViewServer的命令是:
| 1 | adb -s emulator-5556 shell service call window 2 i32 4939 |
?
(3)連接ViewServer,既然ViewServer已經(jīng)打開,那么下一步我們就需要連接它了。由于我們已經(jīng)把設(shè)備emulator-5556的4939端口映射為PC的4939端口上,所以我們需要連接的是127.0.0.1:4939。這需要寫一些java代碼:
| 1 2 3 4 5 6 7 8 9 10 11 | importjava.net.*; try{ ????Socket socket = newSocket(); ????socket.connect(newInetSocketAddress("127.0.0.1", 4939),40000); ????BufferedWriter out = newBufferedWriter(newOutputStreamWriter(socket.getOutputStream())); ????BufferedReader in = newBufferedReader(newInputStreamReader(socket.getInputStream(), "utf-8")); } } catch( Exception e ) { ??????e.printStackTrace(); } |
out和in用于發(fā)送命令和接受返回數(shù)據(jù),需要注意的是,HierarchyViewer和ViewServer的通信采用短連接,所以每發(fā)送一次命令,需要重新建立一次連接,所以以上代碼需要反復(fù)調(diào)用。
?
如何獲取活動的Activity
在打開HierarchyViewer時,會顯示每個設(shè)備當(dāng)前活動的Activity列表,如下圖:
?
這是怎么實現(xiàn)的呢? 這需要向ViewerServer發(fā)送"LIST"命令,看下面的代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //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"; } |
?
我們可以獲取到類似如下的列表
| 1 2 3 4 5 6 7 8 9 10 11 | 44fd1b78 com.android.internal.service.wallpaper.ImageWallpaper 4507aa28 com.android.launcher/com.android.launcher2.Launcher 45047328 com.tencent.mobileqq/com.tencent.mobileqq.activity.HomeActivity 450b8d18 com.tencent.mobileqq/com.tencent.mobileqq.activity.NotificationActivity 451049c0 com.tencent.mobileqq/com.tencent.mobileqq.activity.NotificationActivity 451167a8 com.tencent.mobileqq/com.tencent.mobileqq.activity.UpgradeActivity 450efef0 com.tencent.mobileqq/com.tencent.mobileqq.activity.UpgradeActivity 4502f2e0 TrackingView 4503f560 StatusBarExpanded 44fe0bb0 StatusBar 44f09250 Keyguard |
注意,每行前面的16進制數(shù)字,那是一個hashcode,我們在進一步請求該Activity對應(yīng)的控件樹時要用到該hashcode。
?
如何獲取Activity的控件樹?
選中一個Activity后,HierarchyViewer將獲取它的控件并顯示為層次圖:
?
獲取控件樹信息的命令是DUMP,后面要接對應(yīng)的Activity的hash code,如果使用ffffffff作為參數(shù),那么就是取最前端的Activity。我們以com.android.launcher2.Launcher為例,它的hash code是4507aa28,看代碼:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | //out.write("DUMP ffffffff"); out.write("DUMP 4507aa28"); out.newLine(); out.flush(); ????????? String context1=""; line=""; while ((line = in.readLine()) != null) { ????if ("DONE.".equalsIgnoreCase(line)) { //$NON-NLS-1$ ????????break; ????} ????context1+=line+"\r\n"; } |
?
返回的控件樹被保存文本context1中,一般文本的內(nèi)容都非常大,這里我不把它全部打印出來,我們只取其中一行來看:
| 1 | 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,16911408 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,402653186 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作為參數(shù),看下面的代碼:
| 1 2 3 4 5 6 7 8 | importorg.eclipse.swt.graphics.Image; importorg.eclipse.swt.widgets.Display; out.write("CAPTURE 4507aa28 android.widget.FrameLayout@44edba90"); out.newLine(); out.flush(); Image image = newImage(Display.getDefault(), socket.getInputStream()); |
?到此為止,我相信大家已經(jīng)對HierarchyViewer的主要實現(xiàn)機制有了基本的了解
總結(jié)
以上是生活随笔為你收集整理的Android 之HierarchyViewer - 4939的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CnOpenData中国绿色专利分地区统
- 下一篇: C语言多任务,多进程,多线程