前期準備:html
關於什麼是Hierarchy Viewer,請查看官方文檔:http://developer.android.com/tools/debugging/debugging-ui.html。我的理解:Hierarchy Viewer能得到當前手機實時的UI信息,給界面設計人員和自動化測試人員帶來極大的便利。java
在Android的官方文檔中提到:android
To preserve security, Hierarchy Viewer can only connect to devices running a developer version of the Android system.shell
即:出於安全考慮,Hierarchy Viewer只能鏈接Android開發版手機或是模擬器(準確地說,只有ro.secure參數等於0且ro.debuggable等於1的android系統)。Hierarchy Viewer在鏈接手機時,手機上必須啓動一個叫View Server的客戶端與其進行socket通訊。而在商業手機上,是沒法開啓View Server的,故Hierarchy Viewer是沒法鏈接到普通的商業手機。apache
Android源碼實現這一限制的地方在:windows
ANDROID源碼根目錄\frameworks\base\services\java\com\android\server\wm\WindowManageService.java安全
中的一段:app
=====================================================================================socket
public boolean startViewServer(int port) {
if (isSystemSecure()) {
return false;
}
if (!checkCallingPermission(Manifest.permission.DUMP, "startViewServer")) {
return false;
}ide
....
檢驗一臺手機是否開啓了View Server的辦法爲:
adb shell service call window 3
若返回值是:Result: Parcel(00000000 00000000 '........')" 說明View Server處於關閉狀態
若返回值是:Result: Parcel(00000000 00000001 '........')" 說明View Server處於開啓狀態
如果一臺能夠打開View Server的手機(Android開發版手機 、模擬器or 按照本帖步驟給系統打補丁的手機),咱們可使用如下命令打開View Server:
adb shell service call window 1 i32 4939
使用如下命令關閉View Server:
adb shell service call window 2 i32 4939
實現步驟:
通過一番調查和實踐,我發現其實只要是root,而且裝有busybox的手機,經過修改手機上/system/framework中的某些文件,就能夠開啓。本文參考了http://blog.apkudo.com/tag/viewserver/,如下是具體步驟(本人基於Windows,若你是Linux的操做系統,直接看原帖吧):
前提是:你的手機已經得到ROOT權限,且有BUSYBOX
另外:請仔細閱讀本帖的評論,或許你會有新的收穫。
1.將商業手機經過USB鏈接PC,確保adb服務運行正常
2.備份手機上/system/framework/中的文件至PC。備份的時候請確保PC上保存備份文件的文件夾結構與手機中的/system/framework相同
例如:新建 ANDROID_SDK_ROOT\system\framework文件夾 (本文出現的ANDROID_SDK_ROOT指你安裝Android SDK的根目錄)
接着在cmd中跳轉至ANDROID_SDK_ROOT\platform-tools文件夾下,輸入如下代碼進行備份:
adb pull /system/framework ANDROID_SDK_ROOT\system\framework
3.進入adb shell,輸出BOOTCLASSPATH:
推薦的作法:
1. 在adb shell中echo $BOOTCLASSPATH > /sdcard/bootclasspath.txt
2. 退回到windows cmd中,輸入adb pull /sdcard/bootclasspath.txt
3. bootclasspath.txt將會保存在C:\Users\你的用戶名 文件夾下
在第十五步中將會用到這個txt中的內容。
4.下載baksmali 和smali工具。這兩個工具是用來反編譯和編譯odex文件的。
下載地址:
https://dl.dropboxusercontent.com/u/5055823/baksmali-1.4.2.jar
https://dl.dropboxusercontent.com/u/5055823/smali-1.4.2.jar
假設我將這兩個jar都下載到了ANDROID SDK根目錄下。
5.運行baksmali反編譯\system\framework下的services.odex文件:
java -jar ANDROID_SDK_ROOT\baksmali-1.4.2.jar -a 17 -x ANDROID_SDK_ROOT\system\framework\services.odex -d ANDROID_SDK_ROOT\system\framework
參數解釋:https://code.google.com/p/smali/wiki/DeodexInstructions
想特別說明的是「-a」後跟的數字,表示你係統的API Level(與你的系統版本有關)。系統版本和API Level的對照關係以下:
(另外,你不會連java -jar都不能運行吧?快去裝jdk!)
此步成功的話,在同文件夾下(對於我,就是ANDROID_SDK_ROOT),會有個out文件夾生成
這裏順便解釋一下odex文件和dex文件。
dex文件:Dex是Dalvik VM executes的全稱,即Android Dalvik執行程序,並不是Java的字節碼而是Dalvik字節碼,16進制機器指令。
odex文件:將dex文件依據具體機型而優化,造成的optimized dex文件,提升軟件運行速度,減小軟件運行時對RAM的佔用。
smali文件:將dex文件變爲可讀易懂的代碼形式,反編譯出文件的通常格式。
6.用Eclipse打開out\com\android\server\wm\WindowManagerService.smali文件
查找.method private isSystemSecure()Z這個函數
================================================================
.method private isSystemSecure()Z
.registers 4
.prologue
.line 5965
const-string v0, "1"
const-string v1, "ro.secure"
const-string v2, "1"
invoke-static {v1, v2}, Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v0
if-eqz v0, :cond_22
const-string v0, "0"
const-string v1, "ro.debuggable"
const-string v2, "0"
invoke-static {v1, v2}, Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v0
if-eqz v0, :cond_22
const/4 v0, 0x1
:goto_21
return v0
:cond_22
const/4 v0, 0x0
goto :goto_21
.end method
================================================================
在這段代碼的倒數7,8行「:goto_21」和「return v0」之間加入"const/4 v0, 0x0"一行.這樣,就使得v0返回的值永遠爲0x0,即false,這樣就跳過了WindowManagerService.java裏對isSystemSecure的判斷。
.method private isSystemSecure()Z函數最後變爲:
================================================================
.method private isSystemSecure()Z
.registers 4
.prologue
.line 6276
const-string v0, "1"
const-string v1, "ro.secure"
const-string v2, "1"
invoke-static {v1, v2}, Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v0
if-eqz v0, :cond_22
const-string v0, "0"
const-string v1, "ro.debuggable"
const-string v2, "0"
invoke-static {v1, v2}, Landroid/os/SystemProperties;->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v0
if-eqz v0, :cond_22
const/4 v0, 0x1
:goto_21
const/4 v0, 0x0
return v0
:cond_22
const/4 v0, 0x0
goto :goto_21
.end method
=====================================================================================
7. 如今運行smali,從新編譯:
java -jar smali-1.4.2.jar -o classes.dex
這時候,應該在ANDROID_SDK_ROOT文件夾中出現了classes.dex文件
8. 下載windows下的zip工具:
https://dl.dropboxusercontent.com/u/5055823/zip.exe
假設,我也把zip.exe放進了ANDROID_SDK_ROOT文件夾
9.確認當前cmd命令行運行目錄爲ANDROID_SDK_ROOT,運行:
zip.exe services_hacked.jar ./classes.dex
這時候在ANDROID_SDK_ROOT文件夾下,出現了打包好的services_hacked.jar
10.進入adb shell,輸入su得到ROOT權限
11.從新掛載/system,並更改/system權限
參考步驟(僅供參考,請確保使用相適應於本身手機的正確方法。請參考下面的"galfordq的blog"用戶的回覆):
a. 輸入mount,查看哪一個分區掛載了/system,例如個人是:
b. 輸入如下命令從新掛載/system,並更改/system權限(請將「/dev/block/mmcblk0p25」替換成你的/system掛載分區):
mount -o rw,remount -t yaffs2 /dev/block/mmcblk0p25
chmod -R 777 /system 使得/system 能夠被咱們任意修改
這一步的做用,主要是爲了第17步可以將/system/framework裏的services.odex替換掉。這一步若不成功,在第17步的時候可能出現權限不夠,沒法替換的錯誤(Read-Only File System)
12.下載dexopt-wrapper文件
https://dl.dropboxusercontent.com/u/5055823/dexopt-wrapper
咱們也將dexopt-wrapper文件放在ANDROID_SDK_ROOT文件夾中
13.將services_hacked.jar和dexopt-wrapper複製到手機的/data/local/tmp文件夾中
adb push ANDROID_SDK_ROOT/services_hacked.jar /data/local/tmp
adb push ANDROID_SDK_ROOT/dexopt-wrapper /data/local/tmp
14.進入adb shell,輸入su後,將dexopt-wrapper的權限改成777
chmod 777 /data/local/tmp/dexopt-wrapper
15.在adb shell中cd到/data/local/tmp文件夾下,運行:
./dexopt-wrapper ./services_hacked.jar ./services_hacked.odex <本帖第三步存的地址,可是要刪除其中的":/system/framework/services.jar">
這一步就是將第七部生成dex文件最終優化成了odex文件。[若是失敗,dexopt-wrapper文件拷貝到/system/bin目錄下]
===================================================================================================
例如個人命令是:./dexopt-wrapper ./services_hacked.jar ./services_hacked.odex /system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/
framework.jar:/system/framework/framework2.jar:/system/framework/android.policy.jar:/system/
framework/apache-xml.jar:/system/framework/HTCDev.jar:/system/framework/HTCExtension.jar:/system/
framework/filterfw.jar:/system/framework/com.htc.android.bluetooth.jar:/system/framework/wimax.jar:
/system/framework/usbnet.jar:/system/framework/com.orange.authentication.simcard.jar
===================================================================================================
這樣,便在/data/local/tmp文件夾中生成了咱們本身的odex:services_hacked.odex
16.給咱們本身生成的services_hacked.odex簽名:
busybox dd if=/system/framework/services.odex of=/data/local/tmp/services_hacked.odex bs=1 count=20 skip=52 seek=52 conv=notrunc
參數解釋:
if = input file
of = output file
bs = block size (1 byte)
count = number of blocks
skip = input file offset
seek = output file offset
conv=notrunc – don’t truncate the output file.
17.將/system/framework裏的services.odex替換成咱們本身製做的services_hacked.odex吧!
dd if=/data/local/tmp/services_hacked.odex of=/system/framework/services.odex
這一步運行後,過一小會兒(1分鐘之內)手機就自動重啓了!稍等片刻吧!
18.成功重啓後,用如下命令打開View Server:
adb shell service call window 1 i32 4939
用如下命令查看View Server是否打開:
adb shell service call window 3
返回的值如果Result: Parcel(00000000 00000001 '........'),那麼你就起了!
(若某一步失敗,須要恢復系統,請參考http://blog.apkudo.com/tag/viewserver/中的Step 16或聯繫個人郵箱進一步討論:czxttkl@gmail.com)
接下來,就在Eclipse下編譯運行HierarchyViewer來查看手機實時的UI樹吧。