React Native Debug原理浅析
第一次在segmentfault写博客,很紧张~~~公司项目上ReactNative,之前也是没有接触过,所以也是一边学习一边做项目了,最近腾出手来更新总结了一下RN的Debug的一个小知识点,不是说怎么去Debug,而是Debug的代码原理,下面开始正文。
Debug过程涉及到三个对象,一个是App(Android或iOS),一个是Server,另外一个就是浏览器(Chrome或FireFox或其他)。Server是App和浏览器之间通信的桥梁,比如App发Http请求给Server,Server再通过WebSocket发送给浏览器,反过来也是。首先肯定需要准备一下中介,就是Server
1.Server
这里的Server不用专门准备一台服务器,只需要配置一个Node.js环境,然后启动npm start就行。npm start在package.json中进行配置了,也就是会执行cli.js脚本。
"scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start"
  },然后cli.js会执行runServer.js,在这里启动一个NodeJS Server:
const serverInstance = args.https
    ? https.createServer(
        {
          key: fs.readFileSync(args.key),
          cert: fs.readFileSync(args.cert),
        },
        app,
      )
    : http.createServer(app);
  serverInstance.listen(args.port, args.host, 511, function() {
    attachHMRServer({
      httpServer: serverInstance,
      path: '/hot',
      packagerServer,
    });
    wsProxy = webSocketProxy.attachToServer(serverInstance, '/debugger-proxy');
    ms = messageSocket.attachToServer(serverInstance, '/message');
    readyCallback(reporter);
  });有了中介Server后就可以建立App与浏览器之间的关系了。
2.建立连接
在手机菜单中点击Debug JS Remotely,App就会发出一个Http请求
GET /launch-js-devtools HTTP/1.1Server接收到这个请求会执行opn操作,主要做两件事:
- 打开Chrome的一个tab
- 让这个tab打开urlhttp://localhost:8081/debugger-ui/
这个界面就是我们打开Debug时在浏览器见到的第一个界面
这个界面的文件就是Server的index.html,我截取了body的代码:
<body>
  <div class="content">
    <label for="dark">
      <input type="checkbox" id="dark" onclick="Page.toggleDarkTheme()"> Dark Theme
    </label>
    <label for="maintain-priority">
      <input type="checkbox" id="maintain-priority" onclick="Page.togglePriorityMaintenance()"> Maintain Priority
    </label>
    <p>
      React Native JS code runs as a web worker inside this tab.
    </p>
    <p>Press <kbd id="shortcut" class="shortcut">⌘⌥I</kbd> to open Developer Tools. Enable <a href="https://stackoverflow.com/a/17324511/232122" target="_blank">Pause On Caught Exceptions</a> for a better debugging experience.</p>
    <p>You may also install <a href="https://github.com/facebook/react-devtools/tree/master/packages/react-devtools" target="_blank">the standalone version of React Developer Tools</a> to inspect the React component hierarchy, their props, and state.</p>
    <p>Status: <span id="status">Loading...</span></p>
  </div>
</body>浏览器在执行index.html的时候会发出下面的请求:
GET /debugger-proxy?role=debugger&name=Chrome HTTP/1.1我们来看看发出这个请求有什么目的,扒一扒源码:
function connectToDebuggerProxy() {
    const ws = new WebSocket('ws://' + window.location.host + '/debugger-proxy?role=debugger&name=Chrome'); //Chrome通过websocket和Packager保持通讯
      //WebSocket注册监听
    ws.onopen = function() {
      Page.setState({status: {type: 'connecting'}});
    };
    ws.onmessage = async function(message) {
      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) {
        return;
      }
      // Special message that asks for a new JS runtime
      if (object.method === 'prepareJSRuntime') {
        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') {
        worker.postMessage({
          ...object,
          url: await getBlobUrl(object.url),
        });
      } else {
        // Otherwise, pass through to the worker.
        worker.postMessage(object);
      }
    };
    ws.onclose = function(error) {
      shutdownJSRuntime();
      Page.setState({status: {type: 'error', error}});
      if (error.reason) {
        console.warn(error.reason);
      }
      setTimeout(connectToDebuggerProxy, 500);
    };
    // Let debuggerWorker.js know when we're not visible so that we can warn about
    // poor performance when using remote debugging.
    document.addEventListener('visibilitychange', updateVisibility, false);
  }首先就是通过new WebSocket浏览器建立与Server的联系,WebSocket就是可以保持长连接的全双工通信协议,在握手阶段通过Http进行,后面就和Http没有什么关系了。然后会给这个webSocket注册一些监听:
ws.onopen
ws.onmessage
ws.onclose在webSocket收到消息时会回调ws.onmessage。
到这里App和浏览器之间就已经建立连接了,接下来App会发出几个消息让浏览器加载需要调试的代码, 接着往下看。
3.加载调试代码
首先需要强调的就是浏览器加载项目代码肯定不能在UI线程加载吧,要不然肯定影响浏览器的正常工作。那怎么去加载?启一个后台线程,有的小伙伴就要不信了,别急,我们接着去扒一扒源码。
App发出一个消息让浏览器准备JS的运行环境:
在收到‘prepareJSRuntime’消息会调用createJSRuntime。
// Special message that asks for a new JS runtime
      if (object.method === 'prepareJSRuntime') {
        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') {
        worker.postMessage({
          ...object,
          url: await getBlobUrl(object.url),
        });
      } else {
        // Otherwise, pass through to the worker.
        worker.postMessage(object);
      }接着看‘createJSRuntime’这个函数, 主要工作就是‘new Worker’,看下Worker的定义:
Web Workers is a simple means for web content to run scripts in
background threads. The worker thread can perform tasks without
interfering with the user interface.
也就是会起一个后台线程,来运行‘debuggerWorker.js’这个脚本。
function createJSRuntime() {
      // This worker will run the application JavaScript code,
      // making sure that it's run in an environment without a global
      // document, to make it consistent with the JSC executor environment.
      worker = new Worker('debuggerWorker.js');
      worker.onmessage = function(message) {
        ws.send(JSON.stringify(message.data));
      };
      window.onbeforeunload = function() {
        return 'If you reload this page, it is going to break the debugging session. ' +
          'You should press' + refreshShortcut + 'in simulator to reload.';
      };
      updateVisibility();
}接着看看debuggerWorker.js,主要就是一个消息的监听,可以看到在messageHandlers里主要处理两类消息:
'executeApplicationScript', 'setDebuggerVisibility'
/* global __fbBatchedBridge, self, importScripts, postMessage, onmessage: true */
/* eslint no-unused-vars: 0 */
'use strict';
onmessage = (function() {
  var visibilityState;
  var showVisibilityWarning = (function() {
    var hasWarned = false;
    return function() {
      // Wait until `YellowBox` gets initialized before displaying the warning.
      if (hasWarned || console.warn.toString().includes('[native code]')) {
        return;
      }
      hasWarned = true;
      console.warn(
        'Remote debugger is in a background tab which may cause apps to ' +
        'perform slowly. Fix this by foregrounding the tab (or opening it in ' +
        'a separate window).'
      );
    };
  })();
  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);
      } catch (err) {
        error = err.message;
      }
      sendReply(null /* result */, error);
    },
    'setDebuggerVisibility': function(message) {
      visibilityState = message.visibilityState;
    },
  };
  return function(message) {
    if (visibilityState === 'hidden') {
      showVisibilityWarning();
    }
    var object = message.data;
    var sendReply = function(result, error) {
      postMessage({replyID: object.id, result: result, error: error});
    };
    var handler = messageHandlers[object.method];
    if (handler) {
      // Special cased handlers
      handler(object, sendReply);
    } else {
      // Other methods get called on the bridge
      var returnValue = [[], [], [], 0];
      var error;
      try {
        if (typeof __fbBatchedBridge === 'object') {
          returnValue = __fbBatchedBridge[object.method].apply(null, object.arguments);
        } else {
          error = 'Failed to call function, __fbBatchedBridge is undefined';
        }
      } catch (err) {
        error = err.message;
      } finally {
        sendReply(JSON.stringify(returnValue), error);
      }
    }
  };
})();App在点击调试的时候会给浏览器还发送这么一个‘executeApplicationScript’消息,让浏览器去加载项目代码:
这个messageEvent的数据比较多,我就截取一部分,里面包含了方法名,url(这个url就是后面浏览器需要去下载bundle的地方),inject包含的数据最多,主要是会赋值给浏览器全局对象的方法。
{
  "id": 1,
  "method": "executeApplicationScript",
  "url": "http://localhost:8081/index.android.bundle?platform=android&dev=true&minify=false",
  "inject": {
    "__fbBatchedBridgeConfig": "{\"remoteModuleConfig\":[[\"AccessibilityInfo\",{},[\"isTouchExplorationEnabled\"]],[\"LocationObserver\",{},[\"getCurrentPosition\",\"startObserving\",\"stopObserving\"]],[\"CameraRollManager\",{},[\"getPhotos\",\"saveToCameraRoll\"],[0,1]],[\"NetInfo\",{},[\"getCurrentConnectivity\",\"isConnectionMetered\"],[0,1]],[\"PlatformConstants\",{\"ServerHost\":\"localhost:8081\",\"reactNativeVersion\":{\"patch\":0,\"prerelease\":null,\"minor\":51,\"major\":0},\"Version\":21,\"isTesting\":false}],[\"TimePickerAndroid\",{}
}webSocket首先接收到这个消息, 然后通过worker.postMessage给上面的worker发送‘executeApplicationScript’消息
ws.onmessage = async function(message) {
     ......
      // Special message that asks for a new JS runtime
      if (object.method === 'prepareJSRuntime') {
        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') {
        worker.postMessage({
          ...object,
          url: await getBlobUrl(object.url),
        });
      } else {
        // Otherwise, pass through to the worker.
        worker.postMessage(object);
      }
    };worker接收到这个消息在messageHandlers找到相应的处理方法,在里面首选循环取出inject里面的字段和value然后赋值给self,在这里我理解就是这个worker线程的全局对象,然后通过 importScripts(message.url)去加载bundle。
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);
      } catch (err) {
        error = err.message;
      }
      sendReply(null /* result */, error);
    },
    ......
  };为了证明我上面的分析没错,决定捉包看下发起的请求是不是这样的:
在加载bundle后面还有一个map,体积也很大,有1.74MB的体积,这个是用于映射bundle里面的代码成一个个工程项目里的类文件,这样就和在代码编译器里面调试效果一样了。
4.总结
根据上面的捉包请求简单总结下建立连接的过程,首先通过/launch-jsdevtools打开调试Tab,浏览器通过/debugger-proxy建立与Server的WebSocket连接,然后浏览器打开index.html文件,发起/debugger-ui/debuggerWorker.js建立后台线程,通过这个后台线程加载bundle。
到这里建立Debug连接的原理分析就差不多了,希望对小伙伴们有帮助,欢迎点赞和关注哈。
谢谢大家!
React Native Debug原理浅析的更多相关文章
- 《React Native 精解与实战》书籍连载「React Native 底层原理」
		此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ... 
- React Native & debug & debugger
		React Native & debug & debugger http://localhost:8081/debugger-ui/ react-devtools # yarn: $ ... 
- React Native运行原理解析
		Facebook 于2015年9月15日推出react native for Android 版本, 加上2014年底已经开源的IOS版本,至此RN (react-native)真正成为跨平台的客户端 ... 
- React Native 从入门到原理
		React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原理的文章却寥寥无几. 本文分为两个部分:上半部分用通 ... 
- 关于React Native 火热的话题,从入门到原理
		本文授权转载,作者:bestswifter(简书) React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原 ... 
- React Native 从入门到原理一
		React Native 从入门到原理一 React Native 是最近非常火的一个话题,介绍如何利用 React Native 进行开发的文章和书籍多如牛毛,但面向入门水平并介绍它工作原理的文章却 ... 
- React Native 入门到原理(详解)
		抛砖引玉(帮你更好的去理解怎么产生的 能做什么) 砖一.动态配置 由于 AppStore 审核周期的限制,如何动态的更改 app 成为了永恒的话题.无论采用何种方式,我们的流程总是可以归结为以下三部曲 ... 
- 小谈React、React Native、React Web
		React有三个东西,React JS 前端Web框架,React Native 移动终端Hybrid框架,React Web是一个源码转换工具(React Native 转 Web,并之所以特别提出 ... 
- ReactJs和React Native的那些事
		介绍 1,React Js的目的 是为了使前端的V层更具组件化,能更好的复用,它能够使用简单的html标签创建更多的自定义组件标签,内部绑定事件,同时可以让你从操作dom中解脱出来,只需要操作数据就会 ... 
随机推荐
- Linux基本操作_20191117
			VMware和Ubuntu的安装, 想来想去,还是需要安装,不能老是使用Windows的,后面还有很多都要用到Linux系统的,这个可以说是开发人员必备的了, 基本的使用: 1,Windows下面C: ... 
- 在Python 中怎么表示一个元素在一个list中的数量?
			commonest = [1,2,2,2,1,3,4,5,1,1] print(commonest.count(1)) 
- Struts配置文件以Spring的方式实现自定义加载
			在使用struts时,我们需要在web.xml中配置过滤器,同时我们需要配置struts的配置文件路径来加载项目中struts的相关配置信息.如果我们不配置路径的话,Struts会有一些默认的加载路径 ... 
- 吴裕雄--天生自然python学习笔记:python文档操作自动查找替换 Word 文件中的指定文字
			Win32com 组件提供了自动替换 Word 文件中指定文字 的功能 .在使用“查找” 功能替换文字之前,可先清除源文字及目标文字的格式,以免影响替换效果,语法为 : 替换 Word 文件特定文字的 ... 
- TreeMap简介
			在Map集合框架中,除了HashMap以外,TreeMap也是常用到的集合对象之一.与HashMap相比,TreeMap是一个能比较元素大小的Map集合,会对传入的key进行了大小排序.其中,可以使用 ... 
- django框架基础-ORM单表操作-长期维护
			############### 单表操作-添加数据 ################ import os if __name__ == '__main__': os.environ.set ... 
- android cpu affinity
			暂时无法获取当前线程运行在哪个CPU上,待调查... int omask = 0; int nmask = 0xF0; static void affinity() { int err; int sy ... 
- Python之循环条件、变量、字符串格式化
			一.认识python python语言的优缺点,自行百度,这里不概述,简单说下,python是一门面向对象,解释型计算机语言.那么问题来了,解释型和编译型语言有什么区别? 1.解释型和编译型语言区别 ... 
- 转载【docker】CMD ENTRYPOINT 的使用方法
			原文:https://blog.csdn.net/u010900754/article/details/78526443 
- mysql 优化一
			从几个方面出发: ① 数据库设计② sql语句优化③ 数据库参数配置④ 恰当的硬件资源和操作系统 下面详细介绍: ① 数据库设计 通俗地理解三个范式,对于数据库设计大有好处.在数据库设计中,为了更好地 ... 
