Stetho 是 Facebook 开发并开源的的一个便捷的调试工具(github.com/facebook/st… 开发过程中可以在 chrome 中实时看到接口的请求情况、数据库、以及一些 SharedPreference 的存储情况,大大提升了开发人员的调试效率;具体用法就不做介绍,可以自己 Google 一下。这篇文章就简单来分析一下这个工具的一些总体的设计结构,以及网络拦截模块的一些实现,其它部分大同小异,就不详细分析。
库的代码结构
|-stetho
|---common
|---dumpapp
|---inspector
|---json
|---server
|---websocket
|--- ··· ···
|-stetho-okhttp
|-stetho-okhttp3
|-stetho-sample
|- ··· ···
主要核心代码是放在 stetho
的 module 中,其它是一些适配三方网络库的实现代码和 sample。 而 stetho
中,json
、server
和websocket
主要是一些和 chrome 通讯的包,而我觉得比较核心的部分是放在 inspector
中
|-stetho
|---inspector
|------console
|------database
|------domstorage
|------elements
|------helper
|------jsonrpc
|------network
|------protocol
|------ ··· ···
|---- ··· ···
|- ··· ···
上面包结构中, console
、database
、domstorage
、network
分别承担了不同数据类型的数据采集和处理等,下面主要以网络数据类型做分析,其它的大同小异。
网络拦截模块
网络请求和响应的的拦截流程如下:
requestWillBeSent +---> responseHeadersReceived +---> interpretResponseStream
| | |
| `---> dataSent |
| |
`-----------------------------`--------> httpExchangeFailed
(此流程图可见于源码 NetworkEventReporter)
网络模块的数据发送和接收的拦截实现主要放在 NetworkEventReporterImpl
类中,而此类是 NetworkEventReporter
接口的一个实现类。按照上面的主要流程,我们来分析下这个接口里面几个主要的方法。
请求将要被发送的时候会被调用
void requestWillBeSent(InspectorRequest request);
接收到响应头
void responseHeadersReceived(InspectorResponse response);
异常
void httpExchangeFailed(String requestId, String errorText);
拦截到响应数据流
InputStream interpretResponseStream(String requestId, String contentType,
String contentEncoding, InputStream inputStream, ResponseHandler responseHandler);
响应数据读取失败
void responseReadFailed(String requestId, String errorText);
响应数据读取完成
void responseReadFinished(String requestId);
请求数据发送
void dataSent(String requestId, int dataLength, int encodedDataLength);
接收数据
void dataReceived(String requestId, int dataLength, int encodedDataLength);
以上的这些方法基本上组成了一次网络请求从发送到接收的完整过程,而 stetho 中正是在这些类似 生命周期
的方法中实现对一些参数或者返回数据的拦截和发送
。这里讲到的发送
,则主要是通过一些 chrome 的协议发送到 chrome 中。
那么发送数据的实现过程是如何的呢?在 NetworkEventReporter
的实现类 NetworkEventReporterImpl
中,我们可以看到最终都是调用以下代码实现数据的发送的
peerManager.sendNotificationToPeers("Network.responseReceived", receivedParams);
而这个 peerManager
是什么鬼呢?看代码可以得知,是一个叫做 NetworkPeerManager
的类的对象,这个类继承自 ChromePeerManager
,而 ChromePeerManager
还有另外两个子类分别为 DOMStoragePeerManager
、ConsolePeerManager
,看类名应该大致可以猜出:这是一些分别对应网络、DOM 式数据以及控制台数据的 Peer
的管理类。它们的继承关系如下:
而 NetworkPeerManager
的工作流程又是如何呢,以下进行一些大概的分析。
NetworkPeerManager
首先是一个单例模式,得到一个对象
public static synchronized NetworkPeerManager getOrCreateInstance(Context context) {
if (sInstance == null) {
sInstance = new NetworkPeerManager(
new ResponseBodyFileManager(
context.getApplicationContext()));
}
return sInstance;
}
上面可以看到 NetworkEventReporter
中发送消息用的是这个方法
public void sendNotificationToPeers(String method,
Object params) {
sendMessageToPeers(method, params, null /* callback */);
}
private void sendMessageToPeers(String method,
Object params,
@Nullable PendingRequestCallback callback) {
JsonRpcPeer[] peers = getReceivingPeersSnapshot();
for (JsonRpcPeer peer : peers) {
try {
peer.invokeMethod(method, params, callback);
} catch (NotYetConnectedException e) {
LogRedirector.e(TAG, "Error delivering data to Chrome", e);
}
}
}
可以看到这个方法最终是用到了 JsonRpcPeer
进行发送,那它是如何工作的呢
public void invokeMethod(String method, Object paramsObject,
@Nullable PendingRequestCallback callback)
throws NotYetConnectedException {
Util.throwIfNull(method);
Long requestId = (callback != null) ? preparePendingRequest(callback) : null;
// magic, can basically convert anything for some amount of runtime overhead...
JSONObject params = mObjectMapper.convertValue(paramsObject, JSONObject.class);
JsonRpcRequest message = new JsonRpcRequest(requestId, method, params);
String requestString;
JSONObject jsonObject = mObjectMapper.convertValue(message, JSONObject.class);
requestString = jsonObject.toString();
mPeer.sendText(requestString);
}
可以看到,通过一个对象 mPeer
的 sendText()
方法,这个对象是什么东西呢?来自一个接口 SimpleSession
的实现类。具体来自于哪个呢,要从 JsonRpcPeer
构造函数反查回去。 可以定位到 ChromeDevtoolsServer
里面的 onOpen()
方法,而哪个地方调用到这个方法呢?再查看调用,最终可以定位到 WebSocketSession
类,而这个类就刚好是SimpleSession
的实现类;
这时候就可以在这个实现类中看到 sendText()
是如何实现的。
public void sendText(String payload) {
doWrite(FrameHelper.createTextFrame(payload));
}
private void doWrite(Frame frame) {
if (signalErrorIfNotOpen()) {
return;
}
mWriteHandler.write(frame, mErrorForwardingWriteCallback);
}
而这个 WriteHandler
中的 write
如下,这个类是放在 stetho 的 websocket
包中,到这一层,基本是一些和 chrome 相关通讯的逻辑,本文就就不再往下分析了。可能会重新在后续别的文章中进行分析。
public synchronized void write(Frame frame, WriteCallback callback) {
try {
frame.writeTo(mBufferedOutput);
mBufferedOutput.flush();
callback.onSuccess();
} catch (IOException e) {
callback.onFailure(e);
}
}