看過 vysor原理以及Android同屏方案 , 我突然想到整個過程應該如何驗證的問題。于是反編譯了vysor 最新的apk, 其中的代碼邏輯依然具有很強的借鑒意義。其中通過 shell
環境下調用 adb
獲取截屏權限成為了全篇的亮點所在。以下文字簡要地記錄了個人的理解過程,同時希望增進對Android Framework 的理解。
0. 背景介紹
關于App的創建
由于 Zygote 在系統啟動時冷啟動了一個Dalvik
/ ART
VM
, 并開啟對創建新APP請求的監聽。隨后所有新的應用進程都由Zygote 執行 fork 操作而創建的。具體流程如下所述:
Linux內核啟動后,就開始了初始化 Android 系統(init process)的過程。/system/bin/app_process 運行并啟動了 Android運行時(AndroidRuntime.start()),在這期間運行時啟動了Dalvik 虛擬機,并且創建了zygote進程,以及開啟com.android.server.SystemServer 系統服務進程。Zygote將在有新的應用啟動時被激活,為了加速應用啟動的過程,Zygote會預加載公用的Java類和資源到RAM中,以供應用在實際運行時使用。最終,Zygote將fork自己并啟動這一新的應用進程。
Java 應用與 Android app的差異
從以上Android APP的編譯流程上,我們也不難看出:由于Android 平臺使用了一個不同于一般 JVM 的虛擬機,這就使得Java class 文件需要額外的處理(即 dex化)之后才能運行。
作為一個"推進器",上述 app_process
除了啟動 Zygote
進程外,還可以創建其它進程。有興趣的讀者可以進一步參考鏈接中的 Run a Java main on Android 部分, 在命令行中實際編譯Java代碼,dex
處理以及通過 adb shell
命令打印出 Android 平臺上的"Hello World"。
1. 實現
先上一個截取屏幕并在瀏覽器中顯示的效果圖:
1.0 與截屏的相關API
在OS 4.3 之前有標注為(@hide
)的API android.view.Surface.screenshot ()
; 而4.3之后API變為 android.view.SurfaceControl.screenshot()
. 非root的設備上,一般的APP是沒有權限調用以上的接口的。而在shell
環境下確實具備權限的,而這正一點好成為了一個突破口。
1.1 代碼入口方法
Java 類Main
的靜態方法 main()
中簡單實現了一個 HandlerThread
(可類比源碼中ActivityThread.main())。在looper 正在開始處理消息前,啟動本地的server, 并設置對screenshot GET請求的回調方法。回調處理過程使用上述的 screenshot()
方法進行屏幕截圖,并設置Bitmap數據為對應的 HTTP response。最后設置 adb forward tcp:53516 tcp:53516
將PC上所有 53516 端口通信數據重定向到手機端 53516 端口server上。
1.2 調用隱藏的API
在App內部,通常在有 Context 的情況下我們可以很方便地獲取系統服務:
WindowManager window = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
而此時的入口 Main 是由 app_process 啟動的一個獨立進程。于是問題就出現了,如何獲取當前屏幕的寬和高呢?想想框架中的 Java 部分代碼是如何進行進程間通信的,常見的 AIDL 成為了一個較好的方案。同樣利用框架提供的WINDOW_SERVICE
, 我們可以將系統源碼中的 IWindowManager.aidl 拷貝到工程目錄中,利用對應生成的 local stub 通過編譯,運行時通過反射調用對應所需的服務。
1.3 自動化ADB設置的命令行工具
類Unix系統上的工具 [cmd_runner.c] (https://github.com/rayworks/DroidCast/blob/master/cmd_tool/cmd_runner.c#L284).
為達到跨平臺功能,最近更新的 python 腳本 (Python 2.7.15)
已實現了自動化啟用截圖功能,其中包括 adb forward/unforward PC網絡請求,通過管道 (pipe) 跨進程通信得到已安裝APP的實際路徑,以及 shell 環境下調用 app_process
啟動內部截圖服務等。
對于已連接的單臺設備/模擬器,默認調用:
python scripts/automation.py
即可在自動打開的默認瀏覽器中查看已連接設備的瞬時截圖。
2. 源碼
目前代碼已經放在github DroidCast,歡迎大家 star 和 fork,并與我交流。
3. 最近更新
2019-12-9 使用
ip
命令替換ifconfig
獲取設備IP地址,避免執行時的權限問題2019-11-5 獨立腳本支持Python 3.6+
2019-9-11 遷移到 AndroidX
2019-8-22 利用Python 2to3 轉換工具,支持Python 3.6+
2019-5-30 Python 腳本中支持在有多個連接的設備時,對選定設備進行操作。
2019-5-18 用 Python 腳本自動化實現
adb
命令相關的設定和自動重置,以及打開默認瀏覽器查看截圖2019-5-7 增加對 websocket 的支持,在屏幕旋轉后 web 頁面自動刷新顯示新截圖
2019-4-14 支持不同格式的image請求(png, jpeg, webp)以及屏幕旋轉后的截圖
2019-2-28 適配 Android Pie
2019-2-16 更新命令行工具,實現自動打開默認瀏覽器查看截圖
2018-11-7 支持通過指定的大小截屏并顯示圖片
2018-10-30 增加
adb
設置說明,支持(相同網段WIFI環境下)無線使用場景2018-9-5 更新了命令行工具,使其能定位安裝到設備上的 apk 位置,解決 OS 4.3及以下的設備上出現的無法找到 class 導致的 crash。
2018-4-5 增加 *nix 環境下 command line tool (C 程序) 簡化對
adb
命令相關的設定和自動重置2018-3-28 解決OS 8.0 下 加載 base.apk 失敗的問題。
- You can no longer assume that APKs reside in directories whose names end in -1 or -2. Apps should use sourceDir to get the directory, and not rely on the directory format directly.