一汁讼、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
-
JavaScriptCore
在iOS
平臺(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)分離開了
- html文本描述了頁面應(yīng)該有哪些功能骆捧,css告訴瀏覽器該長(zhǎng)什么樣澎羞。
- 瀏覽器引擎通過解析html和css,翻譯成一些列的預(yù)定義UI控件敛苇,
- 然后UI控件去調(diào)用操作系統(tǒng)繪圖指令去繪制圖像展現(xiàn)給用戶妆绞。
- 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)
- 綠色的是我們應(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
典蜕、React
和JavascriptCore
- 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.js
是Bridge
在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
-
RCTRootView
是React 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 hooks
和modules
, 執(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ī)制
JS用時(shí)序
八、總結(jié)
8.1 React Native 框架分析
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)建ReactContext
、CatalystInstance
等類招刨,解析ReactPackage
生成映射表霎俩,并且配合ReactRootView
管理View的創(chuàng)建與生命周期等功能。
ReactRootView
- 為啟動(dòng)入口核心類,負(fù)責(zé)監(jiān)聽及分發(fā)事件并重新渲染元素打却,App啟動(dòng)后杉适,其將作為App的
root view
。
CatalystInstance
-
CatalystInstance
是ReactNative
應(yīng)用Java層柳击、C++層猿推、JS層通信總管理類,總管Java層捌肴、JS層核心Module
映射表與回調(diào)蹬叭,三端通信的入口與橋梁。
JavaScriptModule
-
JavaScriptModule
是JS Module
状知,負(fù)責(zé)JS到Java的映射調(diào)用格式聲明秽五,由CatalystInstance
統(tǒng)一管理。
NativeModule
-
NativeModule
是java 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)過程的解析
- ReactInstanceManager創(chuàng)建時(shí)會(huì)配置應(yīng)用所需的java模塊與js模塊坯沪,通過ReactRootView的startReactApplication啟動(dòng)APP。
- 在創(chuàng)建ReactInstanceManager同時(shí)會(huì)創(chuàng)建用于加載JsBundle的JSBundlerLoader擒滑,并傳遞給CatalystInstance腐晾。
- CatalystInstance會(huì)創(chuàng)建Java模塊注冊(cè)表及Javascript模塊注冊(cè)表,并遍歷實(shí)例化模塊丐一。
- 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í)行。流程如下圖:
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)模塊處理仅叫。流程如下圖: