实现原理
- The WebSocket Protocol(RFC6455)
- Chrome DevTools Protocol
整体流程
自定义命令流程
stetho-server流程
具体执行流程
1、初始化方法
public static void initializeWithDefaults(final Context context) {
initialize(new Initializer(context) {
@Override
protected Iterable<DumperPlugin> getDumperPlugins() {
return new DefaultDumperPluginsBuilder(context).finish();
}
@Override
protected Iterable<ChromeDevtoolsDomain> getInspectorModules() {
return new DefaultInspectorModulesBuilder(context).finish();
}
});
}
public static void initialize(final Initializer initializer) {
...
initializer.start();
}复制代码
2、在 initializer.start()之后调用了 serverManager.start(),在这里new Thred() 并开启Socket建立连接
while (!Thread.interrupted()) {
try {
LocalSocket socket = mServerSocket.accept();
// Start worker thread
Thread t = new WorkerThread(socket, mSocketHandler);
t.setName(
WORKER_THREAD_NAME_PREFIX +
"-" + mFriendlyName +
"-" + mThreadId.incrementAndGet());
t.setDaemon(true);
t.start();
} catch (SocketException se) {
...
}
}复制代码
3、在WorkerThread中执行 mSocketHandler.onAccepted(mSocket);这里的mSocketHandler为 LazySocketHandler 类,由Stetho类传递过来,在LazySocketHandler的onAccepted方法中,真正处理逻辑的 SocketHandler 是由RealSocketHandlerFactory创建的ProtocolDetectingSocketHandler。
private class RealSocketHandlerFactory implements SocketHandlerFactory {
@Override
public SocketHandler create() {
ProtocolDetectingSocketHandler socketHandler =
new ProtocolDetectingSocketHandler(mContext);
Iterable<DumperPlugin> dumperPlugins = getDumperPlugins();
if (dumperPlugins != null) {
Dumper dumper = new Dumper(dumperPlugins);
socketHandler.addHandler(
new ProtocolDetectingSocketHandler.ExactMagicMatcher(
DumpappSocketLikeHandler.PROTOCOL_MAGIC),
new DumpappSocketLikeHandler(dumper));
// Support the old HTTP-based protocol since it's relatively straight forward to do.
DumpappHttpSocketLikeHandler legacyHandler = new DumpappHttpSocketLikeHandler(dumper);
socketHandler.addHandler(
new ProtocolDetectingSocketHandler.ExactMagicMatcher(
"GET /dumpapp".getBytes()),
legacyHandler);
socketHandler.addHandler(
new ProtocolDetectingSocketHandler.ExactMagicMatcher(
"POST /dumpapp".getBytes()),
legacyHandler);
}
Iterable<ChromeDevtoolsDomain> inspectorModules = getInspectorModules();
if (inspectorModules != null) {
socketHandler.addHandler(
new ProtocolDetectingSocketHandler.AlwaysMatchMatcher(),
new DevtoolsSocketHandler(mContext, inspectorModules));
}
return socketHandler;
}
}复制代码
主要创建了两个处理器,用于处理Chrome Dev Tools相关的 DevtoolsSocketHandler,以及处理自定义命令的DumpappSocketLikeHandler。
4、在 ProtocolDetectingSocketHandler 的 onSecured 方法中,通过MagicMatcher区分当前请求由哪个处理器处理
for (int i = 0, N = mHandlers.size(); i < N; i++) {
HandlerInfo handlerInfo = mHandlers.get(i);
leakyIn.mark(SENSING_BUFFER_SIZE);
boolean matches = handlerInfo.magicMatcher.matches(leakyIn);
leakyIn.reset();
if (matches) {
SocketLike socketLike = new SocketLike(socket, leakyIn);
handlerInfo.handler.onAccepted(socketLike);
return;
}
}复制代码
mHandlers 为List 类型,在RealSocketHandlerFactory执行了addHandler(),添加了Chrome Dev Tools和自定义命令的处理器
5、在分发处理器之后,处理器接收到信息执行onAccepted()方法,DevtoolsSocketHandler处理器调用LightHttpServer.serve()再次进行请求的分发处理(这里先分析 DevtoolsSocketHandler)
while ((request = readRequestMessage(scratchRequest, reader)) != null) {
final LightHttpResponse response = scratchResponse;
response.reset();
boolean keepGoing = dispatchToHandler(anotherSocketLike, request, response);
if (!keepGoing) {
break;
}
writeFullResponse(response, writer, output);
}复制代码
dispatchToHandler(anotherSocketLike, request, response)
private boolean dispatchToHandler(SocketLike socketLike, LightHttpRequest request, LightHttpResponse response)
throws IOException {
HttpHandler handler = mHandlerRegistry.lookup(request.uri.getPath());
if (handler == null) {
...
} else {
try {
return handler.handleRequest(socketLike, request, response);
} catch (RuntimeException e) {
...
}
}
}复制代码
mHandlerRegistry.lookup(request.uri.getPath()) 通过path获取不同的处理器
public synchronized HttpHandler lookup(String path) {
for (int i = 0, N = mPathMatchers.size(); i < N; i++) {
if (mPathMatchers.get(i).match(path)) {
return mHttpHandlers.get(i);
}
}
return null;
}复制代码
这里mPathMatchers,mHttpHandlers维护了一个path和处理器的对应关系,
- ChromeDiscoveryHandler对应的path为
- /json
- /json/version
- /json/activate/ + PAGE_ID
- WebSocketHandler对应path为:/inspector
- DumpappLegacyHttpHandler对应的path为:/dumpapp
6、WebSocketHandler.handleRequest() 处理请求,接着会调用WebSocketSession.handle()方法,在这里mReadHandler.readLoop(mReadCallback) 循环读取WebSocket中的数据Frame,Frame具体结构可参照 RFC6455 文档。
public void readLoop(ReadCallback readCallback) throws IOException {
Frame frame = new Frame();
do {
frame.readFrom(mBufferedInput);
mCurrentPayload.write(frame.payloadData, 0, (int) frame.payloadLen);
if (frame.fin) {
byte[] completePayload = mCurrentPayload.toByteArray();
readCallback.onCompleteFrame(frame.opcode, completePayload, completePayload.length);
mCurrentPayload.reset();
}
} while (frame.opcode != Frame.OPCODE_CONNECTION_CLOSE);
}复制代码
在 readCallback.onCompleteFrame(frame.opcode, completePayload, completePayload.length);方法中再次进行分发处理
public void onCompleteFrame(byte opcode, byte[] payload, int payloadLen) {
switch (opcode) {
case Frame.OPCODE_CONNECTION_CLOSE:
handleClose(payload, payloadLen);
break;
case Frame.OPCODE_CONNECTION_PING:
handlePing(payload, payloadLen);
break;
case Frame.OPCODE_CONNECTION_PONG:
handlePong(payload, payloadLen);
break;
case Frame.OPCODE_TEXT_FRAME:
handleTextFrame(payload, payloadLen);
break;
case Frame.OPCODE_BINARY_FRAME:
handleBinaryFrame(payload, payloadLen);
break;
default:
signalError(new IOException("Unsupported frame opcode=" + opcode));
break;
}
}复制代码
private void handleTextFrame(byte[] payload, int payloadLen) {
mEndpoint.onMessage(WebSocketSession.this, new String(payload, 0, payloadLen));
}复制代码
这里 mEndpoint 为 ChromeDevtoolsServer,在ChromeDevtoolsServer中的onMessage()方法中,handleRemoteMessage(peer, message) 分为处理响应和请求:handleRemoteRequest()和handleRemoteResponse()
7、在handleRemoteRequest()中调用MethodDispatcher.dispatch(peer,request.method,request.params)进行具体的方法分发
public JSONObject dispatch(JsonRpcPeer peer, String methodName, @Nullable JSONObject params) throws JsonRpcException {
MethodDispatchHelper dispatchHelper = findMethodDispatcher(methodName);
...
try {
return dispatchHelper.invoke(peer, params);
} catch (InvocationTargetException e) {
...
}
}复制代码
在findMethodDispatcher(methodName)方法中首先buildDispatchTable()使用反射的方式维护了method和MethodDispatchHelper的映射关系,然后通过methodName获取MethodDispatchHelper。
private static Map<String, MethodDispatchHelper> buildDispatchTable(ObjectMapper objectMapper, Iterable<ChromeDevtoolsDomain> domainHandlers) {
Util.throwIfNull(objectMapper);
HashMap<String, MethodDispatchHelper> methods = new HashMap<String, MethodDispatchHelper>();
for (ChromeDevtoolsDomain domainHandler : Util.throwIfNull(domainHandlers)) {
Class<?> handlerClass = domainHandler.getClass();
String domainName = handlerClass.getSimpleName();
for (Method method : handlerClass.getDeclaredMethods()) {
if (isDevtoolsMethod(method)) {
MethodDispatchHelper dispatchHelper = new MethodDispatchHelper(
objectMapper,
domainHandler,
method);
methods.put(domainName + "." + method.getName(), dispatchHelper);
}
}
}
return Collections.unmodifiableMap(methods);
}复制代码
ChromeDevtoolsDomain的实现类包含:
- DOM
- DOMStorage
- Worker
- CSS
- Debugger
- Profiler
- HeapProfiler
- Console
- Page
- Database
- Inspector
- Runtime
- Network
对应Chrome的调试页面,类的具体内容可参照Chrome DevTools Protocol协议,类封装了具体的方法和请求体、返回体的参数,在这里实现了对数据库、SP等的具体操作方法,此处不做过多分析。
8、在 MethodDispatchHelper.invoke()方法中使用反射的方式执行了 ChromeDevtoolsDomain 中的方法,获取结果后在handleRemoteRequest()封装数据返回。
public JSONObject invoke(JsonRpcPeer peer, @Nullable JSONObject params)
throws InvocationTargetException, IllegalAccessException, JSONException, JsonRpcException {
Object internalResult = mMethod.invoke(mInstance, peer, params);
if (internalResult == null || internalResult instanceof EmptyResult) {
return new JSONObject();
} else {
JsonRpcResult convertableResult = (JsonRpcResult) internalResult;
return mObjectMapper.convertValue(convertableResult, JSONObject.class);
}
}复制代码
9、自定义命令的实现:
在第4
步接收到请求分发处理器之后,DumpappSocketLikeHandler执行onAccepted()方法,执行dump()方法
static void dump(Dumper dumper, Framer framer, String[] args) throws IOException {
try {
int exitCode = dumper.dump(
framer.getStdin(),
framer.getStdout(),
framer.getStderr(),
args);
framer.writeExitCode(exitCode);
} catch (DumpappOutputBrokenException e) {
...
}
}复制代码
public int dump(InputStream input, PrintStream out, PrintStream err, String[] args) {
try {
return doDump(input, out, err, args);
} catch (ParseException e) {
err.println(e.getMessage());
dumpUsage(err);
return 1;
}复制代码
private int doDump(InputStream input, PrintStream out, PrintStream err, String[] args)
throws ParseException, DumpException {
CommandLine parsedArgs = mParser.parse(mGlobalOptions.options, args, true);
if (parsedArgs.hasOption(mGlobalOptions.optionHelp.getOpt())) {
dumpUsage(out);
return 0;
} else if (parsedArgs.hasOption(mGlobalOptions.optionListPlugins.getOpt())) {
dumpAvailablePlugins(out);
return 0;
} else if (!parsedArgs.getArgList().isEmpty()) {
dumpPluginOutput(input, out, err, parsedArgs);
return 0;
} else {
// Didn't understand the options, spit out help but use a non-success exit code.
dumpUsage(err);
return 1;
}
}复制代码
在使用 CommandLine(Apache Commons CLI库)解析参数之后,根据参数返回使用说明、帮助或者在dumpPluginOutput()执行具体的命令
private void dumpPluginOutput(InputStream input, PrintStream out, PrintStream err, CommandLine parsedArgs) throws DumpException {
List<String> args = new ArrayList(parsedArgs.getArgList());
if (args.size() < 1) {
throw new DumpException("Expected plugin argument");
}
String pluginName = args.remove(0);
DumperPlugin plugin = mDumperPlugins.get(pluginName);
if (plugin == null) {
throw new DumpException("No plugin named '" + pluginName + "'");
}
DumperContext dumperContext = new DumperContext(input, out, err, mParser, args);
plugin.dump(dumperContext);
}复制代码
mDumperPlugins 在第3
步中由Dumper dumper = new Dumper(dumperPlugins)
传递,dumperPlugins包含了所有自定义命令 name和DumperPlugin的映射关系,在获取到DumperPlugin执行dump()方法,dump()方法在自定义DumperPlugin时具体实现。
命令行交互建立连接的实现在Stetho项目的/scripts/dumpapp中。