開發(fā)者在開發(fā)中想查看安卓APP運行時的網(wǎng)絡(luò)訪問和數(shù)據(jù)存儲情況扬虚,調(diào)試太麻煩努隙,日志也挺煩,有沒有更好的辦法呢辜昵?Facebook給廣大開發(fā)者傳了福音荸镊,帶了福利,放在下午茶的小桌子上堪置,美食干貨不敢獨吞躬存,所以拿來分享給大家
從事移動端安卓APP的開發(fā),除了代碼邏輯之外就是在和數(shù)據(jù)打交道舀锨。數(shù)據(jù)的輸入輸出岭洲,往返于網(wǎng)絡(luò)接口之間,流竄于內(nèi)存之中存儲之內(nèi)坎匿,不能像編寫的代碼那樣直接在代碼編輯器中看到其具體的內(nèi)容盾剩。所以如果想窺探數(shù)據(jù)的真?zhèn)螌﹀e雷激,目前來說,不外三法告私。本文開始侥锦,告訴你第四條路。
現(xiàn)狀德挣,以及各自的問題
前面說傳統(tǒng)上有兩條路可以幫助開發(fā)者查看APP運行過程中處理的數(shù)據(jù)恭垦,這里簡單描述下處理方式以及每種方式的優(yōu)缺點。
- 斷點調(diào)試運行中的APP格嗅。你可以用調(diào)試器直接調(diào)試一個APP番挺,但如果這個APP過于龐大,初始化加載時間很久屯掖,那么玄柏,最好的調(diào)試辦法是先將APP在設(shè)備(手機或者模擬器)上運行起來,然后用attach to process的方式在被調(diào)試的APP進程上加載調(diào)試器贴铜,這樣會比一上來直接調(diào)試APP更快一些粪摘。想看數(shù)據(jù)的話,直接在相應(yīng)代碼行加上斷點绍坝,附著在APP進程上的調(diào)試器會自動斷下程序徘意,然后查看當前上下文中各種變量的值以及內(nèi)存的數(shù)據(jù),也可以修改這些數(shù)據(jù)轩褐。但是椎咧,如果你想看某個數(shù)據(jù)是不是真的寫到存儲的文件里,估計需要添加額外的讀取代碼來查看把介,而且勤讽,每次給APP掛調(diào)試器查看數(shù)據(jù),感覺還是有些不方便拗踢。如果你想診斷和分析網(wǎng)絡(luò)的訪問速度和數(shù)據(jù)的流量脚牍,數(shù)據(jù)存儲的空間和總體數(shù)據(jù)量,單靠調(diào)試這種手段顯得力不從心了巢墅。而且如果斷點的地方在UI的某處代碼诸狭,長時間處于斷點狀態(tài)查看數(shù)據(jù),會導(dǎo)致APP發(fā)生ANR的異常砂缩。
- 加打印日志作谚。類似產(chǎn)品運營的埋點和服務(wù)端訪問/操作日志,我們也可以在客戶端APP相應(yīng)的位置大書類似到此一游和此地無淫三百靚的句式庵芭,讓APP進程通過一個叫控制臺的老東西(console是計算機世界的老司機了妹懒,啥大風大浪沒見過的)告訴我們發(fā)生了啥,如何發(fā)生的双吆,以及發(fā)生的結(jié)果如何眨唬。斷點不好做到的網(wǎng)絡(luò)訪問速度和數(shù)據(jù)流量等東西也可以通過日志叫喚了会前。這么看起來,貌似加日志已經(jīng)是一種很完美的辦法了匾竿。但是瓦宜,你有沒有感覺到這樣超級麻煩?首先是你的代碼量突然變大了岭妖,代碼結(jié)構(gòu)變丑了临庇,代碼環(huán)境衛(wèi)生變差了,翠花上的酸菜我不敢吃了昵慌。相信我假夺,日志海(骷髏海的代碼態(tài))一定會讓你疲憊的雙眼猶如狂風暴雨里的一葉孤舟,說翻就翻斋攀,眼都不帶眨一下的已卷。說人話,日志是一種侵入式的調(diào)試手段淳蔼,啥叫侵入式侧蘸?就是它必須由您老人家親自動手埋藏在代碼的心房里,直到天荒地老,APP下架鹉梨,它也不會化作半點春泥更護花的讳癌。而對于調(diào)試來說,看日志的情調(diào)less than lower俯画,千篇飛過如同嚼蠟析桥。看過安卓日志的童鞋都知道艰垂,前塵往事并木有渺云煙,那些個天天在微信群里大呼小叫的群主來看看到底啥叫刷屏埋虹〔略鳎可憐的安卓開發(fā)們,天天被日志刷屏搔课。
- 借助第三方工具胰柑。對于網(wǎng)絡(luò)來說,基本就是設(shè)置代理爬泥,最常用的不外乎Charles (收費,基于Java開發(fā)柬讨,跨平臺);Fiddler(免費&收費,基于.Net開發(fā)袍啡,目前支持通過mono的方式運行在Mac和Linux上)踩官;Mitmproxy(免費&開源,基于Python開發(fā)境输,跨平臺)蔗牡;還有比較麻煩的辦法颖系,比如Http/Https代理+Wireshark/tcpdump這種。這些工具只能滿足網(wǎng)絡(luò)監(jiān)控辩越,對于非網(wǎng)絡(luò)數(shù)據(jù)就無能為力了嘁扼。對于存儲在手機上的數(shù)據(jù),可以通過adb登陸到手機黔攒,獲得root權(quán)限后查看APP內(nèi)部數(shù)據(jù)趁啸,也可以采用一些安裝在手機端的帶圖形界面的APP來查看和修改數(shù)據(jù),比如SQLEditor之類的督惰,這類APP同樣需要獲取root權(quán)限莲绰。
那么,后來姑丑,Facebook給我們這些可憐的娃帶來了福音和福利蛤签,試試看咯
聽診器來了
Stetho英譯為“聽診”,是Facebook研發(fā)的安卓APP網(wǎng)絡(luò)診斷和數(shù)據(jù)監(jiān)控的框架栅哀,目前已經(jīng)開放源代碼震肮,開發(fā)者接入Stetho框架提供的SDK到APP中,這樣就可以通過安裝在開發(fā)機(PC/MAC留拾,Windows/OS X/Linux)上安裝的谷歌的Chrome開發(fā)者工具(通過Chrome瀏覽器使用)來查看戳晌,診斷和分析APP中發(fā)生的網(wǎng)絡(luò)請求和響應(yīng)以及數(shù)據(jù)內(nèi)容,就像用Chrome調(diào)試網(wǎng)站一樣調(diào)試APP程序痴柔。當然沦偎,幾乎任何工具都自帶老司機console,Stetho也不例外咳蔚,它提供了一個叫做dumpapp的工具豪嚎,可以向你傾述更多的APP內(nèi)心世界。
接入其實很簡單
再簡單的接入也總有1234步谈火,這里簡單叨逼叨逼幾句
gradle配置
這里不說mvn和low逼的下載&拷貝庫的方式了(拷貝源代碼的方式集成就更不能忍了)侈询,直接上gradle配置
// Gradle dependency on Stetho
dependencies {
compile 'com.facebook.stetho:stetho:1.3.1'
}
如果你使用了Okhttp 3.x的網(wǎng)絡(luò)棧,請集成如下網(wǎng)絡(luò)工具庫
dependencies {
compile 'com.facebook.stetho:stetho-okhttp3:1.3.1'
}
Okhttp 2.2.x+
dependencies {
compile 'com.facebook.stetho:stetho-okhttp:1.3.1'
}
如果使用的是HttpURLConnection
dependencies {
compile 'com.facebook.stetho:stetho-urlconnection:1.3.1'
}
小白兔和大灰狼請注意:
- 如果你使用的是Apache HttpClient糯耍,對不起扔字,你out了,請自行升級網(wǎng)絡(luò)棧温技,當然你也可以在了解了Stetho的玩法之后自己寫一套網(wǎng)絡(luò)監(jiān)控來適配Apache HttpClient革为。
- 如果你使用的網(wǎng)絡(luò)棧不在上面列舉的里面,或者你用c/c++寫的網(wǎng)絡(luò)操作舵鳞,又或者你采用的協(xié)議不是http/https的震檩,那么,網(wǎng)絡(luò)這部分的診斷和監(jiān)控方法系任,估計是很難用了恳蹲。還想用虐块,自己寫咯。
初始化
需要寫的代碼
其實很少嘉蕾,而且?guī)缀跛械膽?yīng)用都是一樣的代碼贺奠,首先是在Application類中初始化
public class MyApplication extends Application {
public void onCreate() {
super.onCreate();
Stetho.initializeWithDefaults(this);
}
}
這個初始化會開啟大部分的聽診模塊,但是網(wǎng)絡(luò)監(jiān)控等一些附加的鉤子模塊除外
網(wǎng)絡(luò)診斷
如果你使用的網(wǎng)絡(luò)棧是OkHttp错忱,而且版本區(qū)間在2.2.x+到3.x儡率,那么想要打開網(wǎng)絡(luò)診斷模塊,只需要在程序合適的位置調(diào)用如下代碼即可
OkHttp 2.2.x+
OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(new StethoInterceptor());
OkHttp 3.x
new OkHttpClient.Builder()
.addNetworkInterceptor(new StethoInterceptor())
.build();
HttpURLConnection
如果你使用的HttpURLConnection,稍微有些麻煩的說以清,你可以使用Stetho框架SDK提供的類StethoURLConnectionManager來完成客戶端網(wǎng)絡(luò)診斷的開啟儿普,但是有一些坑是要注意的。
比如為了讓Stetho向開發(fā)機上的Chrome匯報正確的經(jīng)過壓縮的有效載荷的大小掷倔,你需要親自在http/https的請求頭加上"Accept-Encoding: gzip"眉孩,并且自己處理壓縮過的響應(yīng)數(shù)據(jù)。如果采用Okhttp勒葱,這些都不必勞煩您老人家操心了浪汪,框架默認幫你考慮了。
參考代碼如下:
private final StethoURLConnectionManager stethoManager;
private static final int READ_TIMEOUT_MS = 10000;
private static final int CONNECT_TIMEOUT_MS = 15000;
private static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
private static final String GZIP_ENCODING = "gzip";
private String url = "http://www.figotan.org";
stethoManager = new StethoURLConnectionManager(url);
URL url = new URL(url);
// Note that this does not actually create a new connection so it is appropriate to
// defer preConnect until after the HttpURLConnection instance is configured. Do not
// invoke connect, conn.getInputStream, conn.getOutputStream, etc before calling
// preConnect!
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
try {
conn.setReadTimeout(READ_TIMEOUT_MS);
conn.setConnectTimeout(CONNECT_TIMEOUT_MS);
conn.setRequestMethod(request.method.toString());
// Adding this disables transparent gzip compression so that we can intercept
// the raw stream and display the correct response body size.
conn.setRequestProperty(HEADER_ACCEPT_ENCODING, GZIP_ENCODING);
SimpleRequestEntity requestEntity = null;
if (request.body != null) {
requestEntity = new ByteArrayRequestEntity(request.body);
}
stethoManager.preConnect(conn, requestEntity);
try {
if (request.method == HttpMethod.POST) {
if (requestEntity == null) {
throw new IllegalStateException("POST requires an entity");
}
conn.setDoOutput(true);
requestEntity.writeTo(conn.getOutputStream());
}
// Ensure that we are connected after this point. Note that getOutputStream above will
// also connect and exchange HTTP messages.
conn.connect();
stethoManager.postConnect();
} catch (IOException inner) {
// This must only be called after preConnect. Failures before that cannot be
// represented since the request has not yet begun according to Stetho.
stethoManager.httpExchangeFailed(inner);
throw inner;
}
} catch (IOException outer) {
conn.disconnect();
throw outer;
}
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream rawStream = conn.getInputStream();
try {
// Let Stetho see the raw, possibly compressed stream.
rawStream = stethoManager.interpretResponseStream(rawStream);
if (rawStream != null && GZIP_ENCODING.equals(conn.getContentEncoding())) {
decompressedStream = new GZIPInputStream(in);
} else {
decompressedStream = rawStream;
}
if (decompressedStream != null) {
int n;
byte[] buf = new byte[1024];
while ((n = decompressedStream.read(buf)) != -1) {
out.write(buf, 0, n);
}
}
} finally {
if (rawStream != null) {
rawStream.close();
}
}
} finally {
conn.disconnect();
}
通過如上步驟凛虽,已經(jīng)可以讓你的APP支持網(wǎng)絡(luò)監(jiān)控死遭,數(shù)據(jù)庫監(jiān)控和SharedPreferences文件內(nèi)容監(jiān)控了。如果想玩更高級的凯旋,請看后面的自定義dumpapp插件和Rhino呀潭,如果想直接玩起來,請繼續(xù)往下看至非。
使用起來也不麻煩
使用步驟如下:
首先在手機上運行APP
確保手機USB連接開發(fā)機钠署,在開發(fā)機上打開Chrome瀏覽器
-
在Chrome瀏覽器地址欄中輸入chrome://inspect,會看到如下這張圖,如果圖里面沒有你的APP睡蟋,請返回到上面檢查代碼接入是否正確
-
點擊APP旁邊的inspect連接踏幻,這個時候會彈出一個窗口,如果你用過Chrome的開發(fā)者工具戳杀,是不是會覺得這個界面很熟悉?對了夭苗,這個窗口就是Chrome內(nèi)置的開發(fā)者工具信卡,只不過里面監(jiān)控的內(nèi)容從網(wǎng)頁變成了APP
-
首先看功能導(dǎo)航條的第一個tab,叫做"Elements",工作區(qū)中的內(nèi)容是不是很熟悉题造,Hierarchy Viewer傍菇,很像吧,點擊具體的xml節(jié)點界赔,可以看到連接的手機上對應(yīng)的UI控件高亮顯示了丢习,這個可以像Hierarchy Viewer那樣分析APP頁面的嵌套層級
-
第二個tab叫做"Network",是用來做網(wǎng)絡(luò)監(jiān)控的牵触,基本上覆蓋了Chrome開發(fā)者工具中"Network inspection"的所有功能點,包括下載圖片的預(yù)覽咐低,JSON數(shù)據(jù)查看揽思,網(wǎng)絡(luò)請求內(nèi)容和返回內(nèi)容
-
第三個tab是"Sources",用來查看網(wǎng)頁的詳細內(nèi)容
-
直接跳過"Timeline", "Profiles"看第六個tab见擦,"Resources"钉汗,顧名思義,這里應(yīng)該就是查看APP內(nèi)部產(chǎn)生數(shù)據(jù)的地方啦鲤屡,目前支持的數(shù)據(jù)有兩種损痰,一種是數(shù)據(jù)庫(ContentProvider和Sql的方式)的數(shù)據(jù),另一種是SharedPreferences數(shù)據(jù)
"Audits" 跳過酒来,如同"Timeline"和"Profiles"卢未,目前沒怎么支持,有待進一步發(fā)掘的功能堰汉。
"Console"老司機下面講
有坑嗎辽社?
關(guān)于網(wǎng)絡(luò)監(jiān)控的有一些需要注意的字段的含義,詳細的內(nèi)容可以去精讀一遍Chrome開發(fā)者工具官方文檔
這里只詳細解釋下上面圖中個字段的含義衡奥。
- Name/Path 網(wǎng)絡(luò)資源的名稱和URL路徑爹袁,比如http://www.figotan.org/c/v/logo.jpg這個網(wǎng)絡(luò)資源,Name是logo.jpg Path是www.figotan.org/c/v/
- Method HTTP協(xié)議規(guī)定的請求方法矮固,比如GET POST
- Status/Text HTTP協(xié)議規(guī)定的返回碼和這個返回碼對應(yīng)的含義解釋文字 失息,比如200/OK
- Type 請求資源的MIME類型,比如application/json image/jpeg image/png等等
- Initiator 發(fā)起請求的對象档址,可以是Parser/Redirect/Script/Other,詳見上面的官方文檔
- Size/Content Size表示HTTP響應(yīng)的頭和數(shù)據(jù)體的和盹兢,由遠程服務(wù)端返回;Content是返回的資源解碼后的大小
- Time/Latentcy Time是總的時間間隔守伸,從發(fā)起請求開始到接收到服務(wù)端返回的最后一個字節(jié)為止绎秒;Latency是指的接收到服務(wù)端返回的第一個字節(jié)消耗的時間
- Timeline 顯示了所有網(wǎng)絡(luò)請求的瀑布流
還可以做什么
除了監(jiān)控網(wǎng)絡(luò),查看/修改數(shù)據(jù)之外尼摹,還可以做很多事情见芹,因為Stetho預(yù)留了兩種接口,為了可持續(xù)的發(fā)展
自定義dumpapp插件
自定義插件是讓老司機dumpapp get新技能的首選方法蠢涝,可以很容易地在配置過程中添加玄呛。只需如下代碼即可添加自定義插件:
Stetho.initialize(Stetho.newInitializerBuilder(context)
.enableDumpapp(new DumperPluginsProvider() {
@Override
public Iterable<DumperPlugin> get() {
return new Stetho.DefaultDumperPluginsBuilder(context)
.provide(new HelloWorldDumperPlugin())
.provide(new APODDumperPlugin(context.getContentResolver()))
.finish();
}
})
.enableWebKitInspector(new ExtInspectorModulesProvider(context))
.build());
其中HelloWorldDumperPlugin和APODDumperPlugin是自定義的插件,具體內(nèi)容可以參考Stetho提供的sample程序
執(zhí)行dumpapp命令需要先從git取下最新的代碼和二,然后找到dumpapp腳本徘铝,并執(zhí)行
$ git clone https://github.com/facebook/stetho.git
$ cd stetho
// 列舉出支持的命令(插件)
$ ./scripts/dumpapp -p com.facebook.stetho.sample -l
參照sample代碼編寫dumpapp插件,然后用dumpapp命令驗證插件的效果
Stetho對于JavaScript的支持
目前Stetho對于JavaScript腳本的支持是采用內(nèi)嵌Mozilla研發(fā)的Rhino
第一種采用dumpapp的插件擴展方式雖然功能強大,無所不能惕它,但是完成一件事情需要一定的技術(shù)和時間成本怕午,必須經(jīng)歷一系列的編寫,編譯淹魄,構(gòu)建郁惜,安裝,調(diào)試揭北,修改代碼扳炬,再下一個輪回,迭代幾次后才能形成產(chǎn)出搔体,這其實是類c/c++/java這種非動態(tài)語言的一個缺陷恨樟,軟件的研發(fā)周期太長。那么疚俱,如果有一種寫完即發(fā)布的腳本語言能夠支持起來劝术,其實對于研發(fā)效率來說,是有很大提升的呆奕,比如lua/javascript/perl/python/groovy等等养晋,這樣的語言輕巧,無需編譯梁钾,寫完就可以發(fā)布驗證绳泉,甚至可以邊寫邊調(diào)試邊上線。
Chrome開發(fā)者工具原生支持JavaScript姆泻,所以Stetho也提供了JavaScript的支持零酪。
Rhino是一個可以運行在Java程序內(nèi)部的JavaScript實現(xiàn),由Mozilla開發(fā)并發(fā)布為一個開源的項目
下面說說集成和使用方式
如果要讓APP支持Rhino, 首先是gradle配置
dependencies {
compile 'com.facebook.stetho:stetho-js-rhino:1.3.1'
}
然后就可以通過開發(fā)機上Chrome瀏覽器提供的開發(fā)者工具的"Console"老司機(任何工具都有老司機console)來發(fā)射JavaScript代碼了拇勃,參考代碼如下:
importPackage(android.widget);
importPackage(android.os);
var handler = new Handler(Looper.getMainLooper());
handler.post(function() { Toast.makeText(context, "Hello from JavaScript", Toast.LENGTH_LONG).show() });
運行效果如下:
如果你想通過APP傳遞一些變量四苇,類,閉包和函數(shù)給JavaScript運行環(huán)境中方咆,那么你可以在Stetho初始化的時候添加如下代碼:
Stetho.initialize(Stetho.newInitializerBuilder(context)
.enableWebKitInspector(new InspectorModulesProvider() {
@Override
public Iterable<ChromeDevtoolsDomain> get() {
return new DefaultInspectorModulesBuilder(context).runtimeRepl(
new JsRuntimeReplFactoryBuilder(context)
// Pass to JavaScript: var foo = "bar";
.addVariable("foo", "bar")
.build()
).finish();
}
})
.build());
更多玩法請移步Rhino on Stetho
參考資料
Stetho官方文檔
Stetho源代碼
Stetho: A new debugging platform for Android
A First Glance at Stetho tool
Remote Debugging on Android with Chrome
Chrome DevTools Overview
想要知道的更多月腋,那就來我的博客看看吧!