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裝置
1 adb devices
假設我們有多台裝置串連在PC上,該命令的輸出為:
1 List of devices attached
2 emulator-5554 device
3 emulator-5556 device
以裝置emulator-5556為例,接下來我們把它的4939連接埠映射到PC的4939連接埠上:
1 adb -s emulator-5556 forward tcp:4939 tcp:4939
如果串連了多台Android裝置,HierarchyViewer將把下一台Android裝置的4939連接埠映射到PC的4940連接埠,以此類推。
(2)開啟ViewServer服務。
首先,需要判斷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
反之,關閉ViewServer的命令是:
1 adb -s emulator-5556 shell service call window 2 i32 4939
(3)串連ViewServer,既然ViewServer已經開啟,那麼下一步我們就需要串連它了。由於我們已經把裝置emulator-5556的4939連接埠映射為PC的4939連接埠上,所以我們需要串連的是127.0.0.1:4939。這需要寫一些java代碼:
01 import java.net.*;
02
03 try{
04 Socket socket = new Socket();
05 socket.connect(new InetSocketAddress("127.0.0.1", 4939),40000);
06 BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
07 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
08 }
09 } catch ( Exception e ) {
10 e.printStackTrace();
11 }
out和in用於發送命令和接受返回資料,需要注意的是,HierarchyViewer和ViewServer的通訊採用短串連,所以每發送一次命令,需要重建立立一次串連,所以以上代碼需要反覆調用。
如何擷取活動的Activity
在開啟HierarchyViewer時,會顯示每個裝置當前活動的Activity列表,如:
這是怎麼實現的呢? 這需要向ViewerServer發送"LIST"命令,看下面的代碼:
01 //send ‘LIST’ command
02 out.write("LIST");
03 out.newLine();
04 out.flush();
05
06 //receive response from viewserver
07 String context="";
08 String line;
09 while ((line = in.readLine()) != null) {
10 if ("DONE.".equalsIgnoreCase(line)) { //$NON-NLS-1$
11 break;
12 }
13 context+=line+"\r\n";
14 }
我們可以擷取到類似如下的列表
01 44fd1b78 com.android.internal.service.wallpaper.ImageWallpaper
02 4507aa28 com.android.launcher/com.android.launcher2.Launcher
03 45047328 com.tencent.mobileqq/com.tencent.mobileqq.activity.HomeActivity
04 450b8d18 com.tencent.mobileqq/com.tencent.mobileqq.activity.NotificationActivity
05 451049c0 com.tencent.mobileqq/com.tencent.mobileqq.activity.NotificationActivity
06 451167a8 com.tencent.mobileqq/com.tencent.mobileqq.activity.UpgradeActivity
07 450efef0 com.tencent.mobileqq/com.tencent.mobileqq.activity.UpgradeActivity
08 4502f2e0 TrackingView
09 4503f560 StatusBarExpanded
10 44fe0bb0 StatusBar
11 44f09250 Keyguard
注意,每行前面的16進位數字,那是一個hashcode,我們在進一步請求該Activity對應的控制項樹時要用到該hashcode。
如何擷取Activity的控制項樹
選中一個Activity後,HierarchyViewer將擷取它的控制項並顯示為層次圖:
擷取控制項樹資訊的命令是DUMP,後面要接對應的Activity的hash code,如果使用ffffffff作為參數,那麼就是取最前端的Activity。我們以com.android.launcher2.Launcher為例,它的hash code是4507aa28,看代碼:
01 //out.write("DUMP ffffffff");
02 out.write("DUMP 4507aa28");
03 out.newLine();
04 out.flush();
05
06 String context1="";
07 line="";
08 while ((line = in.readLine()) != null) {
09 if ("DONE.".equalsIgnoreCase(line)) { //$NON-NLS-1$
10 break;
11 }
12 context1+=line+"\r\n";
13 }
返回的控制項樹被儲存文本context1中,一般文本的內容都非常大,這裡我不把它全部列印出來,我們只取其中一行來看:
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作為參數,看下面的代碼:
1 import org.eclipse.swt.graphics.Image;
2 import org.eclipse.swt.widgets.Display;
3
4 out.write("CAPTURE 4507aa28 android.widget.FrameLayout@44edba90");
5 out.newLine();
6 out.flush();
7
8 Image image = new Image(Display.getDefault(), socket.getInputStream());
到此為止,我相信大家已經對HierarchyViewer的主要實現機制有了基本的瞭解,接下來我們就要真正開始閱讀HierarchyViewer的代碼了,後面幾章的內容大概是:
使用Eclipse閱讀和調試HierarchyViewer
HierarchyViewer的後台代碼導讀
HierarchyViewer的前台代碼導讀