国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

React Native Debug原理淺析

senntyou / 2613人閱讀

摘要:過程涉及到三個對象,一個是或,一個是,另外一個就是瀏覽器或或其他。在中進行配置了,也就是會執(zhí)行腳本。然后會給這個注冊一些監(jiān)聽在收到消息時會回調(diào)。發(fā)出一個消息讓瀏覽器準備的運行環(huán)境在收到消息會調(diào)用。

第一次在segmentfault寫博客,很緊張~~~公司項目上ReactNative,之前也是沒有接觸過,所以也是一邊學習一邊做項目了,最近騰出手來更新總結(jié)了一下RN的Debug的一個小知識點,不是說怎么去Debug,而是Debug的代碼原理,下面開始正文。

Debug過程涉及到三個對象,一個是App(Android或iOS),一個是Server,另外一個就是瀏覽器(Chrome或FireFox或其他)。Server是App和瀏覽器之間通信的橋梁,比如App發(fā)Http請求給Server,Server再通過WebSocket發(fā)送給瀏覽器,反過來也是。首先肯定需要準備一下中介,就是Server

1.Server

這里的Server不用專門準備一臺服務(wù)器,只需要配置一個Node.js環(huán)境,然后啟動npm start就行。npm start在package.json中進行配置了,也就是會執(zhí)行cli.js腳本。

"scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start"
  },

然后cli.js會執(zhí)行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與瀏覽器之間的關(guān)系了。

2.建立連接

在手機菜單中點擊Debug JS Remotely,App就會發(fā)出一個Http請求

GET /launch-js-devtools HTTP/1.1

Server接收到這個請求會執(zhí)行opn操作,主要做兩件事:

打開Chrome的一個tab

讓這個tab打開urlhttp://localhost:8081/debugger-ui/

這個界面就是我們打開Debug時在瀏覽器見到的第一個界面

這個界面的文件就是Server的index.html,我截取了body的代碼:


  

React Native JS code runs as a web worker inside this tab.

Press ??I to open Developer Tools. Enable Pause On Caught Exceptions for a better debugging experience.

You may also install the standalone version of React Developer Tools to inspect the React component hierarchy, their props, and state.

Status: Loading...

瀏覽器在執(zhí)行index.html的時候會發(fā)出下面的請求:

GET /debugger-proxy?role=debugger&name=Chrome HTTP/1.1

我們來看看發(fā)出這個請求有什么目的,扒一扒源碼:

function connectToDebuggerProxy() {
    const ws = new WebSocket("ws://" + window.location.host + "/debugger-proxy?role=debugger&name=Chrome"); //Chrome通過websocket和Packager保持通訊
  
      //WebSocket注冊監(jiān)聽
    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的聯(lián)系,WebSocket就是可以保持長連接的全雙工通信協(xié)議,在握手階段通過Http進行,后面就和Http沒有什么關(guān)系了。然后會給這個webSocket注冊一些監(jiān)聽:

ws.onopen 
ws.onmessage
ws.onclose

在webSocket收到消息時會回調(diào)ws.onmessage。

到這里App和瀏覽器之間就已經(jīng)建立連接了,接下來App會發(fā)出幾個消息讓瀏覽器加載需要調(diào)試的代碼, 接著往下看。

3.加載調(diào)試代碼

首先需要強調(diào)的就是瀏覽器加載項目代碼肯定不能在UI線程加載吧,要不然肯定影響瀏覽器的正常工作。那怎么去加載?啟一個后臺線程,有的小伙伴就要不信了,別急,我們接著去扒一扒源碼。
App發(fā)出一個消息讓瀏覽器準備JS的運行環(huán)境:

在收到‘prepareJSRuntime’消息會調(diào)用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’這個函數(shù), 主要工作就是‘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,主要就是一個消息的監(jiān)聽,可以看到在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在點擊調(diào)試的時候會給瀏覽器還發(fā)送這么一個‘executeApplicationScript’消息,讓瀏覽器去加載項目代碼:

這個messageEvent的數(shù)據(jù)比較多,我就截取一部分,里面包含了方法名,url(這個url就是后面瀏覽器需要去下載bundle的地方),inject包含的數(shù)據(jù)最多,主要是會賦值給瀏覽器全局對象的方法。

{
  "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發(fā)送‘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找到相應的處理方法,在里面首選循環(huán)取出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);
    },
    ......
  };

為了證明我上面的分析沒錯,決定捉包看下發(fā)起的請求是不是這樣的:

在加載bundle后面還有一個map,體積也很大,有1.74MB的體積,這個是用于映射bundle里面的代碼成一個個工程項目里的類文件,這樣就和在代碼編譯器里面調(diào)試效果一樣了。

4.總結(jié)

根據(jù)上面的捉包請求簡單總結(jié)下建立連接的過程,首先通過/launch-jsdevtools打開調(diào)試Tab,瀏覽器通過/debugger-proxy建立與Server的WebSocket連接,然后瀏覽器打開index.html文件,發(fā)起/debugger-ui/debuggerWorker.js建立后臺線程,通過這個后臺線程加載bundle。

到這里建立Debug連接的原理分析就差不多了,希望對小伙伴們有幫助,歡迎點贊和關(guān)注哈。

謝謝大家!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/93343.html

相關(guān)文章

  • React Native Debug原理淺析

    摘要:過程涉及到三個對象,一個是或,一個是,另外一個就是瀏覽器或或其他。在中進行配置了,也就是會執(zhí)行腳本。然后會給這個注冊一些監(jiān)聽在收到消息時會回調(diào)。發(fā)出一個消息讓瀏覽器準備的運行環(huán)境在收到消息會調(diào)用。 第一次在segmentfault寫博客,很緊張~~~公司項目上ReactNative,之前也是沒有接觸過,所以也是一邊學習一邊做項目了,最近騰出手來更新總結(jié)了一下RN的Debug的一個小知識...

    avwu 評論0 收藏0
  • 淺析React之事件系統(tǒng)(二)

    摘要:因為阻止事件冒泡的行為只能用于合成事件中,沒法阻止原生事件的冒泡。同時的創(chuàng)建和冒泡是在原生事件冒泡到最頂層的之后的。淺析之事件系統(tǒng)一 上篇文章中,我們談到了React事件系統(tǒng)的實現(xiàn)方式,和在React中使用原生事件的方法,那么這篇文章我們來繼續(xù)分析下,看看React中合成事件和原生事件混用的各種情況。 上一個例子 在上篇文章中,我們舉了個例子。為了防止大家不記得,我們來看看那個例子的代...

    villainhr 評論0 收藏0
  • SegmentFault 技術(shù)周刊 Vol.4 - 這份 Android 有點甜

    摘要:閱讀本期周刊,你將快速入門,開啟甜蜜之旅。然則的原理負責發(fā)送以及處理消息,創(chuàng)建消息隊列并不斷從隊列中取出消息交給,則用于保存消息。 showImg(/img/bVCN99?w=900&h=385); 2016 年 8 月,Android 7.0 Nougat(牛軋?zhí)牵┱桨l(fā)布,那么問題來了,你 Marshmallow 了么(? -? ?) Cupcake、Donut、Gingerbre...

    jay_tian 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<