React Native调试源码分析——Debuger

1、前言

    我们都知道我们在调试的时候都会开一个chrome,在chrome的调试界面中打断点、看log,本节就主要了解以下Debuger模式下js代码是如何加载的、js代码如何更新、如何断点源码。

2、App开启Debug模式

    跟着Debug JS Remotely按钮走,直接上reloadJSInProxyMode函数

//DevSupportManagerImpl.java

private void reloadJSInProxyMode() {
    // When using js proxy, there is no need to fetch JS bundle as proxy executor will do that
    // anyway
    mDevServerHelper.launchJSDevtools();//0<--

    JavaJSExecutor.Factory factory = new JavaJSExecutor.Factory() {
      @Override
      public JavaJSExecutor create() throws Exception {
        WebsocketJavaScriptExecutor executor = new WebsocketJavaScriptExecutor();//1<--
        SimpleSettableFuture future = new SimpleSettableFuture<>();
        executor.connect(//2<--
                mDevServerHelper.getWebsocketProxyURL(),
                getExecutorConnectCallback(future));
        // TODO(t9349129) Don't use timeout
        try {
          future.get(90, TimeUnit.SECONDS);//3<--
          return executor;
        } catch (ExecutionException e) {
          throw (Exception) e.getCause();
        } catch (InterruptedException | TimeoutException e) {
          throw new RuntimeException(e);
        }
      }
    };
    mReactInstanceManagerHelper.onReloadWithJSDebugger(factory);//4<--
  }

可以看到这个函数名叫做reloadJSInProxyMode,也就是说js环境是被代理的

0、launchJSDevtools,这里就是启动chrome的操作

1、这里创建了一个由websocket实现的js代码运行环境,说明js代码是远程执行的

2、让executor去连接远程远程的WebSokcetServer

3、连接等待时间是90s,超时则算失败

4、使用这个远程js运行环境去reload我们的js代码

3、chrome代理执行代码

    启动chrome时会加载debugger-ui界面,源码在node_modules/react-native/local-cli/server/util/debugger-ui/index.html位置

index.html
  function connectToDebuggerProxy() {
    const ws = new WebSocket('ws://' + window.location.host + '/debugger-proxy?role=debugger&name=Chrome');//1<--
    let worker;
    let queuedMessages = [];
    let appExecuted = false;

    function createJSRuntime() {
      worker = new Worker('debuggerWorker.js');//2<--
      worker.onmessage = function(message) {
        ws.send(JSON.stringify(message.data));
      };
......
    }

    function shutdownJSRuntime() {
      if (worker) {
        worker.terminate();
        worker = null;
        window.onbeforeunload = null;
      }
    }
......
    ws.onmessage = async function(message) {//3<--
      if (!message.data) {
        return;
      }
      const object = JSON.parse(message.data);
      if (object.$event === 'client-disconnected') {
        shutdownJSRuntime();
        Page.setState({status: {type: 'disconnected'}});
        return;
      }
      if (object.method === 'prepareJSRuntime') {//4<--
        shutdownJSRuntime();
        console.clear();
        createJSRuntime();
        ws.send(JSON.stringify({replyID: object.id}));
        Page.setState({status: {type: 'connected', id: object.id}});
      } else if (object.method === '$disconnected') {
        shutdownJSRuntime();
        Page.setState({status: {type: 'disconnected'}});
      } else if (object.method === 'executeApplicationScript') {//5<--
        appExecuted = false;
        worker.postMessage({
          ...object,
          url: await getBlobUrl(object.url),
        });
        appExecuted = true;
        // Flush any messages queued up and clear them
        for (const message of queuedMessages) {
          worker.postMessage(message);
        }
        queuedMessages = [];
      } else {
        if (appExecuted) {//6<--
          worker.postMessage(object);
        } else {
          queuedMessages.push(object);
        }
      }
    };

    ws.onclose = function(error) {
      shutdownJSRuntime();
......
    };
......
  }

1、额?预期中这个chrome应是个websocketserver供app连接,但看这个代码表明chrome也是个websocketclient,不过这里给server传递了参数role=debugger,表明自己的身份,回过头看app的url拼接"ws://%s/debugger-proxy?role=client",说明app的身份是client,可以推测他们连的是同一个服务器,使用身份字段让区分各自的请求并由websocketserver转发

2、这里出现了debuggerWorker,这是个js线程,这个线程是真正运行RN js代码的地方

3、websocket收到message后创建js运行环境或者转发给debuggerWorker线程

4、创建js线程消息

5、这是执行js代码的消息,这个消息体内包含了代码信息,这一步中根据消息体中的url参数发送给Metro Server,等待Metro打包后返回BlobUrl并传递给debuggerWorker

6、这时其他信息,一般都是jscall

4、debuger中转站

    前面我们知道app和chrome都是使用WebSocket Client来连接的,因此我们需要找出WebSocket Server

function attachToServer(server, path) {
  var WebSocketServer = require('ws').Server;
  var wss = new WebSocketServer({
    server: server,
    path: path,
  });
  var debuggerSocket, clientSocket;

  function send(dest, message) {
    if (!dest) {
      return;
    }

    try {
      dest.send(message);
    } catch (e) {
      console.warn(e);
      // Sometimes this call throws 'not opened'
    }
  }

  wss.on('connection', function(ws) {
    const {url} = ws.upgradeReq;

    if (url.indexOf('role=debugger') > -1) {
      if (debuggerSocket) {
        ws.close(1011, 'Another debugger is already connected');
        return;
      }
      debuggerSocket = ws;
      debuggerSocket.onerror = debuggerSocket.onclose = () => {
        debuggerSocket = null;
        if (clientSocket) {
          clientSocket.close(1011, 'Debugger was disconnected');
        }
      };
      debuggerSocket.onmessage = ({data}) => send(clientSocket, data);
    } else if (url.indexOf('role=client') > -1) {
      if (clientSocket) {
        clientSocket.onerror = clientSocket.onclose = clientSocket.onmessage = null;
        clientSocket.close(1011, 'Another client connected');
      }
      clientSocket = ws;
      clientSocket.onerror = clientSocket.onclose = () => {
        clientSocket = null;
        send(debuggerSocket, JSON.stringify({method: '$disconnected'}));
      };
      clientSocket.onmessage = ({data}) => send(debuggerSocket, data);
    } else {
      ws.close(1011, 'Missing role param');
    }
  });

  return {
    server: wss,
    isChromeConnected: function() {
      return !!debuggerSocket;
    },
  };
}

这个代码很清晰,大致的内容就是创建一个WebSocketServer,将client(App)、debuger(Chrome)分别保存,并通过本身转发client和debuger的消息,这也应证了之前的猜测

5、debuggerWorker

    这个类是在debug模式中运行js代码的真实环境,它是个单独的线程,接收到websocket的消息并做对应操作


  var messageHandlers = {
    executeApplicationScript: function(message, sendReply) {
      for (var key in message.inject) {
        self[key] = JSON.parse(message.inject[key]);
      }
      var error;
      try {
        importScripts(message.url);//1<--
      } catch (err) {
        error = err.message;
      }
      sendReply(null /* result */, error);
    },
    setDebuggerVisibility: function(message) {
      visibilityState = message.visibilityState;
    },
  };

1、这里收到消息后就直接importScript(mesage.url),importScript是js自带的函数,这样就能执行url中的代码

message.url中对应的代码都是打包过后的代码,已经跟源码对不上了,这就需要sourceMapping

在打包后的代码后面都跟着一串注释

# sourceMappingURL=http://localhost:8081/index2.map?platform=android&dev=true&minify=false&revisionId=1c71084ee7e99ed7

这段注释执行的时候会促发chrome去请求sourceMapping,chrome通过获取到的sourceMapping就能对应上源码的位置了

6、总结

  •                                                                 Metro Server
  •                                                                         |   blobUrl
  •                                                                        V                
  •     App    <-->    WebSocket Proxy  <-->    Chrome  <-->  debuggerWorker

 

 

 

你可能感兴趣的:(react,naitve,RN,debuger)