Stetho,一個功能強大的 Android 應用調試橋

What is Stetho ?

Stetho 是一個功能強大的 Android 應用調試橋,起到橋梁的作用,連接 Android 應用和 Chrome,通過 Chrome 開發者工具調試 Android 應用,提供視圖元素檢查,網絡監控,數據庫動態交互,Dumpapp(可擴展的命令行交互接口),JavaScript Console 等功能。

當啟用后,開發者可以通過 Chrome 桌面瀏覽器中的開發者工具訪問本地應用。開發者也可以選擇啟用可選的 dumpapp 工具提供一個強大的應用內部命令行接口。

旦你完成了下面的設置說明,只要啟動你電腦上的 Chrome 瀏覽器并輸入 chrome://inspect 點擊 "Inspect" 按鈕即可開始調試。

配置說明

  1. 添加 stetho 主依賴

    在 Gradle 中包含 stetho

    // Gradle dependency on Stetho 
       dependencies { 
         compile 'com.非死book.stetho:stetho:1.4.1' 
       }

    在 Maven 中包含 stetho

    <dependency>
         <groupid>com.非死book.stetho</groupid> 
         <artifactid>stetho</artifactid> 
         <version>1.4.1</version> 
       </dependency>
  2. 只有 stetho 主依賴是必須的,但你可能還希望有一個網絡助手

    dependencies { 
         compile 'com.非死book.stetho:stetho-okhttp3:1.4.1' 
       }

    或者:

    dependencies { 
         compile 'com.非死book.stetho:stetho-okhttp:1.4.1' 
       }

    或者:

    dependencies { 
         compile 'com.非死book.stetho:stetho-urlconnection:1.4.1' 
       }

功能說明

Chrome DevTools

Stetho 為你的應用提供了 C/S 協議實現,所以你可以通過 Chrome 集成的前端開發工具訪問你的應用。只要你的應用集成了 Stetho,只需導航到 chrome://inspect 并點擊 "Inspect" 即可開始使用。

Network Inspection

使用 Chrome 開發者工具各種功能實現網絡監控,包括圖片預覽,JSON 響應輔助工具,甚至把跟蹤信息導出為 HAR 格式文件。

Database Inspection

SQLite 數據庫可視化與交互,具備完全讀寫功能,

  • Web SQL 下的是應用的數據庫,點擊數據庫可以輸入 SQL 語句對其進行操作
  • Local Storage 就是對應 Android 下的 SharedPreferences,可修改 SharedPreferences 中的值

View Hierarchy

View Hierarchy 支持 API 15 或更高版本。使用 View Hierarchy 可以很方便的檢查界面元素,比如:

  1. View Hierarchy 中包含界面所有元素的層次結構和屬性
  2. 鼠標移動到 View Hierarchy 中某個 View,app 中對應的 View 會高亮顯示
  3. 點擊 View Hierarchy 左上角的搜索按鈕,再點擊 app 當前界面的控件,View Hierarchy 會顯示該控件在層次中的位置

dumpapp

Dumpapp 為應用提供了一個可擴展的命令行交互接口,提供了一組默認的插件,但是 dumpapp 的真正強大之處在于能夠輕松創建自己的插件!

dumpapp 就在工程的 scripts/dumpapp 下,遺憾的是目前在 Windows 下還用不了,因為它只提供了 Linux/Mac 下的執行腳本。

常用命令(插件):

  • 列出所有 Plugin : ./scripts/dumpapp -p com.非死book.stetho.sample -l
  • 打印 SharedPreferences : ./scripts/dumpapp prefs print
  • 寫 SharedPreferences : ./scripts/dumpapp prefs write <path> <key> <type> <value>

dumpapp 默認提供的插件就在 com.非死book.stetho.dumpapp.plugins.* ,具體使用方法可以參考源碼中的說明。

JavaScript Console

JavaScript Console 允許執行那些可以與應用或 Android SDK 交互的 JavaScript 代碼。

Stetho 使用 Rhino 實現使用腳本方式調用 Java。

Rhino 是一個完全使用Java語言編寫的開源JavaScript實現。Rhino通常用于在Java程序中,為最終用戶提供腳本化能力。它被作為J2SE 6上的默認Java腳本化引擎。

集成說明

1. 初始化

在你的 Application 初始化時候調用 Stetho 的初始化方法:

public class MyApplication extends Application {
  public void onCreate() {
    super.onCreate();
    Stetho.initializeWithDefaults(this);
  }
}

這將啟用大多數默認配置,但不啟用一些額外的鉤子(啟用網絡監控需要注意)。 有關各個子系統的具體細節,請參見下文。

2. 啟用網絡監控

如果你使用的是 2.2.x+ 或 3.x 版本的 OkHttp 庫,可以使用攔截器系統自動掛接到現有堆棧。 這是目前啟用網絡監控的最簡單和最直接的方法。

For OkHttp 2.x

OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(new StethoInterceptor());

For OkHttp 3.x

new OkHttpClient.Builder()
    .addNetworkInterceptor(new StethoInterceptor())
    .build();

由于攔截器可以修改請求和響應,應該在其他攔截器之后添加 Stetho 攔截器以獲取準確的網絡交互視圖。

如果你使用 HttpURLConnection ,可以使用 StethoURLConnectionManager 來幫助集成,但該方法有一些注意事項,比如你必須明確地添加 Accept-Encoding:gzip 到請求頭,并手動處理壓縮的響應,以便 Stetho 報告壓縮的有效負載大小。具體可以參考 stetho-sample 中的 Networker 的實現。

Stetho 目前沒有提供 HttpClient 網絡監控支持,具體原因可以查看 issues 116 (HttpClient 在 Android5.0 已經被廢棄,不建議再使用)。

OkHttp + Retrofit

一般開發中我們都會使用 OkHttp + Retrofit , OkHttp 用于 HTTP 網絡交互, Retrofit 用于將 HTTP API 轉換為 Java 接口。

默認情況下, Retrofit 會自己創建一個 OkHttpClient ,我們也可以在創建 Retrofit 的時候通過 client(OkHttpClient client) 方法提供一個 OkHttpClient 。

sRetrofit = new Retrofit.Builder()
        .baseUrl(baseUrl)
        .client(sClient)
        .build();

通過 client(OkHttpClient client) 方法設置共用一個 OkHttpClient ,監控 Retrofit 中的網絡交互。

更多細節見 stetho-sample 項目。

3. 自定義 dumpapp 插件

自定義插件主要是實現 DumperPlugin 接口中的 String getName() 和 void dump(DumperContext dumpContext) 方法。 getName() 方法返回插件的名稱, dump(DumperContext dumpContext) 是命令行中調用該插件時的回調方法。其中, dumpContext.getStdout() 獲取命令行輸出, dumpContext.getArgsAsList() 獲取命令行調用的參數列表。

public class MyDumperPlugin implements DumperPlugin {

private static final String XML_SUFFIX = ".xml"; private static final String NAME = "prefs"; private final Context mAppContext;

public MyDumperPlugin(Context context) { mAppContext = context.getApplicationContext(); }

@Override public String getName() { return NAME; }

@Override public void dump(DumperContext dumpContext) throws DumpUsageException { PrintStream writer = dumpContext.getStdout(); List<String> args = dumpContext.getArgsAsList();

String commandName = args.isEmpty() ? "" : args.remove(0);

if (commandName.equals("print")) {
  doPrint(writer, args);
} else if (commandName.equals("write")) {
  doWrite(args);
} else {
  doUsage(writer);
}

}

// 省略部分代碼

}</code></pre>

然后把初始化調用替換如下:

Stetho.initialize(Stetho.newInitializerBuilder(context)
    .enableDumpapp(new DumperPluginsProvider() {
      @Override
      public Iterable<DumperPlugin> get() {
        return new Stetho.DefaultDumperPluginsBuilder(context)
            .provide(new MyDumperPlugin())
            .finish();
      }
    })
    .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context))
    .build());

4. 啟用JavaScript Console

啟用 JavaScript Console 只需在 build.gradle 中添加如下依賴即可:

compile "com.非死book.stetho:stetho-js-rhino:1.4.1"

啟動 app,在 Chrome 開發者工具的 Console 輸入下面代碼使 app 打印一個Toast:

importPackage(android.widget);
importPackage(android.os);
var handler = new Handler(Looper.getMainLooper());
handler.post(function() { Toast.makeText(context, "hello", Toast.LENGTH_LONG).show() });

importPackage(android.widget) 等于 java 中 import android.widget.*; ,JavaScript 中使用 var 定義變量,這段代碼就是創建了一個 handler 并調用 post 方法在 ui 線程彈一個 Toast。

在 Toast.makeText 中的 context 是從哪里來的呢?

context 是在 com.非死book.stetho.rhino.JsRuntimeReplFactoryBuilder 的 initJsScope 方法中被綁定到 JSContext 的,下面是 initJsScope 方法的源碼:

private @NonNull ScriptableObject initJsScope(@NonNull Context jsContext) {
    // Set the main Rhino goodies
    ImporterTopLevel importerTopLevel = new ImporterTopLevel(jsContext);
    ScriptableObject scope = jsContext.initStandardObjects(importerTopLevel, false);

ScriptableObject.putProperty(scope, "context", Context.javaToJS(mContext, scope));

try {
  importClasses(jsContext, scope);
  importPackages(jsContext, scope);
  importConsole(scope);
  importVariables(scope);
  importFunctions(scope);
} catch (StethoJsException e) {
  String message = String.format("%s\n%s", e.getMessage(), Log.getStackTraceString(e));
  LogUtil.e(e, message);
  CLog.writeToConsole(Console.MessageLevel.ERROR, Console.MessageSource.JAVASCRIPT, message);
}

return scope;

}</code></pre>

JsRuntimeReplFactoryBuilder 提供了一些方法可以傳遞自己的變量,類,包和函數到 JavaScript 環境。

添加變量,類,包和函數到 JavaScript 運行時

修改初始化代碼如下:

Stetho.initialize(Stetho.newInitializerBuilder(context)
        .enableWebKitInspector(new ExtInspectorModulesProvider(context))
        .build());
private static class ExtInspectorModulesProvider implements InspectorModulesProvider {

private Context mContext;
private final Handler handler = new Handler(Looper.getMainLooper());

ExtInspectorModulesProvider(Context context) {
  mContext = context;
}

@Override
public Iterable<ChromeDevtoolsDomain> get() {
  return new Stetho.DefaultInspectorModulesBuilder(mContext)
      .runtimeRepl(new JsRuntimeReplFactoryBuilder(mContext)
          // 添加變量
          .addVariable("test", new AtomicBoolean(true))
          // 添加類
          .importClass(R.class)
          // 添加包
          .importPackage(MyApplication.class.getPackage().getName()) 
          // 添加方法到 javascript: void toast(String)
          .addFunction("toast", new BaseFunction() {
            @Override
            public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {

              // javascript 傳遞的參數在 varags
              final String message = args[0].toString();
              handler.post(new Runnable() {
                @Override
                public void run() {
                  Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
                }
              });

              // 在 javascript 返回 undefined
              return org.mozilla.javascript.Context.getUndefinedValue();
            }
          })
          .build())
      .finish();
}

}</code></pre>

說明:Java原語類型將被自動裝箱,只有對象可以傳遞到 JavaScript 運行時。

綁定完成后就可以在 JavaScript Console 中使用自己的變量,類,包和函數了。

注意: Rhino 對包名的檢查是嚴格的 ,必須是 com.** , org.** , net.** 之類比較正規的格式。假如:包名使用 linchaolong.stetho.demo ,在 importClasses(linchaolong.stetho.demo.R) 時, ScriptRuntime 會報 EcmaError

Failed to import class: linchaolong.stetho.demo.R
com.非死book.stetho.rhino.JsRuntimeReplFactoryBuilder$StethoJsException: Failed to import class: linchaolong.stetho.demo.R
    at com.非死book.stetho.rhino.JsRuntimeReplFactoryBuilder.importClasses(JsRuntimeReplFactoryBuilder.java:195)
    at com.非死book.stetho.rhino.JsRuntimeReplFactoryBuilder.initJsScope(JsRuntimeReplFactoryBuilder.java:173)
    at com.非死book.stetho.rhino.JsRuntimeReplFactoryBuilder.initJsScope(JsRuntimeReplFactoryBuilder.java:158)
    at com.非死book.stetho.rhino.JsRuntimeReplFactoryBuilder.access$000(JsRuntimeReplFactoryBuilder.java:45)
    at com.非死book.stetho.rhino.JsRuntimeReplFactoryBuilder$1.newInstance(JsRuntimeReplFactoryBuilder.java:146)
    at com.非死book.stetho.inspector.protocol.module.Runtime$Session.getRepl(Runtime.java:271)
    at com.非死book.stetho.inspector.protocol.module.Runtime$Session.evaluate(Runtime.java:260)
    at com.非死book.stetho.inspector.protocol.module.Runtime.evaluate(Runtime.java:158)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.非死book.stetho.inspector.MethodDispatcher$MethodDispatchHelper.invoke(MethodDispatcher.java:96)
    at com.非死book.stetho.inspector.MethodDispatcher.dispatch(MethodDispatcher.java:67)
    at com.非死book.stetho.inspector.ChromeDevtoolsServer.handleRemoteRequest(ChromeDevtoolsServer.java:129)
    at com.非死book.stetho.inspector.ChromeDevtoolsServer.handleRemoteMessage(ChromeDevtoolsServer.java:111)
    at com.非死book.stetho.inspector.ChromeDevtoolsServer.onMessage(ChromeDevtoolsServer.java:87)
    at com.非死book.stetho.websocket.WebSocketSession$1.handleTextFrame(WebSocketSession.java:176)
    at com.非死book.stetho.websocket.WebSocketSession$1.onCompleteFrame(WebSocketSession.java:136)
    at com.非死book.stetho.websocket.ReadHandler.readLoop(ReadHandler.java:44)
    at com.非死book.stetho.websocket.WebSocketSession.handle(WebSocketSession.java:45)
    at com.非死book.stetho.websocket.WebSocketHandler.doUpgrade(WebSocketHandler.java:117)
    at com.非死book.stetho.websocket.WebSocketHandler.handleRequest(WebSocketHandler.java:83)
    at com.非死book.stetho.server.http.LightHttpServer.dispatchToHandler(LightHttpServer.java:84)
    at com.非死book.stetho.server.http.LightHttpServer.serve(LightHttpServer.java:61)
    at com.非死book.stetho.inspector.DevtoolsSocketHandler.onAccepted(DevtoolsSocketHandler.java:52)
    at com.非死book.stetho.server.ProtocolDetectingSocketHandler.onSecured(ProtocolDetectingSocketHandler.java:63)
    at com.非死book.stetho.server.SecureSocketHandler.onAccepted(SecureSocketHandler.java:33)
    at com.非死book.stetho.server.LazySocketHandler.onAccepted(LazySocketHandler.java:36)
    at com.非死book.stetho.server.LocalSocketServer$WorkerThread.run(LocalSocketServer.java:167)
Caused by: org.mozilla.javascript.EcmaError: ReferenceError: "linchaolong" is not defined. (chrome#1)
    at org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3949)
    at org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3927)
    at org.mozilla.javascript.ScriptRuntime.notFoundError(ScriptRuntime.java:4012)
    at org.mozilla.javascript.ScriptRuntime.name(ScriptRuntime.java:1849)
    at org.mozilla.javascript.Interpreter.interpretLoop(Interpreter.java:1558)
    at org.mozilla.javascript.Interpreter.interpret(Interpreter.java:815)
    at org.mozilla.javascript.InterpretedFunction.call(InterpretedFunction.java:109)
    at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:393)
    at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3280)
    at org.mozilla.javascript.InterpretedFunction.exec(InterpretedFunction.java:120)
    at org.mozilla.javascript.Context.evaluateString(Context.java:1191)
    at com.非死book.stetho.rhino.JsRuntimeReplFactoryBuilder.importClasses(JsRuntimeReplFactoryBuilder.java:193)
    ... 27 more

只在debug模式下使用 Stetho

修改 dependencies 配置,只在 debug 模式下編譯 stetho 和 stetho-js-rhino

dependencies {
  // Debug
  debugCompile "com.非死book.stetho:stetho:${stetho}"
  compile "com.非死book.stetho:stetho-okhttp3:${stetho}"
  debugCompile "com.非死book.stetho:stetho-js-rhino:${stetho}"
}

說明: ${stetho} 是 stetho 的版本號。

在 src/debug/java 目錄下新建一個 DebugApplication 繼承自 MyApplication ,并把初始化 Stetho 的代碼移到 DebugApplication

public class DebugApplication extends MyApplication{

@Override public void onCreate() { super.onCreate(); Stetho.initializeWithDefaults(this); }</code></pre>

在 src/debug 目錄下創建一個 AndroidManifest.xml ,并添加 debug 模式下需要的權限和修改 application 節點 android:name 值為 DebugApplication(使用 tools:replace 覆蓋 android:name 字段)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="

<uses-permission android:name="android.permission.READ_CALENDAR" />

<application tools:replace="android:name" android:name=".DebugApplication" />

</manifest></code></pre>

完成上面處理后,Stetho 只在 debug 版本下起作用,不影響 release 版本。

 

來自:http://www.jianshu.com/p/38d8324b126a

 

 本文由用戶 snvq8521 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!