Stetho,一個功能強(qiáng)大的 Android 應(yīng)用調(diào)試橋

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)語法可以參考下面的文檔

Scripting Java

Performance Hints

在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)載請注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容