React Native之原理淺析

原文鏈接

一汁讼、JavaScriptCore

講React Native之前唁情,了解JavaScriptCore會(huì)有幫助梧税,也是必要的占业。React Native的核心驅(qū)動(dòng)力就來自于JS Engine. 你寫的所有JS和JSX代碼都會(huì)被JS Engine來執(zhí)行, 沒有JS Engine的參與,你是無法享受ReactJS給原生應(yīng)用開發(fā)帶來的便利的君旦。在iOS上澎办,默認(rèn)的就是JavaScriptCore嘲碱, iOS 7之后的設(shè)備都支持. iOS 不允許用自己的JS Engine. JavaScriptCore來自于WebKit, 所以金砍,安卓上默認(rèn)也是用JavaScriptCore

你深入了解React Native的第一站應(yīng)該是 JavaScriptCore

  • JavaScriptCoreiOS平臺(tái)上給React Native提供的接口也僅限于那幾個(gè)接口局蚀,你弄明白了JavaScriptCore那幾個(gè)接口, React Native 剩下的魔法秘密都可以順藤摸瓜來分析了。
  • 接下來要講解的就是Facebook圍繞這幾個(gè)接口以及用一個(gè)React來顛覆整個(gè)native開發(fā)所做的精妙設(shè)計(jì)和封裝

二恕稠、瀏覽器工作原理

  • 瀏覽器通過Dom Render來渲染所有的元素.
  • 瀏覽器有一整套的UI控件琅绅,樣式和功能都是按照html標(biāo)準(zhǔn)實(shí)現(xiàn)的
  • 瀏覽器能讀懂html和css。
  • html告訴瀏覽器繪制什么控件(html tag)鹅巍,css告訴瀏覽器每個(gè)類型的控件(html tag)具體長(zhǎng)什么樣千扶。
  • 瀏覽器的主要作用就是通過解析html來形成dom樹,然后通過css來點(diǎn)綴和裝飾樹上的每一個(gè)節(jié)點(diǎn)

UI的描述和呈現(xiàn)分離開了

  1. html文本描述了頁面應(yīng)該有哪些功能骆捧,css告訴瀏覽器該長(zhǎng)什么樣澎羞。
  2. 瀏覽器引擎通過解析html和css,翻譯成一些列的預(yù)定義UI控件敛苇,
  3. 然后UI控件去調(diào)用操作系統(tǒng)繪圖指令去繪制圖像展現(xiàn)給用戶妆绞。
  4. Javascript可有可無,主要用于html里面一些用戶事件響應(yīng)枫攀,DOM操作括饶、異步網(wǎng)絡(luò)請(qǐng)求和一些簡(jiǎn)單的計(jì)算

在react native 里面,1和2是不變的来涨,也是用html語言描述頁面有哪些功能图焰,然后stylesheet告訴瀏覽器引擎每個(gè)控件應(yīng)該長(zhǎng)什么樣。并且和瀏覽器用的是同一個(gè)引擎

在步驟3里面UI控件不再是瀏覽器內(nèi)置的控件蹦掐,而是react native自己實(shí)現(xiàn)的一套UI控件(兩套技羔,android一套,ios一套)卧抗,這個(gè)切換是在MessageQueque中進(jìn)行的堕阔,并且還可以發(fā)現(xiàn),他們tag也是不一樣的

Javascript在react native里面非常重要

  • 它負(fù)責(zé)管理UI component的生命周期颗味,管理Virtual DOM
  • 所有業(yè)務(wù)邏輯都是用javascript來實(shí)現(xiàn)或者銜接
  • 調(diào)用原生的代碼來操縱原生組件超陆。
  • Javascript本身是無繪圖能力的,都是通過給原生組件發(fā)指令來完成

三浦马、React Native 架構(gòu)

680.jpg
  • 綠色的是我們應(yīng)用開發(fā)的部分时呀。我們寫的代碼基本上都是在這一層
  • 藍(lán)色代表公用的跨平臺(tái)的代碼和工具引擎,一般我們不會(huì)動(dòng)藍(lán)色部分的代碼
  • 黃色代碼平臺(tái)相關(guān)的代碼晶默,做定制化的時(shí)候會(huì)添加修改代碼谨娜。不跨平臺(tái),要針對(duì)平臺(tái)寫不同的代碼磺陡。iOS寫OC, android寫java趴梢,web寫js. 每個(gè)bridge都有對(duì)應(yīng)的js文件漠畜,js部分是可以共享的,寫一份就可以了坞靶。如果你想做三端融合憔狞,你就得理解這一個(gè)東西。如果你要自己定制原生控件彰阴,你就得寫bridge部分
  • 紅色部分是系統(tǒng)平臺(tái)的東西瘾敢。紅色上面有一個(gè)虛線,表示所有平臺(tái)相關(guān)的東西都通過bridge隔離開來了
  • 大部分情況下我們只用寫綠色的部分尿这,少部分情況下會(huì)寫黃色的部分簇抵。你如果對(duì)基礎(chǔ)架構(gòu)和開源感興趣,你可以寫藍(lán)色部分射众,然后嘗試給那些大的開源項(xiàng)目提交代碼碟摆。紅色部分是獨(dú)立于React Native的

四、React Native叨橱、React和JavascriptCore的關(guān)系

React Native最重要的三個(gè)概念應(yīng)該就是React Native典蜕、ReactJavascriptCore

  • React是一個(gè)純JS庫,所有的React代碼和所有其它的js代碼都需要JS Engine來解釋執(zhí)行雏逾。因?yàn)榉N種原因嘉裤,瀏覽器里面的JS代碼是不允許調(diào)用自定義的原生代碼的,而React又是為瀏覽器JS開發(fā)的一套庫栖博,所以屑宠,比較容易理解的事實(shí)是React是一個(gè)純JS庫,它封裝了一套Virtual Dom的概念仇让,實(shí)現(xiàn)了數(shù)據(jù)驅(qū)動(dòng)編程的模式典奉,為復(fù)雜的Web UI實(shí)現(xiàn)了一種無狀態(tài)管理的機(jī)制, 標(biāo)準(zhǔn)的HTML/CSS之外的事情,它無能為力丧叽。調(diào)用原生控件卫玖,驅(qū)動(dòng)聲卡顯卡,讀寫磁盤文件踊淳,自定義網(wǎng)絡(luò)庫等等假瞬,這是JS/React無能為力的
  • 你可以簡(jiǎn)單理解為React是一個(gè)純JS 函數(shù), 它接受特定格式的字符串?dāng)?shù)據(jù)迂尝,輸出計(jì)算好的字符串?dāng)?shù)據(jù)
  • JS Engine負(fù)責(zé)調(diào)用并解析運(yùn)行這個(gè)函數(shù)
  • React Native呢脱茉? 它比較復(fù)雜。復(fù)雜在哪里垄开?前面我們說了React 是純JS庫琴许,意味著React只能運(yùn)行JS代碼,通過JS Engine提供的接口(Html Tag)繪制html支持的那些元素溉躲,驅(qū)動(dòng)有限的聲卡顯卡榜田。簡(jiǎn)單點(diǎn)說, React只能做瀏覽器允許它做的事情, 不能調(diào)用原生接口益兄, 很多的事情也只能干瞪眼

React Native它可不一樣

  • 第一點(diǎn),驅(qū)動(dòng)關(guān)系不一樣箭券。前面我們說的是, JS Engine來解析執(zhí)行React腳本, 所以净捅,React由瀏覽器(最終還是JS Engine)來驅(qū)動(dòng). 到了React Native這里,RN的原生代碼(Timer和用戶事件)驅(qū)動(dòng)JS Engine, 然后JS Engine解析執(zhí)行React或者相關(guān)的JS代碼邦鲫,然后把計(jì)算好的結(jié)果返回給Native code. 然后, Native code 根據(jù)JS計(jì)算出來的結(jié)果驅(qū)動(dòng)設(shè)備上所有能驅(qū)動(dòng)的硬件灸叼。重點(diǎn)神汹,所有的硬件庆捺。也就是說,在RN這里屁魏,JS代碼已經(jīng)擺脫JS Engine(瀏覽器)的限制滔以,可以調(diào)用所有原生接口啦
  • 第二點(diǎn), 它利用React的Virtual Dom和數(shù)據(jù)驅(qū)動(dòng)編程概念,簡(jiǎn)化了我們?cè)鷳?yīng)用的開發(fā), 同時(shí)氓拼,它不由瀏覽器去繪制你画,只計(jì)算出繪制指令,最終的繪制還是由原生控件去負(fù)責(zé)桃漾,保證了原生的用戶體驗(yàn)

React Native組件結(jié)構(gòu)

驅(qū)動(dòng)硬件的能力決定能一個(gè)軟件能做多大的事情坏匪,有多大的主控性。研究過操作系統(tǒng)底層?xùn)|西或者匯編的同學(xué)明白撬统,我們大部分時(shí)候?qū)懙拇a是受限的代碼适滓,很多特權(quán)指令我們是沒法使用的,很多設(shè)備我們是不允許直接驅(qū)動(dòng)的恋追。我們現(xiàn)在的編程里面幾乎已經(jīng)沒有人提中斷了凭迹,沒有中斷,硬件的操作幾乎會(huì)成為一場(chǎng)災(zāi)難.

在一定程度上苦囱,React Native和NodeJS有異曲同工之妙嗅绸。它們都是通過擴(kuò)展JavaScript Engine, 使它具備強(qiáng)大的本地資源和原生接口調(diào)用能力,然后結(jié)合JavaScript豐富的庫和社區(qū)和及其穩(wěn)定的跨平臺(tái)能力撕彤,把javascript的魔力在瀏覽器之外的地方充分發(fā)揮出來

JavaScriptCore + ReactJS + Bridges 就成了React Native

  • JavaScriptCore負(fù)責(zé)JS代碼解釋執(zhí)行
  • ReactJS負(fù)責(zé)描述和管理VirtualDom,指揮原生組件進(jìn)行繪制和更新鱼鸠,同時(shí)很多計(jì)算邏輯也在js里面進(jìn)行。ReactJS自身是不直接繪制UI的,UI繪制是非常耗時(shí)的操作薪铜,原生組件最擅長(zhǎng)這事情桃纯。
  • Bridges用來翻譯ReactJS的繪制指令給原生組件進(jìn)行繪制,同時(shí)把原生組件接收到的用戶事件反饋給ReactJS造锅。
    要在不同的平臺(tái)實(shí)現(xiàn)不同的效果就可以通過定制Bridges來實(shí)現(xiàn)

深入 Bridge 前面有提到, RN厲害在于它能打通JS和Native Code, 讓JS能夠調(diào)用豐富的原生接口,充分發(fā)揮硬件的能力, 實(shí)現(xiàn)非常復(fù)雜的效果,同時(shí)能保證效率和跨平臺(tái)性。

打通RN任督二脈的關(guān)鍵組件就是Bridge. 在RN中如果沒有Bridge, JS還是那個(gè)JS廉邑,只能調(diào)用JS Engine提供的有限接口哥蔚,繪制標(biāo)準(zhǔn)html提供的那些效果,那些攝像頭倒谷,指紋,3D加速,聲卡, 視頻播放定制等等糙箍,JS都只能流流口水渤愁,原生的、平臺(tái)相關(guān)的深夯、設(shè)備相關(guān)的效果做不了抖格, 除非對(duì)瀏覽器進(jìn)行定制

  • Bridge的作用就是給RN內(nèi)嵌的JS Engine提供原生接口的擴(kuò)展供JS調(diào)用。所有的本地存儲(chǔ)咕晋、圖片資源訪問雹拄、圖形圖像繪制、3D加速掌呜、網(wǎng)絡(luò)訪問滓玖、震動(dòng)效果、NFC质蕉、原生控件繪制势篡、地圖、定位模暗、通知等都是通過Bridge封裝成JS接口以后注入JS Engine供JS調(diào)用禁悠。理論上,任何原生代碼能實(shí)現(xiàn)的效果都可以通過Bridge封裝成JS可以調(diào)用的組件和方法, 以JS模塊的形式提供給RN使用兑宇。
  • 每一個(gè)支持RN的原生功能必須同時(shí)有一個(gè)原生模塊和一個(gè)JS模塊碍侦,JS模塊是原生模塊的封裝,方便Javascript調(diào)用其接口顾孽。Bridge會(huì)負(fù)責(zé)管理原生模塊和對(duì)應(yīng)JS模塊之間的溝通, 通過Bridge, JS代碼能夠驅(qū)動(dòng)所有原生接口祝钢,實(shí)現(xiàn)各種原生酷炫的效果。
  • RN中JS和Native分隔非常清晰若厚,JS不會(huì)直接引用Native層的對(duì)象實(shí)例拦英,Native也不會(huì)直接引用JS層的對(duì)象實(shí)例(所有Native和JS互掉都是通過Bridge層會(huì)幾個(gè)最基礎(chǔ)的方法銜接的)。
  • Bridge 原生代碼負(fù)責(zé)管理原生模塊并生成對(duì)應(yīng)的JS模塊信息供JS代碼調(diào)用测秸。每個(gè)功能JS層的封裝主要是針對(duì)ReactJS做適配疤估,讓原生模塊的功能能夠更加容易被用ReactJS調(diào)用。MessageQueue.jsBridge在JS層的代理霎冯,所有JS2N和N2JS的調(diào)用都會(huì)經(jīng)過MessageQueue.js來轉(zhuǎn)發(fā)铃拇。JS和Native之間不存在任何指針傳遞,所有參數(shù)都是字符串傳遞沈撞。所有的instance都會(huì)被在JS和Native兩邊分別編號(hào)慷荔,然后做一個(gè)映射,然后那個(gè)數(shù)字/字符串編號(hào)會(huì)做為一個(gè)查找依據(jù)來定位跨界對(duì)象。

五缠俺、Bridge各模塊簡(jiǎn)介

5.1 RCTRootView

  • RCTRootViewReact Native加載的地方,是萬物之源显晶。從這里開始贷岸,我們有了JS Engine, JS代碼被加載進(jìn)來,對(duì)應(yīng)的原生模塊也被加載進(jìn)來磷雇,然后js loop開始運(yùn)行偿警。 js loop的驅(qū)動(dòng)來源是Timer和Event Loop(用戶事件). js loop跑起來以后應(yīng)用就可以持續(xù)不停地跑下去了。
  • 如果你要通過調(diào)試來理解RN底層原理唯笙,你也應(yīng)該是從RCTRootView著手螟蒸,順藤摸瓜。
  • 每個(gè)項(xiàng)目的AppDelegate.m的- (BOOL)application:didFinishLaunchingWithOptions:里面都可以看到RCTRootView的初始化代碼崩掘,RCTRootView初始化完成以后七嫌,整個(gè)React Native運(yùn)行環(huán)境就已經(jīng)初始化好了,JS代碼也加載完畢呢堰,所有React的繪制都會(huì)有這個(gè)RCTRootView來管理抄瑟。

RCTRootView做的事情如下

  • 創(chuàng)建并且持有RCTBridge
  • 加載JS Bundle并且初始化JS運(yùn)行環(huán)境.
  • 初始化JS運(yùn)行環(huán)境的時(shí)候在App里面顯示loadingView, 注意不是屏幕頂部的那個(gè)下拉懸浮進(jìn)度提示條. RN第一次加載之后每次啟動(dòng)非撤财快枉疼,很少能意識(shí)到這個(gè)加載過程了。loadingView默認(rèn)情況下為空, 也就是默認(rèn)是沒有效果的鞋拟。loadingView可以被自定義骂维,直接覆蓋RCTRootView.loadingView就可以了.開發(fā)模式下RN app第一次啟動(dòng)因?yàn)樾枰暾虬麄€(gè)js所以可以很明顯看到加載的過程,加載第一次以后就看不到很明顯的加載過程了贺纲,可以執(zhí)行下面的命令來觸發(fā)重新打包整個(gè)js來觀察loadingView的效果 watchman watch-del-all && rm -rf node_modules/ && yarn install && yarn start – –reset-cache, 然后殺掉app重啟你就會(huì)看到一個(gè)很明顯的進(jìn)度提示.
  • JS運(yùn)行環(huán)境準(zhǔn)備好以后把加載視圖用RCTRootContentView替換加載視圖
  • 有準(zhǔn)備工作就緒以后調(diào)用AppRegistry.runApplication正式啟動(dòng)RN JS代碼航闺,從Root Component()開始UI繪制

一個(gè)App可以有多個(gè)RCTRootView, 初始化的時(shí)候需要手動(dòng)傳輸Bridge做為參數(shù),全局可以有多個(gè)RCTRootView, 但是只能有一個(gè)Bridge

如果你做過React Native和原生代碼混編猴誊,你會(huì)發(fā)現(xiàn)混編就是把AppDelegate里面那段初始化RCTRootView的代碼移動(dòng)到需要混編的地方潦刃,然后把RCTRootView做為一個(gè)普通的subview來加載到原生的view里面去,非常簡(jiǎn)單懈叹。不過這地方也要注意處理好單Bridge實(shí)例的問題乖杠,同時(shí),混編里面要注意RCTRootView如果銷毀過早可能會(huì)引發(fā)JS回調(diào)奔潰的問題

5.2 RCTRootContentView

  • RCTRootContentView reactTag在默認(rèn)情況下為1. 在Xcode view Hierarchy debugger 下可以看到澄成,最頂層為RCTRootView, 里面嵌套的是RCTRootContentView, 從RCTRootContentView開始胧洒,每個(gè)View都有一個(gè)reactTag
  • RCTRootView繼承自UIView, RCTRootView主要負(fù)責(zé)初始化JS Environment和React代碼,然后管理整個(gè)運(yùn)行環(huán)境的生命周期墨状。 RCTRootContentView繼承自RCTView, RCTView繼承自UIView, RCTView封裝了React Component Node更新和渲染的邏輯卫漫, RCTRootContentView會(huì)管理所有react ui components. RCTRootContentView同時(shí)負(fù)責(zé)處理所有touch事件

5.3 RCTBridge

這是一個(gè)加載和初始化專用類,用于前期JS的初始化和原生代碼的加載

  • 負(fù)責(zé)加載各個(gè)Bridge模塊供JS調(diào)用
  • 找到并注冊(cè)所有實(shí)現(xiàn)了RCTBridgeModule protocol的類, 供JS后期使用.
  • 創(chuàng)建和持有 RCTBatchedBridge

5.4 RCTBatchedBridge

如果RCTBridge是總裁, 那么RCTBatchedBridge就是副總裁肾砂。前者負(fù)責(zé)發(fā)號(hào)施令列赎,后者負(fù)責(zé)實(shí)施落地

  • 負(fù)責(zé)Native和JS之間的相互調(diào)用(消息通信)
  • 持有JSExecutor
  • 實(shí)例化所有在RCTBridge里面注冊(cè)了的native node_modules
  • 創(chuàng)建JS運(yùn)行環(huán)境, 注入native hooksmodules, 執(zhí)行 JS bundle script
  • 管理JS run loop, 批量把所有JS到native的調(diào)用翻譯成native invocations
  • 批量管理原生代碼到JS的調(diào)用,把這些調(diào)用翻譯成JS消息發(fā)送給JS executor

5.5 RCTJavaScriptLoader

這是實(shí)現(xiàn)遠(yuǎn)程代碼加載的核心镐确。熱更新包吝,開發(fā)環(huán)境代碼加載肛根,靜態(tài)jsbundle加載都離不開這個(gè)工具。

  • 從指定的地方(bundle, http server)加載 script bundle
  • 把加載完成的腳本用string的形式返回
  • 處理所有獲取代碼漏策、打包代碼時(shí)遇到的錯(cuò)誤

5.6 RCTContextExecutor

  • 封裝了基礎(chǔ)的JS和原生代碼互掉和管理邏輯派哲,是JS引擎切換的基礎(chǔ)。通過不同的RCTCOntextExecutor來適配不同的JS Engine掺喻,讓我們的React JS可以在iOS芭届、Android、chrome甚至是自定義的js engine里面執(zhí)行感耙。這也是為何我們能在chrome里面直接調(diào)試js代碼的原因
  • 管理和執(zhí)行所有N2J調(diào)用

5.7 RCTModuleData

  • 加載和管理所有和JS有交互的原生代碼褂乍。把需要和JS交互的代碼按照一定的規(guī)則自動(dòng)封裝成JS模塊
  • 收集所有橋接模塊的信息,供注入到JS運(yùn)行環(huán)境

5.8 RCTModuleMethod

記錄所有原生代碼的導(dǎo)出函數(shù)地址(JS里面是不能直接持有原生對(duì)象的)即硼,同時(shí)生成對(duì)應(yīng)的字符串映射到該函數(shù)地址逃片。JS調(diào)用原生函數(shù)的時(shí)候會(huì)通過message的形式調(diào)用過來

  • 記錄所有的原生代碼的函數(shù)地址,并且生成對(duì)應(yīng)的字符串映射到該地址
  • 記錄所有的block的地址并且映射到唯一的一個(gè)id
  • 翻譯所有J2N call只酥,然后執(zhí)行對(duì)應(yīng)的native方法
  • 如果是原生方法的調(diào)用則直接通過方法名調(diào)用褥实,MessageQueue會(huì)幫忙把Method翻譯成MethodID, 然后轉(zhuǎn)發(fā)消息給原生代碼,傳遞函數(shù)簽名和參數(shù)給原生MessageQueue, 最終給RCTModuleMethod解析調(diào)用最終的方法
  • 如果JS調(diào)用的是一個(gè)回調(diào)block裂允,MessageQueue會(huì)把回調(diào)對(duì)象轉(zhuǎn)化成一個(gè)一次性的block id, 然后傳遞給RCTModuleMethod, 最終由RCTModuleMethod解析調(diào)用损离。基本上和方法調(diào)用一樣绝编,只不過生命周期會(huì)不一樣僻澎,block是動(dòng)態(tài)生成的,要及時(shí)銷毀十饥,要不然會(huì)導(dǎo)致內(nèi)存泄漏

實(shí)際上是不存在原生MessageQueue對(duì)象模塊的窟勃,JS的MessageQueue對(duì)應(yīng)到原生層就是RCTModuleData & RCTModuleMethod的組合, MessageQueue的到原生層的調(diào)用先經(jīng)過RCTModuleData和RCTModuleMethod翻譯成原生代碼調(diào)用,然后執(zhí)行

5.9 MessageQueue

  • 這是核心中的核心逗堵。整個(gè)react native對(duì)瀏覽器內(nèi)核是未做任何定制的秉氧,完全依賴瀏覽器內(nèi)核的標(biāo)準(zhǔn)接口在運(yùn)作。它怎么實(shí)現(xiàn)UI的完全定制的呢砸捏?它實(shí)際上未使用瀏覽器內(nèi)核的任何UI繪制功能谬运,注意是未使用UI繪制功能。它利用javascript引擎強(qiáng)大的DOM操作管理能力來管理所有UI節(jié)點(diǎn)垦藏,每次刷新前把所有節(jié)點(diǎn)信息更新完畢以后再給yoga做排版梆暖,然后再調(diào)用原生組件來繪制。javascript是整個(gè)系統(tǒng)的核心語言掂骏。
  • 我們可以把瀏覽器看成一個(gè)盒子轰驳,javascript引擎是盒子里面的總管,DOM是javascript引擎內(nèi)置的,javascript和javascript引擎也是無縫鏈接的级解。react native是怎么跳出這個(gè)盒子去調(diào)用外部原生組件來繪制UI的呢冒黑?秘密就在MessageQueue。
  • javascript引擎對(duì)原生代碼的調(diào)用都是通過一套固定的接口來實(shí)現(xiàn)勤哗,這套接口的主要作用就是記錄原生接口的地址和對(duì)應(yīng)的javascript的函數(shù)名稱抡爹,然后在javascript調(diào)用該函數(shù)的時(shí)候把調(diào)用轉(zhuǎn)發(fā)給原生接口

六、React Native 初始化

React Native的初始化從RootView開始芒划,默認(rèn)在AppDelegate.m:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 里面會(huì)有RootViewd的初始化邏輯冬竟,調(diào)試的時(shí)候可以從這里入手

React Native的初始化分為幾個(gè)步驟

  • 原生代碼加載
  • JS Engine初始化(生成一個(gè)空的JS引擎)
  • JS基礎(chǔ)設(shè)施初始化. 主要是require等基本模塊的加載并替換JS默認(rèn)的實(shí)現(xiàn)。自定義require, Warning window, Alert window, fetch等都是在這里進(jìn)行的民逼”门梗基礎(chǔ)設(shè)施初始化好以后就可以開始加載js代碼了
  • 遍歷加載所有要導(dǎo)出給JS用的原生模塊和方法, 生成對(duì)應(yīng)的JS模塊信息,打包成json的格式給JS Engine, 準(zhǔn)確地說是給MessageQueue.

這里需要提一下的是

這里的導(dǎo)出是沒有對(duì)象的拼苍,只有方法和模塊笑诅。JS不是一個(gè)標(biāo)準(zhǔn)的面向?qū)ο笳Z言,剛從Java轉(zhuǎn)JavaScript的同學(xué)都會(huì)在面向?qū)ο筮@個(gè)概念上栽跟頭疮鲫,這里特別提醒一下

6.1 原生代碼初始化

這里討論的主要是RN相關(guān)的原生代碼和用戶自定義的RN模塊的原生代碼的加載和初始化吆你。原生代碼初始化主要分兩步

  • 靜態(tài)加載。iOS沒有動(dòng)態(tài)加載原生代碼的接口棚点,所有的代碼都在編譯的初期就已經(jīng)編譯為靜態(tài)代碼并且鏈接好早处,程序啟動(dòng)的時(shí)候所有的原生代碼都會(huì)加載好。這是原生代碼的靜態(tài)加載瘫析,iOS里面沒有動(dòng)態(tài)加載原生代碼的概念,這也是為何沒有靜態(tài)代碼熱更新的原因
  • RN模塊解析和注入JS默责。這是加載的第二步贬循。在RootView初始化的時(shí)候會(huì)遍歷所有被標(biāo)記為RCTModule的原生模塊,生成一個(gè)json格式的模塊信息桃序,里面包含模塊名稱和方法名稱杖虾,然后注入到JS Engine, 由MessageQueue記錄下來。原生代碼在生成json模塊信息的時(shí)候同時(shí)會(huì)在原生代碼這邊維護(hù)一個(gè)名稱字典媒熊,用來把模塊和方法的名稱映射到原生代碼的地址上去奇适,用于JS調(diào)用原生代碼的翻譯

6.2 Javascript環(huán)境初始化

  • RN的初始化是從RCRootView開始的,所有的繪制都會(huì)在這個(gè)RootView里面進(jìn)行(Alert除外)
  • RootView做的第一件事情就是初始化一個(gè)空的JS Engine芦鳍。 這個(gè)空的JS Engine里面包含一些最基礎(chǔ)的模塊和方法(fetch, require, alert等), 沒有UI繪制模塊嚷往。 RN的工作就是替換這些基礎(chǔ)的模塊和方法,然后把RN的UI繪制模塊加載并注入到JS Engine.

JS Engine不直接管理UI的繪制

  • 所有的繪制由原生控制的UI事件和Timer觸發(fā)
  • 影響界面刷新的事件發(fā)生以后一部分直接由原生控件消化掉柠衅,直接更新原生控件皮仁。剩下的部分會(huì)通過Bridge派發(fā)給MessageQueue,然后在JS層進(jìn)行業(yè)務(wù)邏輯的計(jì)算,再由React來進(jìn)行Virtual Dom的管理和更新贷祈。Virtual Dom再通過MessageQueue發(fā)送重繪指令給對(duì)應(yīng)的原生組件進(jìn)行UI更新

6.3 NativeModules加載

  • 在OC里面趋急,所有NativeModules要加載進(jìn)JS Engine都必須遵循一定的協(xié)議(protocol)。
  • 模塊(OC里面的類)需要聲明為<rctbridgemodule style="box-sizing: border-box;">, 然后在類里面還必須調(diào)用宏RCT_EXPORT_MODULE() 用來定義一個(gè)接口告訴JS當(dāng)前模塊叫什么名字势誊。這個(gè)宏可以接受一個(gè)可選的參數(shù)呜达,指定模塊名,不指定的情況下就取類名粟耻。</rctbridgemodule>
  • 對(duì)應(yīng)的JS模塊在初始化的時(shí)候會(huì)調(diào)用原生類的[xxx new]方法- 模塊聲明為<RCTBridgeModule>后只是告訴Native Modules這有一個(gè)原生模塊闻丑,是一個(gè)空的模塊。要導(dǎo)出任何方法給JS使用都必須手動(dòng)用宏RCT_EXPORT_METHOD來導(dǎo)出方法給JS用.
  • 所有的原生模塊都會(huì)注冊(cè)到NativeModules這一個(gè)JS模塊下面去勋颖,你如果想要讓自己的模塊成為一個(gè)頂級(jí)模塊就必須再寫一個(gè)JS文件封裝一遍NativeModules里面的方法嗦嗡。
  • 你如果想自己的方法導(dǎo)出就默認(rèn)成為頂級(jí)方法,那么你需要一個(gè)手動(dòng)去調(diào)用JSC的接口饭玲,這個(gè)在前面章節(jié)有講解侥祭。 不建議這樣做,因?yàn)檫@樣你會(huì)失去跨JS引擎的便利性茄厘。
  • 你可以導(dǎo)出常量到JS里面去, 模塊初始化的時(shí)候會(huì)堅(jiān)持用戶是否有實(shí)現(xiàn)constantsToExport 方法, 接受一個(gè)常量詞典

|

<pre style="box-sizing: border-box; overflow: auto hidden; font-family: PingFangSC-Regular, Roboto, Verdana, "Open Sans", "Helvetica Neue", Helvetica, "Hiragino Sans GB", "Microsoft YaHei", "Source Han Sans CN", "WenQuanYi Micro Hei", Arial, sans-serif; font-size: 1em; line-height: 1.5em; white-space: pre-wrap; overflow-wrap: break-word; margin: 0px; background-color: transparent;">- (NSDictionary *)constantsToExport
{
return @{ @"firstDayOfTheWeek": @"Monday" };// JS里面可以直接調(diào)用 ModuleName.firstDayOfTheWeek獲取這個(gè)常量
}
</pre>

|

  • 常量只會(huì)在初始化的時(shí)候調(diào)用一次矮冬,動(dòng)態(tài)修改該方法的返回值無效
  • 所有標(biāo)記為RCT_EXPORT_MODULE的模塊都會(huì)在程序啟動(dòng)的時(shí)候自動(dòng)注冊(cè)好這些模塊,主要是記錄模塊名和方法名次哈。只是注冊(cè)胎署,不一定會(huì)初始化。
  • Native Modules導(dǎo)出宏具體使用方法見官方文檔Native Modules

6.4 三個(gè)線程

React Native有三個(gè)重要的線程:

  • Shadow queue. 布局引擎(yoga)計(jì)算布局用的
  • Main thread. 主線程窑滞。就是操作系統(tǒng)的UI線程琼牧。無論是iOS還是android,一個(gè)進(jìn)程都只有一個(gè)UI線程哀卫,我們常說的主線程. React Native所有UI繪制也是由同一個(gè)UI線程來維護(hù)
  • Javascript thread. javascript線程巨坊。 大家都知道javascript是單線程模型,event驅(qū)動(dòng)的異步模型此改。React Native用了JS引擎趾撵,所以也必需有一個(gè)獨(dú)立的js 線程. 所有JS和原生代碼的交互都發(fā)生在這個(gè)線程里。死鎖共啃,異常也最容易發(fā)生在這個(gè)線程

可以看到Shadow queue是queue而不是thread, 在iOS里面queue是thread之上的一層抽象,GCD里面的一個(gè)概念占调,創(chuàng)建queue的時(shí)候可以指定是并行的還是串行的。也就是說移剪,一個(gè)queue可能對(duì)應(yīng)多個(gè)thread

七究珊、內(nèi)部機(jī)制

內(nèi)部機(jī)制

681.jpg

JS用時(shí)序

682.png

八、總結(jié)

8.1 React Native 框架分析

683.png

8.2 層次架構(gòu)

  • Java層:該層主要提供了Android的UI渲染器UIManager(將JavaScript映射成Android Widget)以及一些其他的功能組件(例如:Fresco挂滓、Okhttp)等苦银,在java層均封裝為Module啸胧,java層核心jar包是react-native.jar,封裝了眾多上層的interface幔虏,如Module纺念,Registry,bridge等
  • C++層:主要處理Java與JavaScript的通信以及執(zhí)行JavaScript代碼工作想括,該層封裝了JavaScriptCore陷谱,執(zhí)行對(duì)js的解析∩冢基于JavaScriptCore烟逊,Web開發(fā)者可以盡情使用ES6的新特性,如class铺根、箭頭操作符等宪躯,而且 React Native運(yùn)行在JavaScriptCore中的,完全不存在瀏覽器兼容的情況位迂。Bridge橋接了java 访雪, js 通信的核心接口。JSLoader主要是將來自assets目錄的或本地file加載javascriptCore掂林,再通過JSCExectutor解析js文件
  • Js層:該層提供了各種供開發(fā)者使用的組件以及一些工具庫臣缀。
    Component:Js層通js/jsx編寫的Virtual Dom來構(gòu)建Component或Module,Virtual DOM是DOM在內(nèi)存中的一種輕量級(jí)表達(dá)方式泻帮,可以通過不同的渲染引擎生成不同平臺(tái)下的UI精置。component的使用在 React 里極為重要, 因?yàn)閏omponent的存在讓計(jì)算 DOM diff 更高效。
    ReactReconciler : 用于管理頂層組件或子組件的掛載锣杂、卸載脂倦、重繪

注:JSCore,即JavaScriptCore蹲堂,JS解析的核心部分狼讨,IOS使用的是內(nèi)置的JavaScriptCore,Androis上使用的是 https://webkit.org 家的jsc.so柒竞。

Java層核心類及原理,如下所示

ReactContext

  • ReactContext繼承于ContextWrapper播聪,是ReactNative應(yīng)用的上下文朽基,通過getContext()去獲得,通過它可以訪問ReactNative核心類的實(shí)現(xiàn)离陶。

ReactInstanceManager

  • ReactInstanceManager是ReactNative應(yīng)用總的管理類稼虎,創(chuàng)建ReactContextCatalystInstance等類招刨,解析ReactPackage生成映射表霎俩,并且配合ReactRootView管理View的創(chuàng)建與生命周期等功能。

ReactRootView

  • 為啟動(dòng)入口核心類,負(fù)責(zé)監(jiān)聽及分發(fā)事件并重新渲染元素打却,App啟動(dòng)后杉适,其將作為App的root view

CatalystInstance

  • CatalystInstanceReactNative應(yīng)用Java層柳击、C++層猿推、JS層通信總管理類,總管Java層捌肴、JS層核心Module映射表與回調(diào)蹬叭,三端通信的入口與橋梁。

JavaScriptModule

  • JavaScriptModuleJS Module状知,負(fù)責(zé)JS到Java的映射調(diào)用格式聲明秽五,由CatalystInstance統(tǒng)一管理。

NativeModule

  • NativeModulejava Module饥悴,負(fù)責(zé)Java到Js的映射調(diào)用格式聲明坦喘,由CatalystInstance統(tǒng)一管理。

JavascriptModuleRegistry

  • JS Module映射表,負(fù)責(zé)將所有JavaScriptModule注冊(cè)到CatalystInstance铺坞,通過Java動(dòng)態(tài)代理調(diào)用到Js起宽。

NativeModuleRegistry

  • 是Java Module映射表,即暴露給Js的API集合。

CoreModulePackage

  • 定義核心框架模塊济榨,創(chuàng)建NativeModules&JsModules

8.3 啟動(dòng)過程的解析

  1. ReactInstanceManager創(chuàng)建時(shí)會(huì)配置應(yīng)用所需的java模塊與js模塊坯沪,通過ReactRootView的startReactApplication啟動(dòng)APP。
  2. 在創(chuàng)建ReactInstanceManager同時(shí)會(huì)創(chuàng)建用于加載JsBundle的JSBundlerLoader擒滑,并傳遞給CatalystInstance腐晾。
  3. CatalystInstance會(huì)創(chuàng)建Java模塊注冊(cè)表及Javascript模塊注冊(cè)表,并遍歷實(shí)例化模塊丐一。
  4. CatalystInstance通過JSBundlerLoader向Node Server請(qǐng)求Js Bundle藻糖,并傳遞給JSCJavaScriptExectutor,最后傳遞給javascriptCore库车,再通過ReactBridge通知ReactRootView完成渲染

8.4 Js與Java通信機(jī)制

Java與Js之間的調(diào)用巨柒,是以兩邊存在兩邊存在同一份模塊配置表,最終均是將調(diào)用轉(zhuǎn)化為{moduleID,methodID柠衍,callbackID洋满,args},處理端在模塊配置表里查找注冊(cè)的模塊與方法并調(diào)用珍坊。

Java 調(diào)用Js

Java通過注冊(cè)表調(diào)用到CatalystInstance實(shí)例牺勾,透過ReactBridge的jni,調(diào)用到Onload.cpp中的callFunction阵漏,最后通過javascriptCore驻民,調(diào)用BatchedBridge.js翻具,根據(jù)參數(shù){moduleID,methodID}require相應(yīng)Js模塊執(zhí)行。流程如下圖:

684.png

Js 調(diào)用Java

如果消息隊(duì)列中有等待Java 處理的邏輯回还,而且 Java 超過 5ms 都沒有來取走裆泳,那么 JavaScript 就會(huì)主動(dòng)調(diào)用 Java 的方法,在需要調(diào)用調(diào)Java模塊方法時(shí),會(huì)把參數(shù){moduleID,methodID}等數(shù)據(jù)存在MessageQueue中懦趋,等待Java的事件觸發(fā)晾虑,把MessageQueue中的{moduleID,methodID}返回給Java,再根據(jù)模塊注冊(cè)表找到相應(yīng)模塊處理仅叫。流程如下圖:

WechatIMG1 1.png

九帜篇、更多參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市诫咱,隨后出現(xiàn)的幾起案子笙隙,更是在濱河造成了極大的恐慌,老刑警劉巖坎缭,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竟痰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡掏呼,警方通過查閱死者的電腦和手機(jī)坏快,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來憎夷,“玉大人莽鸿,你說我怎么就攤上這事∈案” “怎么了祥得?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蒋得。 經(jīng)常有香客問我级及,道長(zhǎng),這世上最難降的妖魔是什么额衙? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任饮焦,我火速辦了婚禮,結(jié)果婚禮上窍侧,老公的妹妹穿的比我還像新娘追驴。我一直安慰自己,他們只是感情好疏之,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著暇咆,像睡著了一般锋爪。 火紅的嫁衣襯著肌膚如雪丙曙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天其骄,我揣著相機(jī)與錄音亏镰,去河邊找鬼。 笑死拯爽,一個(gè)胖子當(dāng)著我的面吹牛索抓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播毯炮,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼逼肯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了桃煎?” 一聲冷哼從身側(cè)響起篮幢,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎为迈,沒想到半個(gè)月后三椿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡葫辐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年搜锰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耿战。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蛋叼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昆箕,到底是詐尸還是另有隱情鸦列,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布鹏倘,位于F島的核電站薯嗤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏纤泵。R本人自食惡果不足惜骆姐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望捏题。 院中可真熱鬧玻褪,春花似錦、人聲如沸公荧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽循狰。三九已至窟社,卻和暖如春券勺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背灿里。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國打工关炼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匣吊。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓儒拂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親色鸳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子社痛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350