Stetho的通信原理

Stetho簡介

stetho是Facebook推出的安卓APP網絡診斷和數據監控的工具,接入方便,功能強大,是Android開發者必備的友好工具。
主要功能包括:

  • 實時查看App的佈局
  • 網絡請求抓包
  • 數據庫、SharedPreferences文件內容監控
  • 自定義dumpapp插件
  • 對於JavaScript的支持

具體的使用方法可以看這篇文章
本文主要想講一下自定義dumpapp插件的通信原理。

dumpapp插件示例

在主機上給設備發送一個files tree命令,得到如下結果:

$ ./dumpapp files tree
+---lib
+---cache
|   +---com.android.opengl.shaders_cache
+---files

在app中對應這樣一段java代碼,來處理files tree命令。

  private void doTree(PrintStream writer) throws DumpUsageException {
    File baseDir = getBaseDir(mContext);
    printDirectoryVisual(baseDir, 0, writer);
  }

問題是,爲什麼在主機上執行一段腳本(dumpapp.py)後會讓設備上的app執行相應的處理程序呢?

一般PushService可以完成類似的功能,後臺下發一條指令,客戶端完成指定的動作。對於Stetho這樣的Android調試工具來說,顯然不需要使用後臺,用ADB就可以實現。


ADB通信的原理

ADB的結構是一個client-server的結構,包含3個部分:

  • Client : 發送命令。客戶端在PC主機上運行,在shell裏使用Adb命令的時候就會開啓一個client。
  • Daemon : 在設備上執行命令。守護進程在設備上後臺運行。(aabd運行在Andriod設備的底層)
  • Server : 管理客戶端(client)和守護進程(daemon)的連接。server在PC主機上後臺運行。

smartsocket

android提供了smartsocket,詳見這裏

— smartsockets ——————————————————-
Port 5037 is used for smart sockets which allow a client on the host
side to request access to a service in the host adb daemon or in the
remote (device) daemon. The service is requested by ascii name,
preceeded by a 4 digit hex length. Upon successful connection an
“OKAY” response is sent, otherwise a “FAIL” message is returned. Once
connected the client is talking to that (remote or local) service.
client:
server: “OKAY”
client:
server: “FAIL”

總結來說,就是可以給adb-server發送一條指令<service-name>,然後adb-server會轉發給adbd,讓adbd來執行<service-name>.
舉例來說,當我們執行adb shell cat /proc/net/unix,最終就是通過adbd在設備上執行的。

Stetho的通信模型如下:

其中stetho-server就是app啓的一個Thread用來accept客戶端的connect。


程序流程

可以通過在關鍵位置打上斷點的方式來看程序的流程。
Python可以用Pycharm來打斷點。
如圖配置一個debug版本,這樣就可以以./dumapp -l的方式debug了。

Android app當然是用Android Studio打斷點了。

dumpapp.py流程分析

詳見代碼(dumpapp.py)

例子1:
adb.select_service('shell:cat /proc/net/unix')

通過這個命令其實是在找到指定的Unix域套接字。

/proc/net/unix文件下可以看到所有的unix域套接字,Path字段前面有@符號的表示它是一個ABSTRACT類型的socket,如果是絕對路徑則表示是FILESYSTEM類型的。

例子2:
發起一個connect到Unix域套接字的請求
adb.select_service('localabstract:%s' % (socket_name))

這裏的python用到的幾個service協議應該是android提供的smartsocket本身就支持的,在與adb的端口號連接後就能使用socket來發送service的名字給android設備了。
詳見這裏.
如下的命令就可以直接跟stetho-server連接。

stetho-server流程分析

詳見代碼LocalSocketServer.java

這裏創建ServerSocket時的address格式是stetho_+進程名+_ devtools_remote


Unix域套接字

socket API原本是爲網絡通訊設計的,但後來在socket的框架上發展出一種IPC機制,就是UNIX Domain Socket(Unix域協議)。雖然網絡socket也可用於同一臺主機的進程間通訊(通過loopback地址127.0.0.1),但是UNIX Domain Socket用於IPC更有效率:不需要經過網絡協議棧,不需要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另一個進程。這是因爲,IPC機制本質上是可靠的通訊,而網絡協議是爲不可靠的通訊設計的。UNIX Domain Socket也提供面向流和麪向數據包兩種API接口,類似於TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不會丟失也不會順序錯亂。

Unix域協議所用的API就是在不同主機上執行客戶/服務通信所用的套接字API。

Android中的Unix域套接字

在Android API中,有幾個類對Unix域套接字(也叫localsocket)進行了封裝,不僅可以用來應用程序之間進行IPC通信,還可以跨應用程序層和Linux層運行的程序進行通信。
LocalSocket在Unix域名空間創建一個套接字(非服務端)。
LocalSocketImpl是Framework層Socket的實現,通過JNI調用系統socket API。
LocalServerSocket創建服務器端Unix域套接字,與LocalSocket對應。

創建socket時指定的domain類型是AF_UNIX。

/**
 * Creates a socket in the underlying OS.
 */
public void create (int sockType) throws IOException {
    //...
    fd = Os.socket(OsConstants.AF_UNIX, osType, 0);    
}

通過搜索發現LocalSocketImpl的native實現是在libandroid_runtime.so中。

比如listen的native實現就是調用了socket的listen函數。

/* private native void listen_native(int fd, int backlog) throws IOException; */
static void
socket_listen (JNIEnv *env, jobject object, jobject fileDescriptor, jint backlog)
{
    int ret,fd;
    fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    ret = listen(fd, backlog);
    if (ret < 0) {
        jniThrowIOException(env, errno);
        return;
    }
}

參考

ADB原理,Wi-Fi連接,常用命令及拓展
《UNIX網絡編程卷1》
Android LocalSocket與Socket 區別
如何給安卓APP安裝聽診器,檢查數據問題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章