开发者在开发中想查看安卓APP运行时的网络访问和数据存储状况,调试太麻烦,日志也挺烦,有没有更好的办法呢?Facebook给广大开发者传了福音,带了福利,放在下午茶的小桌子上,美食干货不敢独吞,因此拿来分享给你们javascript
从事移动端安卓APP的开发,除了代码逻辑以外就是在和数据打交道。数据的输入输出,往返于网络接口之间,流窜于内存之中存储以内,不能像编写的代码那样直接在代码编辑器中看到其具体的内容。因此若是想窥探数据的真伪对错,目前来讲,不外三法。本文开始,告诉你第四条路。html
前面说传统上有两条路能够帮助开发者查看APP运行过程当中处理的数据,这里简单描述下处理方式以及每种方式的优缺点。java
断点调试运行中的APP。你能够用调试器直接调试一个APP,但若是这个APP过于庞大,初始化加载时间好久,那么,最好的调试办法是先将APP在设备(手机或者模拟器)上运行起来,而后用attach to process的方式在被调试的APP进程上加载调试器,这样会比一上来直接调试APP更快一些。想看数据的话,直接在相应代码行加上断点,附着在APP进程上的调试器会自动断下程序,而后查看当前上下文中各类变量的值以及内存的数据,也能够修改这些数据。可是,若是你想看某个数据是否是真的写到存储的文件里,估计须要添加额外的读取代码来查看,并且,每次给APP挂调试器查看数据,感受仍是有些不方便。若是你想诊断和分析网络的访问速度和数据的流量,数据存储的空间和整体数据量,单靠调试这种手段显得力不从心了。并且若是断点的地方在UI的某处代码,长时间处于断点状态查看数据,会致使APP发生ANR的异常。python
加打印日志。相似产品运营的埋点和服务端访问/操做日志,咱们也能够在客户端APP相应的位置大书相似到此一游和此地无淫三百靓的句式,让APP进程经过一个叫控制台的老东西(console是计算机世界的老司机了,啥大风大浪没见过的)告诉咱们发生了啥,如何发生的,以及发生的结果如何。断点很差作到的网络访问速度和数据流量等东西也能够经过日志叫唤了。这么看起来,貌似加日志已是一种很完美的办法了。可是,你有没有感受到这样超级麻烦?首先是你的代码量忽然变大了,代码结构变丑了,代码环境卫生变差了,翠花上的酸菜我不敢吃了。相信我,日志海(骷髅海的代码态)必定会让你疲惫的双眼犹如暴风骤雨里的一叶孤舟,说翻就翻,眼都不带眨一下的。说人话,日志是一种侵入式的调试手段,啥叫侵入式?就是它必须由您老人家亲自动手埋藏在代码的心房里,直到天荒地老,APP下架,它也不会化做半点春泥更护花的。而对于调试来讲,看日志的情调less than lower,千篇飞过如同嚼蜡。看过安卓日志的童鞋都知道,前尘往事并木有渺云烟,那些个每天在微信群里大呼小叫的群主来看看到底啥叫刷屏。可怜的安卓开发们,每天被日志刷屏。android
借助第三方工具。对于网络来讲,基本就是设置代理,最经常使用的不外乎Charles (收费,基于Java开发,跨平台);Fiddler(免费&收费,基于.Net开发,目前支持经过mono的方式运行在Mac和Linux上);Mitmproxy(免费&开源,基于Python开发,跨平台);还有比较麻烦的办法,好比Http/Https代理+Wireshark/tcpdump这种。这些工具只能知足网络监控,对于非网络数据就无能为力了。对于存储在手机上的数据,能够经过adb登录到手机,得到root权限后查看APP内部数据,也能够采用一些安装在手机端的带图形界面的APP来查看和修改数据,好比SQLEditor之类的,这类APP一样须要获取root权限。c++
那么,后来,Facebook给咱们这些可怜的娃带来了福音和福利,试试看咯git
Stetho英译为“听诊”,是Facebook研发的安卓APP网络诊断和数据监控的框架,目前已经开放源代码,开发者接入Stetho框架提供的SDK到APP中,这样就能够经过安装在开发机(PC/MAC,Windows/OS X/Linux)上安装的谷歌的Chrome开发者工具(经过Chrome浏览器使用)来查看,诊断和分析APP中发生的网络请求和响应以及数据内容,就像用Chrome调试网站同样调试APP程序。固然,几乎任何工具都自带老司机console,Stetho也不例外,它提供了一个叫作dumpapp的工具,能够向你倾述更多的APP心里世界。github
再简单的接入也总有1234步,这里简单叨逼叨逼几句。chrome
这里不说mvn和low逼的下载&拷贝库的方式了(拷贝源代码的方式集成就更不能忍了),直接上gradle配置数据库
// Gradle dependency on Stetho dependencies { compile 'com.facebook.stetho:stetho:1.3.1' }
若是你使用了Okhttp 3.x的网络栈,请集成以下网络工具库
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了,请自行升级网络栈,固然你也能够在了解了Stetho的玩法以后本身写一套网络监控来适配Apache HttpClient。
若是你使用的网络栈不在上面列举的里面,或者你用c/c++写的网络操做,又或者你采用的协议不是http/https的,那么,网络这部分的诊断和监控方法,估计是很难用了。还想用,本身写咯。
须要写的代码其实不多,并且几乎全部的应用都是同样的代码,首先是在Application类中初始化
public class MyApplication extends Application { public void onCreate() { super.onCreate(); Stetho.initializeWithDefaults(this); } }
这个初始化会开启大部分的听诊模块,可是网络监控等一些附加的钩子模块除外
若是你使用的网络栈是OkHttp,并且版本区间在2.2.x+到3.x,那么想要打开网络诊断模块,只须要在程序合适的位置调用以下代码便可
OkHttp 2.2.x+
OkHttpClient client = new OkHttpClient(); client.networkInterceptors().add(new StethoInterceptor());
OkHttp 3.x
new OkHttpClient.Builder() .addNetworkInterceptor(new StethoInterceptor()) .build();
若是你使用的HttpURLConnection,稍微有些麻烦的说,你可使用Stetho框架SDK提供的类StethoURLConnectionManager来完成客户端网络诊断的开启,可是有一些坑是要注意的。
好比为了让Stetho向开发机上的Chrome汇报正确的通过压缩的有效载荷的大小,你须要亲自在http/https的请求头加上"Accept-Encoding: gzip",而且本身处理压缩过的响应数据。若是采用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(); }
经过如上步骤,已经可让你的APP支持网络监控,数据库监控和SharedPreferences文件内容监控了。若是想玩更高级的,请看后面的自定义dumpapp插件和Rhino,若是想直接玩起来,请继续往下看。
使用步骤以下:
首先在手机上运行APP
确保手机USB链接开发机,在开发机上打开Chrome浏览器
在Chrome浏览器地址栏中输入chrome://inspect,会看到以下这张图,若是图里面没有你的APP,请返回到上面检查代码接入是否正确
点击APP旁边的inspect链接,这个时候会弹出一个窗口,若是你用过Chrome的开发者工具,是否是会以为这个界面很熟悉?对了,这个窗口就是Chrome内置的开发者工具,只不过里面监控的内容从网页变成了APP
首先看功能导航条的第一个tab,叫作"Elements",工做区中的内容是否是很熟悉,Hierarchy Viewer,很像吧,点击具体的xml节点,能够看到链接的手机上对应的UI控件高亮显示了,这个能够像Hierarchy Viewer那样分析APP页面的嵌套层级
第二个tab叫作"Network",是用来作网络监控的,基本上覆盖了Chrome开发者工具中"Network inspection"的全部功能点,包括下载图片的预览,JSON数据查看,网络请求内容和返回内容
第三个tab是"Sources",用来查看网页的详细内容
直接跳过"Timeline", "Profiles"看第六个tab,"Resources",顾名思义,这里应该就是查看APP内部产生数据的地方啦,目前支持的数据有两种,一种是数据库(ContentProvider和Sql的方式)的数据,另外一种是SharedPreferences数据
"Audits" 跳过,如同"Timeline"和"Profiles",目前没怎么支持,有待进一步发掘的功能。
"Console"老司机下面讲
关于网络监控的有一些须要注意的字段的含义,详细的内容能够去精读一遍Chrome开发者工具官方文档
这里只详细解释下上面图中个字段的含义。
Name/Path 网络资源的名称和URL路径,好比http://www.figotan.org/c/v/logo.jpg这个网络资源,Name是logo.jpg Path是www.figotan.org/c/v/
Method HTTP协议规定的请求方法,好比GET POST
Status/Text HTTP协议规定的返回码和这个返回码对应的含义解释文字 ,好比200/OK
Type 请求资源的MIME类型,好比application/json image/jpeg image/png等等
Initiator 发起请求的对象,能够是Parser/Redirect/Script/Other,详见上面的官方文档
Size/Content Size表示HTTP响应的头和数据体的和,由远程服务端返回;Content是返回的资源解码后的大小
Time/Latentcy Time是总的时间间隔,从发起请求开始到接收到服务端返回的最后一个字节为止;Latency是指的接收到服务端返回的第一个字节消耗的时间
Timeline 显示了全部网络请求的瀑布流
除了监控网络,查看/修改数据以外,还能够作不少事情,由于Stetho预留了两种接口,为了可持续的发展
自定义插件是让老司机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是自定义的插件,具体内容能够参考Stetho提供的sample程序
执行dumpapp命令须要先从git取下最新的代码,而后找到dumpapp脚本,并执行
$ git clone https://github.com/facebook/stetho.git $ cd stetho // 列举出支持的命令(插件) $ ./scripts/dumpapp -p com.facebook.stetho.sample -l
参照sample代码编写dumpapp插件,而后用dumpapp命令验证插件的效果
目前Stetho对于JavaScript脚本的支持是采用内嵌Mozilla研发的Rhino。
第一种采用dumpapp的插件扩展方式虽然功能强大,无所不能,可是完成一件事情须要必定的技术和时间成本,必须经历一系列的编写,编译,构建,安装,调试,修改代码,再下一个轮回,迭代几回后才能造成产出,这实际上是类c/c++/java这种非动态语言的一个缺陷,软件的研发周期太长。那么,若是有一种写完即发布的脚本语言可以支持起来,其实对于研发效率来讲,是有很大提高的,好比lua/javascript/perl/python/groovy等等,这样的语言轻巧,无需编译,写完就能够发布验证,甚至能够边写边调试边上线。
Chrome开发者工具原生支持JavaScript,因此Stetho也提供了JavaScript的支持。
Rhino是一个能够运行在Java程序内部的JavaScript实现,由Mozilla开发并发布为一个开源的项目。
下面说说集成和使用方式
若是要让APP支持Rhino, 首先是gradle配置
dependencies { compile 'com.facebook.stetho:stetho-js-rhino:1.3.1' }
而后就能够经过开发机上Chrome浏览器提供的开发者工具的"Console"老司机(任何工具都有老司机console)来发射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传递一些变量,类,闭包和函数给JavaScript运行环境中,那么你能够在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
https://segmentfault.com/a/1190000012075067