What is Stetho ?
Stetho 是一個功能強(qiáng)大的 Android 應(yīng)用調(diào)試橋,起到橋梁的作用,連接 Android 應(yīng)用和 Chrome,通過 Chrome 開發(fā)者工具調(diào)試 Android 應(yīng)用,提供視圖元素檢查,網(wǎng)絡(luò)監(jiān)控,數(shù)據(jù)庫動態(tài)交互,Dumpapp(可擴(kuò)展的命令行交互接口),JavaScript Console 等功能。
當(dāng)啟用后,開發(fā)者可以通過 Chrome 桌面瀏覽器中的開發(fā)者工具訪問本地應(yīng)用。開發(fā)者也可以選擇啟用可選的 dumpapp 工具提供一個強(qiáng)大的應(yīng)用內(nèi)部命令行接口。
官網(wǎng):http://facebook.github.io/stetho/
項(xiàng)目地址:https://github.com/facebook/stetho
一旦你完成了下面的設(shè)置說明,只要啟動你電腦上的 Chrome 瀏覽器并輸入chrome://inspect點(diǎn)擊 "Inspect" 按鈕即可開始調(diào)試。
注意:如果你點(diǎn)擊 "Inspect" 出現(xiàn)的是一個空白頁面,請嘗試下面的解決方案:
可能是 Chrome 版本過低,嘗試升級 Chrome。
先翻個墻再打開調(diào)試界面(第一次需要這樣,后面不需要)。
配置說明
添加 stetho 主依賴
在 Gradle 中包含 stetho
// Gradle dependency on Stetho
dependencies {
compile 'com.facebook.stetho:stetho:1.4.1'
}
在 Maven 中包含 stetho
com.facebook.stetho
stetho
1.4.1
只有 stetho 主依賴是必須的,但你可能還希望有一個網(wǎng)絡(luò)助手
dependencies {
compile 'com.facebook.stetho:stetho-okhttp3:1.4.1'
}
或者:
dependencies {
compile 'com.facebook.stetho:stetho-okhttp:1.4.1'
}
或者:
dependencies {
compile 'com.facebook.stetho:stetho-urlconnection:1.4.1'
}
功能說明
Chrome DevTools

image
Stetho 為你的應(yīng)用提供了 C/S 協(xié)議實(shí)現(xiàn),所以你可以通過 Chrome 集成的前端開發(fā)工具訪問你的應(yīng)用。只要你的應(yīng)用集成了 Stetho,只需導(dǎo)航到chrome://inspect并點(diǎn)擊 "Inspect" 即可開始使用。
Network Inspection

image
使用 Chrome 開發(fā)者工具各種功能實(shí)現(xiàn)網(wǎng)絡(luò)監(jiān)控,包括圖片預(yù)覽,JSON 響應(yīng)輔助工具,甚至把跟蹤信息導(dǎo)出為 HAR 格式文件。
Database Inspection

image
SQLite 數(shù)據(jù)庫可視化與交互,具備完全讀寫功能,
Web SQL 下的是應(yīng)用的數(shù)據(jù)庫,點(diǎn)擊數(shù)據(jù)庫可以輸入 SQL 語句對其進(jìn)行操作
Local Storage 就是對應(yīng) Android 下的 SharedPreferences,可修改 SharedPreferences 中的值
View Hierarchy

image
View Hierarchy 支持 API 15 或更高版本。使用 View Hierarchy 可以很方便的檢查運(yùn)行時界面的層次結(jié)構(gòu)與數(shù)據(jù),比如:
View Hierarchy 中包含界面所有元素的層次結(jié)構(gòu)和屬性
鼠標(biāo)移動到 View Hierarchy 中某個 View,app 中對應(yīng)的 View 會高亮顯示
點(diǎn)擊 View Hierarchy 左上角的搜索按鈕,再點(diǎn)擊 app 當(dāng)前界面的控件,View Hierarchy 會顯示該控件在層次中的位置
dumpapp

image
Dumpapp 為應(yīng)用提供了一個可擴(kuò)展的命令行交互接口,提供了一組默認(rèn)的插件,但是 dumpapp 的真正強(qiáng)大之處在于能夠輕松創(chuàng)建自己的插件!
dumpapp 就在工程的 scripts/dumpapp 下,遺憾的是目前在 Windows 下還用不了,因?yàn)樗惶峁┝?Linux/Mac 下的執(zhí)行腳本。
常用命令(插件):
列出所有 Plugin :./scripts/dumpapp -p com.facebook.stetho.sample -l
打印 SharedPreferences :./scripts/dumpapp prefs print
寫 SharedPreferences :./scripts/dumpapp prefs write
dumpapp 默認(rèn)提供的插件就在com.facebook.stetho.dumpapp.plugins.*,具體使用方法可以參考源碼中的說明。
JavaScript Console

image
JavaScript Console 允許執(zhí)行那些可以與應(yīng)用或 Android SDK 交互的 JavaScript 代碼。
Stetho 使用Rhino實(shí)現(xiàn)使用腳本方式調(diào)用 Java。
Rhino 是一個完全使用Java語言編寫的開源JavaScript實(shí)現(xiàn)。Rhino通常用于在Java程序中,為最終用戶提供腳本化能力。它被作為J2SE 6上的默認(rèn)Java腳本化引擎。
Github:https://github.com/mozilla/rhino
官網(wǎng):https://www.mozilla.org/rhino/
集成說明
1. 初始化
在你的 Application 初始化時候調(diào)用 Stetho 的初始化方法:
public class MyApplication extends Application {
public void onCreate() {
super.onCreate();
Stetho.initializeWithDefaults(this);
}
}
這將啟用大多數(shù)默認(rèn)配置,但不啟用一些額外的鉤子(啟用網(wǎng)絡(luò)監(jiān)控需要注意)。 有關(guān)各個子系統(tǒng)的具體細(xì)節(jié),請參見下文。
2. 啟用網(wǎng)絡(luò)監(jiān)控
如果你使用的是 2.2.x+ 或 3.x 版本的 OkHttp 庫,可以使用攔截器系統(tǒng)自動掛接到現(xiàn)有堆棧。 這是目前啟用網(wǎng)絡(luò)監(jiān)控的最簡單和最直接的方法。
For OkHttp 2.x
OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(new StethoInterceptor());
For OkHttp 3.x
new OkHttpClient.Builder()
.addNetworkInterceptor(new StethoInterceptor())
.build();
由于攔截器可以修改請求和響應(yīng),應(yīng)該在其他攔截器之后添加 Stetho 攔截器以獲取準(zhǔn)確的網(wǎng)絡(luò)交互視圖。
如果你使用HttpURLConnection,可以使用StethoURLConnectionManager來幫助集成,但該方法有一些注意事項(xiàng),比如你必須明確地添加Accept-Encoding:gzip到請求頭,并手動處理壓縮的響應(yīng),以便 Stetho 報告壓縮的有效負(fù)載大小。具體可以參考stetho-sample中的Networker的實(shí)現(xiàn)。
Stetho 目前沒有提供 HttpClient 網(wǎng)絡(luò)監(jiān)控支持,具體原因可以查看issues 116(HttpClient 在 Android5.0 已經(jīng)被廢棄,不建議再使用)。
OkHttp + Retrofit
一般開發(fā)中我們都會使用OkHttp + Retrofit,OkHttp用于 HTTP 網(wǎng)絡(luò)交互,Retrofit用于將 HTTP API 轉(zhuǎn)換為 Java 接口。
默認(rèn)情況下,Retrofit會自己創(chuàng)建一個OkHttpClient,我們也可以在創(chuàng)建Retrofit的時候通過client(OkHttpClient client)方法提供一個OkHttpClient。
sRetrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(sClient)
.build();
通過client(OkHttpClient client)方法設(shè)置共用一個OkHttpClient,監(jiān)控Retrofit中的網(wǎng)絡(luò)交互。
更多細(xì)節(jié)見stetho-sample項(xiàng)目。
3. 自定義 dumpapp 插件
自定義插件主要是實(shí)現(xiàn) DumperPlugin 接口中的String getName()和void dump(DumperContext dumpContext)方法。getName()方法返回插件的名稱,dump(DumperContext dumpContext)是命令行中調(diào)用該插件時的回調(diào)方法。其中,dumpContext.getStdout()獲取命令行輸出,dumpContext.getArgsAsList()獲取命令行調(diào)用的參數(shù)列表。
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 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);
}
}
// 省略部分代碼
}
然后把初始化調(diào)用替換如下:
Stetho.initialize(Stetho.newInitializerBuilder(context)
.enableDumpapp(new DumperPluginsProvider() {
@Override
public Iterable get() {
return new Stetho.DefaultDumperPluginsBuilder(context)
.provide(new MyDumperPlugin())
.finish();
}
})
.enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context))
.build());
更多細(xì)節(jié)見stetho-sample項(xiàng)目。
4. 啟用JavaScript Console
啟用 JavaScript Console 只需在 build.gradle 中添加如下依賴即可:
compile "com.facebook.stetho:stetho-js-rhino:1.4.1"
啟動 app,在 Chrome 開發(fā)者工具的 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() });

Paste_Image.png
importPackage(android.widget)等于 java 中import android.widget.*;,JavaScript 中使用 var 定義變量,這段代碼就是創(chuàng)建了一個 handler 并調(diào)用 post 方法在 ui 線程彈一個 Toast。
關(guān)于 Rhino 相關(guān)語法可以參考下面的文檔
在Toast.makeText中的 context 是從哪里來的呢?
context 是在com.facebook.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;
}
JsRuntimeReplFactoryBuilder提供了一些方法可以傳遞自己的變量,類,包和函數(shù)到 JavaScript 環(huán)境。
添加變量,類,包和函數(shù)到 JavaScript 運(yùn)行時
修改初始化代碼如下:
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 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 傳遞的參數(shù)在 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();
}
}
說明:Java原語類型將被自動裝箱,只有對象可以傳遞到 JavaScript 運(yùn)行時。
綁定完成后就可以在 JavaScript Console 中使用自己的變量,類,包和函數(shù)了。

Paste_Image.png
注意:Rhino 對包名的檢查是嚴(yán)格的,必須是com.**,org.**,net.**之類比較正規(guī)的格式。假如:包名使用linchaolong.stetho.demo,在importClasses(linchaolong.stetho.demo.R)時,ScriptRuntime會報EcmaError
Failed to import class: linchaolong.stetho.demo.R
com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder$StethoJsException: Failed to import class: linchaolong.stetho.demo.R
at com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder.importClasses(JsRuntimeReplFactoryBuilder.java:195)
at com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder.initJsScope(JsRuntimeReplFactoryBuilder.java:173)
at com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder.initJsScope(JsRuntimeReplFactoryBuilder.java:158)
at com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder.access$000(JsRuntimeReplFactoryBuilder.java:45)
at com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder$1.newInstance(JsRuntimeReplFactoryBuilder.java:146)
at com.facebook.stetho.inspector.protocol.module.Runtime$Session.getRepl(Runtime.java:271)
at com.facebook.stetho.inspector.protocol.module.Runtime$Session.evaluate(Runtime.java:260)
at com.facebook.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.facebook.stetho.inspector.MethodDispatcher$MethodDispatchHelper.invoke(MethodDispatcher.java:96)
at com.facebook.stetho.inspector.MethodDispatcher.dispatch(MethodDispatcher.java:67)
at com.facebook.stetho.inspector.ChromeDevtoolsServer.handleRemoteRequest(ChromeDevtoolsServer.java:129)
at com.facebook.stetho.inspector.ChromeDevtoolsServer.handleRemoteMessage(ChromeDevtoolsServer.java:111)
at com.facebook.stetho.inspector.ChromeDevtoolsServer.onMessage(ChromeDevtoolsServer.java:87)
at com.facebook.stetho.websocket.WebSocketSession$1.handleTextFrame(WebSocketSession.java:176)
at com.facebook.stetho.websocket.WebSocketSession$1.onCompleteFrame(WebSocketSession.java:136)
at com.facebook.stetho.websocket.ReadHandler.readLoop(ReadHandler.java:44)
at com.facebook.stetho.websocket.WebSocketSession.handle(WebSocketSession.java:45)
at com.facebook.stetho.websocket.WebSocketHandler.doUpgrade(WebSocketHandler.java:117)
at com.facebook.stetho.websocket.WebSocketHandler.handleRequest(WebSocketHandler.java:83)
at com.facebook.stetho.server.http.LightHttpServer.dispatchToHandler(LightHttpServer.java:84)
at com.facebook.stetho.server.http.LightHttpServer.serve(LightHttpServer.java:61)
at com.facebook.stetho.inspector.DevtoolsSocketHandler.onAccepted(DevtoolsSocketHandler.java:52)
at com.facebook.stetho.server.ProtocolDetectingSocketHandler.onSecured(ProtocolDetectingSocketHandler.java:63)
at com.facebook.stetho.server.SecureSocketHandler.onAccepted(SecureSocketHandler.java:33)
at com.facebook.stetho.server.LazySocketHandler.onAccepted(LazySocketHandler.java:36)
at com.facebook.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.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder.importClasses(JsRuntimeReplFactoryBuilder.java:193)
... 27 more
更多細(xì)節(jié)請參考stetho-js-rhino項(xiàng)目中的README.md
只在debug模式下使用 Stetho
修改 dependencies 配置,只在 debug 模式下編譯stetho和stetho-js-rhino
dependencies {
// Debug
debugCompile "com.facebook.stetho:stetho:${stetho}"
compile "com.facebook.stetho:stetho-okhttp3:${stetho}"
debugCompile "com.facebook.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);
}
在src/debug目錄下創(chuàng)建一個AndroidManifest.xml,并添加 debug 模式下需要的權(quán)限和修改 application 節(jié)點(diǎn)android:name值為 DebugApplication(使用tools:replace覆蓋android:name字段)
xmlns:tools="http://schemas.android.com/tools"
package="com.facebook.stetho.sample">
tools:replace="android:name"
android:name=".DebugApplication" />

Paste_Image.png
完成上面處理后,Stetho 只在 debug 版本下起作用,不影響 release 版本。
作者:linchaolong
鏈接:http://m.itdecent.cn/p/38d8324b126a
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。