字节小程序
开发者社区
小程序小游戏
登录
【技术攻略】抖音小程序调试原理

【技术攻略】抖音小程序调试原理

6830浏览作者: 社区管理员-CX

本文阐述目前抖音小程序 所使用的本地以及远程调试相关技术,阐述整体运作流程,包含本地调试、远程调试、小程序 webview 调试以及 V8 调试相关的核心类与实现。

基础概念

首先需要提到的是,chrome 与 V8 本身实现了一套调试协议:https://chromedevtools.github.io/devtools-protocol/

比如我们在 chrome devtool 打了一个断点,devtool 会自动产生协议消息,V8 获得协议消息之后也能对该协议消息进行解析产生相应的反馈,产生协议消息再给 chrome devtool, chrome devtool 再展示相应的变量信息等。

如下图,拦截了 ws 消息,可以看到协议中产生的消息自动产生了 id 序列号码以及方法(绿色的是 chrome devtool 发出的消息,红色是从 V8 发来,chrome devtool 接收到的消息)。


本地调试


小程序在内部包支持本地直接连接 USB 进行本地调试,在没有配置远程调试链接模式下,默认在初始化V8引擎后,内部会在本地开启一个websocket server,端口为9229。

同时这个websocket server需要支持/json(更多可以参考:chromedevtools.github.io/devtools-pr… ),这样chrome就能在 chrome://inspect 界面找到该调试。

具体操作就是,手机设备先连接USB线,然后执行 adb forward tcp:9229 tcp:9229 转发端口到电脑设备,这时我们可以访问 localhost:9229,可以看到如下数据,描述了调试调试链接等信息。

[
  {
    "description": "helium v8 inspect",
    "devtoolsFrontendUrl": "chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/helium",
    "title": "helium",
    "id": "helium",
    "type": "node",
    "url": "file://",
    "webSocketDebuggerUrl": "ws://127.0.0.1:9229/helium"
  }
]

打开 chrome://inspect,可以配置寻找的相应端口号


最后从 chrome://inspect 可以看到


点击 inspect 则打开 json 结构中的 devtoolsFrontendUrl

chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/helium

可以观察发现 ws=127.0.0.1:9229/helium 其实就是告诉 chrome devtool 去连接该链接的 websocket server,这样的话就 websocket server 就能收到 devtool 传来的调试协议消息,然后传给 v8,v8 产生协议消息后再给websocket server 向 devtool 发送消息,这样就产生了整个调试通路。

远程调试


本地调试是我们的内部调试方式,并且使用 USB 调试比较麻烦,为了解决开发者的调试,我们加了一个远程调试逻辑,其实原理也很简单,我们通过一个远程的中转 websocket 服务器,客户端不再作为 server,而是作为 client,然后 client 连接远程服务端,devtool 也连接远程服务端,devtool 发送断点调试信息给远程服务端,远程服务端将该信息透传给手机中的 v8,这样就形成了通路。

其实对于小程序场景,其中还有一个角色是抖音开发者工具 IDE (electron),IDE 包含了 chrome devtool,整体的运行流程如下:

  • 开发者开发完成,点击开发者工具的真机调试
  • 抖音开发者工具连接远程服务端,获取调试房间 id,将房间 id 带入了二维码信息中,产生了二维码
  • 带有抖音小程序的手机扫码,获取调试房间 id 后连接到远程服务器的同一个房间中
  • 这时候设备中的 V8 就可以与 chrome devtool 通过远程服务器的房间进行转发通信消息,也就形成了整个调试通路

小程序 webview 调试


对于小程序场景还有一个 webview 的调试,那我们也要一起支持远程调试,怎么做呢?

其实从 chrome://inspect 本身我们可以看到开了 debug 设置的 webview 本身就是可以调试的,其实我们就可以猜想到 webview 本身在手机就起了服务,因此我们只要同样连上这个服务,就可以往里面收发消息完成远程调试了。


如下,chrome for android 其实开启了 unix domain socket。


所以只要连接上它们就行了,然后再新增一个 websocket client,将我们从原本的远程 Websocket client 收到的 DOM 调试信息转发给该 unix domain socket,client从该 unix domain socket 收到消息转发给远程服务器,最后 devtool 就可以收到 webview DOM 的调试信息。

可以看到上面的示意图,相比前一节新增了一个 WebSocket client(webview)以及 Webview Unix Socket 的模块,通过它们的转发通信,我们就实现了远程调试小程序的页面元素。

V8 调试模块

下述讲解 V8 调试模块接入的一些核心类,包含 v8_inspector::V8InspectorClient、v8_inspector::V8Inspector、v8_inspector::V8InspectorSession、v8_inspector::V8Inspector::Channel等,有兴趣接入 V8 调试模块业务可以作为参考,整体工作流程如下所示:


代码示例:

// 创建 Channel
// 需要自己实现 Channel
// class ChannelImpl final: public v8_inspector::V8Inspector::Channel
v8_inspector::V8Inspector::Channel channel_ = new ChannelImpl();
v8_inspector::StringView view( ... )

// 创建 session
// 连接 inspector 和 channel
v8_inspector::V8InspectorSession session_ = 
    inspector_.connect( 
        1, 
        channel, 
        view);

// 需要调用 contextCreated 
// 将上下文信息传入 inspector
// ctx_name 为上下文名字
v8_inspector::StringView ctx_name( /*ctx_name*/ )
inspector_->contextCreated(、
    v8_inspector::V8ContextInfo(
        context, 
        1, 
        ctx_name);

v8_inspector::V8InspectorClient

class V8_EXPORT V8InspectorClient {
 public:
  virtual ~V8InspectorClient() = default;

  virtual void runMessageLoopOnPause(int contextGroupId) {}
  virtual void quitMessageLoopOnPause() {}
};

这个类我们主要需要实现两个函数,一个是 runMessageLoopOnPause,一个是 quitMessageLoopOnPause

  • runMessageLoopOnPause

在断点的时候,v8 会触发 runMessageLoopOnPause,这时候需要接入方同步的消费所有来源于 devtool 消息(消费即传入 v8 驱动运行),我们需要开启自己的messageLoop去消费消息,否则会导致丢失所有 devtool 发来的消息的作用,现象就是拿不到上下文信息,console 控制台将无效没有反应

  • quitMessageLoopOnPause

将结束断点的时候,就会自动触发 quitMessageLoopOnPause,这时候我们可以停止 MessageLoop,不需要再继续进行同步地消费消息了

v8_inspector::V8Inspector

这个类构造时需要传入两个参数,一个是 isolate,一个是上述我们所讲的 V8InspectorClient 实例,首先需要说的是,一个 v8Inspector 对应一个 isolate,一个 isolate 下有多个 context,因此其实一个 inspector 可以调试多个 context。

v8_inspector::V8Inspector::Channel
// Connection.
  class V8_EXPORT Channel {
   public:
    virtual ~Channel() = default;
    virtual void sendResponse(int callId,
                              std::unique_ptr<StringBuffer> message) = 0;
    virtual void sendNotification(std::unique_ptr<StringBuffer> message) = 0;
    virtual void flushProtocolNotifications() = 0;
 };

channel 是跟 v8 inspector 的通道,可以获取到 v8 内部产生的协议消息,需要做的事情就将它转发给 devtool,主要需要实现 sendResponse 和 sendNotification,其实很简单直接将消息转发给 devtool 就行了。

void sendResponse(int callId, std::unique_ptr<v8_inspector::StringBuffer> message) override {

sendToDevtool(message->string());

}

void sendNotification(std::unique_ptr<v8_inspector::StringBuffer> message) override {

sendToDevtool(message->string());

}

v8_inspector::V8InspectorSession

std::unique_ptr<StringBuffer> &str = messages.front();
session->dispatchProtocolMessage(str->string());

参考资料

最后一次编辑于 2022年02月28日
加载中