實現原理
- 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中。