React Native 網路層分析

iKcamp發表於2018-01-19

文:志俊(滬江Web前端)

本文原創,轉載請註明作者及出處

在使用React Native開發中,我們熟練的採用JavaScript的方式傳送請求的方式傳送一個請求到服務端,但是處理這個請求的過程其實和處理Web應用中傳送的請求的過程是不一樣的。因為處理這個請求的目標不是瀏覽器,而是嵌入這個應用的原生作業系統。

banner

在處理React Native的請求時,分為兩部分:一部分是JavaScript的執行環境,另一部分是嵌入JavaScriptNative(即原生AndroidIOS)執行環境。React Native內建了三種傳送網路請求的方式:fetch, XMLHttpRequestWebSocket。但是React Native的執行環境和Web應用的執行環境不一樣,所以需要在原生應用層採用自定義函式來擴充執行時(runtime)環境來處理JavaScript發出的網路請求。

請求傳送方式及過程

banner
對於常用的網路請求物件:XMLHttpRequest(XHR)、Fetch及WebSocket,熟悉前端開發的同學應該非常瞭解。XHR是Web開發中用得比較多的傳送請求的方式,FetchWebsocket也是後起之秀,在很多現代Web應用中得以採用。但是,在React Native中,這些物件的使用和Web應用是有差別的。當你在JS層呼叫網路請求時,其實是經歷了兩個過程才到達真正的伺服器端。就像頭部banner表示的那樣。

XMLHttpRequest(XHR)

React Native中, XMLHttpRequest(XHR)由兩部分組成: “前端”(front-end)和“後端”(back-end)。前端負責與JavaScript互動,後端負責在原生平臺上轉換JavaScript傳送過來的請求為原生系統自己的請求。

這裡的後端其實是一個原生平臺頂層抽象的統一API層,使得JavaScript層可以呼叫原先系統的網路模組。例如IOS下內建的URLSession模組和Android下的OKHTTP模組。

Fetch

在現代Web瀏覽器中,FetchAPI提供了和XHR大部分相同的功能,但是Fetch提供了一種更加簡單,高效的方式來跨網路非同步獲取資源,同時可操縱RequestResponse物件來複用請求。

但是在React Native中,為了相容兩種平臺的差異,採用了依賴於XMLHttpRequestFetch Polyfill來實現這個請求物件。這就意味著我們不能像實用Web平臺下的Fetch物件一樣來實用React Native下的該物件。比如採用這個物件來傳送binary資料。當然可以採用第三方的庫比如react-native-fetch-blob來實現相應的功能。

Websocket

Websocket作為一種新的通訊協議,採用全雙工通訊方式與伺服器間進行通訊的網路技術。

React Native中,Websocket並不是一個獨立的請求,和XMLHttpRequest(XHR)一樣由兩部分組成: “前端”(front-end)和“後端”(back-end)。前端負責與JavaScript互動,後端負責在原生平臺上轉換JavaScript傳送過來的請求為原生系統自己的請求。在IOS中採用的是自己開發的NSStream,而在Android系統中則是OKHTTP模組。

檢視React Native中的網路請求

React Native開發中,你可以通過Chrome Developer Tools (CDT)Sources皮膚中除錯javascript部分的程式碼,包括斷點、輸出資訊、斷點除錯等一切javascript除錯所需的資訊。但是,唯一缺少的就是網路請求的跟蹤除錯。我們沒辦法像Web開發那樣,可以通過CDT中的網路皮膚(Network)來檢視應用的網路請求的相關資訊。

使用代理除錯網路請求

雖然沒有辦法通過CDT檢視應用的網路請求,但是我們可以通過FiddlerCharlesProxyWireshark等軟體設定代理,來檢視追蹤除錯網路請求。這裡使用Fiddler來作為代理。

  1. 首先設定Fiddler的代理埠: 開啟Filddler -> Tool -> Options -> Connects,在監聽埠處填寫相應的埠號,

    fiddler設定

  2. 在除錯機器上、Android或者IOS模擬器模擬器中設定代理: 找到除錯的機器上的網路設定中,設定當前連線的WIFI的代理地址

    fiddler設定

  3. 重新整理應用,在fiddler中檢視網路請求(提示:右鍵,在新頁籤中開啟可檢視清晰圖片):

    fiddler設定

在代理應用中,我們可以檢視請求頭,返回頭,返回結果等相關的網路資訊。當然,還可以根據相關代理軟體攔截請求,重新設定後傳送。

使用Reactotron除錯網路

上面通過設定代理的方式來檢視和追蹤網路請求,雖然功能強大,但是實際操作起來有些難度,上手成本比較高。通過使用Reactotron,可以將除錯的配置資訊整合到應用中,方便在不同的開發環境下有相同的除錯配置,節約開發配置成本。

Reactotron由兩部分組成,一部分是除錯應用,一部分是除錯配置。

  1. 除錯應用分別有各個作業系統的GUI安裝版本。當然,如果習慣使用命令列,也可以使用NPM安裝reactotron-cli
npm install -g reactotron-cli
複製程式碼

reactotron設定

  1. 設定除錯配置:
在你的`React Native`應用中安裝`reactotron-react-native`

  ```
  npm i --save-dev reactotron-react-native
  ```
  然後,在你的應用的新增配置檔案,定製除錯內容:

  ```
  import Reactotron, {
    trackGlobalErrors,
    openInEditor,
    overlay,
    asyncStorage,
    networking
  } from 'reactotron-react-native'
  Reactotron
    .configure({
      name: 'xxx'                 // 除錯的名稱
    })
    .use(trackGlobalErrors())     // 設定監聽全域性錯誤
    .use(openInEditor())          // 設定在編輯器中開啟錯誤
    .use(overlay())               // 設定圖片遮蓋圖片(用於UI還原度對比)
    .use(asyncStorage())          // 設定非同步儲存除錯
    .use(networking())            // 設定網路除錯
    .connect()                    // 連線應用(必須)

  ```
  然後在你的應用的入口檔案中引入這個配置檔案。然後重新啟動應用。當然,還可以使用正規表示式過濾請求的`contentType`的型別和要忽略的請求的`url`,見下面的配置:

  ```
   .use(networking({
      ignoreContentTypes: /^(image)\/.*$/i,   // 設定reactotron要忽略的檔案型別
      ignoreUrls: /\/(logs|symbolicate)$/,    // 設定reactotron要忽略的url請求路徑
   }))
  ```
![reactotron設定](https://raw.githubusercontent.com/yandan66/blogs/master/source/_posts/20171124/reactotron_02.png)
![reactotron設定](https://raw.githubusercontent.com/yandan66/blogs/master/source/_posts/20171124/reactotron_03.png)
![reactotron結果](https://gitlab.yeshj.com/hujiang-f2e/fun-book/raw/5f6236238c0e51a085d3be32b349436c5e3dad73/%E9%97%A8%E6%88%B7%E7%BA%BF/image/ezgif.com-resize.gif)
複製程式碼

reactotron除錯網路只是他的一個功能之一,其他還有很多強大的功能。有興趣可以檢視他的文件

使用Chrome Developer Tools網路皮膚除錯網路

React Native預設暴露出來的介面中,是沒有直接在Chrome Developer Tools檢視網路請求的方法的,檢視 RN 原始碼 Libraries/Core/InitializeCore.js,註釋中寫著:

Sets an object’s property. If a property with the same name exists, this will replace it but maintain its descriptor configuration. By default, the property will replaced with a lazy getter. * The original property value will be preserved as original[PropertyName] so that, if necessary, it can be restored. For example, if you want to route network requests through DevTools (to trace them): * global.XMLHttpRequest = global.originalXMLHttpRequest; * @see github.com/facebook/re…

具體實現在XHRInterceptor.js中。原來的XMLHttpRequest被改寫成了 originalXMLHttpRequest,所以要在Chrome 中顯示network 只需要替換XMLHttpRequestoriginalXMLHttpRequest。在入口檔案處設定:

if (__DEV__) {
  GLOBAL.XMLHttpRequest = GLOBAL.originalXMLHttpRequest || GLOBAL.XMLHttpRequest
}
複製程式碼

當然,這樣有可能會產生CORS, Chrome 會限制跨域請求。這時要麼後端配合一下去除限制,要麼使用 Allow-Control-Allow-Origin: * 外掛。

React Native傳送二進位制資料(binary data )

由於React NativeFetch物件的底層採用的是XHR實現,這就限制了傳送二進位制資料的功能。當然React Native提供了一系列的方式來解決這個問題,比如: 轉換二進位制檔案為base64字串或者採用第三方庫 react-native-fetch-blob。但是並沒有從底層解決這個問題。

轉換二進位制為base64傳送

到目前為止,React Native不能傳送非序列化的資料,所以,要傳送二進位制資料,採用Base64編碼的字串是個不錯的選擇。

banner

例如,你從伺服器下載一張圖片(注意:不是通過url從伺服器獲取),請求通過JavaScript執行緒,再通過React Native提供的橋接器,最後通過原生系統的網路模組傳送到服務端。服務端返回一個Base64編碼過的圖片,JavaScript執行緒收到返回的字串後,會分配相應的記憶體,然後React Native會呼叫相應的原生模組渲染成相應圖片。但是值得主要的是,這種方式會造成典型的效能問題——記憶體洩漏。

通過Base64編碼的方式傳輸二進位制檔案,這裡會造成一系列效能問題,這篇文章中列出了大部分效能問題及提出了相應的解決方案。

現在使用的各種方法傳送二進位制檔案都存在各種問題,最終的解決方式是要相應的標準能夠實現二進位制的傳輸。目前,WebSocket已經支援了二進位制傳輸。在最新版本的React Native層也已經支援WebSocket協議來傳輸二進位制檔案,但是,相應的原生平臺的網路模組暫時還不支援。

總結

React Native開發方式是非常不錯的體驗,但是,受各個平臺差異和標準的限制,不得不折中處理一些問題。隨之而來的是相應的效能、效率的問題。另外,採用開發,效能上和使用者體驗上和原生應用還是有一定差距。但是如果在原生應用中能夠整合React Native,會顯著提高開發效率。

參考

Network layer in React Native

reactotron介紹

reactotron

Reactotron on React Native

React Native 網路層分析

React Native 網路層分析

推薦: 翻譯專案Master的自述:

1. 乾貨|人人都是翻譯專案的Master

2. iKcamp出品微信小程式教學共5章16小節彙總(含視訊)

3. 開始免費連載啦~每週2更共11堂iKcamp課|基於Koa2搭建Node.js實戰專案教學(含視訊)| 課程大綱介紹


React Native 網路層分析

2019年,iKcamp原創新書《Koa與Node.js開發實戰》已在京東、天貓、亞馬遜、噹噹開售啦!

相關文章