Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
本文結(jié)合React Native跨平臺(tái)框架的應(yīng)用工作原理,分析性能瓶頸,分享實(shí)踐中的優(yōu)化性能問(wèn)題內(nèi)容輸入。
背景
隨著移動(dòng)互聯(lián)網(wǎng)的高速發(fā)展,在很多的業(yè)務(wù)場(chǎng)景下,傳統(tǒng)的純?cè)_(kāi)發(fā)已經(jīng)不能滿(mǎn)足日益增長(zhǎng)的業(yè)務(wù)需求。為了提升開(kāi)發(fā)效率,使APP具備動(dòng)態(tài)化能力,同時(shí)提升用戶(hù)體驗(yàn),項(xiàng)目中引入了React Native框架。使用過(guò)程中發(fā)現(xiàn)部分React Native頁(yè)面性能存在一些問(wèn)題,尤其是復(fù)雜頁(yè)面渲染性能較差,于是決定深入調(diào)研和解決這些問(wèn)題。
React Native工作原理
1.概覽
React Native的頁(yè)面使用JavaScript編寫(xiě),在JavaScript虛擬機(jī)里運(yùn)行,JavaScript標(biāo)簽/組件最終會(huì)被解析為iOS和Android端的View,從而實(shí)現(xiàn)跨平臺(tái)。JavaScript代碼運(yùn)行在一個(gè)后臺(tái)線(xiàn)程,也就是JavaScript虛擬機(jī)運(yùn)行的線(xiàn)程,而不是主線(xiàn)程。這樣設(shè)計(jì)的初衷是為了防止主線(xiàn)程被JavaScript代碼執(zhí)行阻塞,保持UI界面更流暢。JavaScript線(xiàn)程和主線(xiàn)程之間的交互是異步的。
React Native概覽
2. 定位問(wèn)題和優(yōu)化思路
一個(gè)React Native頁(yè)面生命周期,一般包括初始化,加載數(shù)據(jù),頁(yè)面渲染,事件響應(yīng),頁(yè)面刷新,銷(xiāo)毀等幾個(gè)過(guò)程。接下來(lái)將逐一分析這幾個(gè)過(guò)程中,可能存在的性能瓶頸和優(yōu)化思路。
2.1 頁(yè)面初始化
頁(yè)面初始化時(shí),React Native框架會(huì)先從本地文件系統(tǒng)加載JavaScript Bundle文件,并執(zhí)行文件里的JavaScript代碼。
性能瓶頸:
? JavaScript文件加載
? JavaScript代碼執(zhí)行
優(yōu)化思路:
? JavaScript Bundle拆包,把公用基礎(chǔ)代碼抽取出來(lái),每次打開(kāi)頁(yè)面,只加載業(yè)務(wù)代碼,減少文件大小,同時(shí)也可以減少代碼執(zhí)行量。
? 可以考慮使用JS Byte Code,加快JavaScript代碼執(zhí)行速度。
2.2 加載數(shù)據(jù)
React Native頁(yè)面加載數(shù)據(jù),一般由JavaScript端組裝參數(shù),Native端發(fā)起網(wǎng)絡(luò)請(qǐng)求,再將結(jié)果返回給JavaScript端。
性能瓶頸:
? 一般JavaScript端組裝參數(shù)的時(shí)候,可能需要從Native端讀取數(shù)據(jù),會(huì)涉及一次或多次JavaScript-Native通信,這個(gè)過(guò)程是異步的,如果涉及到數(shù)據(jù)傳輸,則會(huì)經(jīng)歷JS Object->String->Native Object和Native Object->String->JS Object(JSON序列化和反序列化)兩個(gè)過(guò)程,比較耗時(shí)。
? 網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)
優(yōu)化思路:
? 盡量避免每次加載數(shù)據(jù)時(shí),JavaScript端都要從Native端讀取數(shù)據(jù),最好有一套比
2.3 頁(yè)面渲染
先看一下React Native頁(yè)面渲染過(guò)程
React Native頁(yè)面渲染過(guò)程
?APP啟動(dòng),主線(xiàn)程初始化React Native框架。
?主線(xiàn)程創(chuàng)建Bridge,加載JavaScript文件。
?JavaScript線(xiàn)程解析執(zhí)行JavaScript代碼,生成DOM Tree結(jié)構(gòu)。
?Shadow Thread處理樣式信息,生成layout數(shù)據(jù),生成Shadow Tree。
?主線(xiàn)程根據(jù)Shadow Tree的層級(jí)關(guān)系,和layout數(shù)據(jù),創(chuàng)建Native View。
?主線(xiàn)程渲染Native View。
性能瓶頸:
React Native頁(yè)面渲染,經(jīng)歷了多次線(xiàn)程切換和JavaScript-Native通信,組件渲染也是順序進(jìn)行,雖然是批量操作,但是無(wú)法并發(fā)渲染。
優(yōu)化思路:
盡量減少頁(yè)面的組件數(shù)量和嵌套關(guān)系,復(fù)雜的頁(yè)面結(jié)構(gòu),大量的組件嵌套,首次渲染的時(shí)候,會(huì)非常慢,會(huì)出現(xiàn)明顯白屏現(xiàn)象。
2.4 事件響應(yīng)
React Native事件響應(yīng)
?Native捕捉到Touch,Timer,網(wǎng)絡(luò)等Event。
?Native負(fù)責(zé)組裝數(shù)據(jù)。
?Native通過(guò)Bridge將數(shù)據(jù)序列化為JSON String,傳遞給JavaScript。
?JavaScript反序列化JSON為JavaScript Object,處理Event。
?JavaScript組裝數(shù)據(jù),序列化JSON String,通過(guò)Bridge 回調(diào)Native方法。
?Bridge反序列化JSON為Native Object,執(zhí)行Native方法。
?刷新UI。
性能瓶頸:
在React Native中,Native端負(fù)責(zé)捕捉事件,通知JavaScript端處理,處理完畢之后,再通知Native端刷新UI。一次事件處理,包括了兩次JavaScript-Native通信。
而JavaScript是單線(xiàn)程執(zhí)行的,如果JavaScript處理邏輯太多,則會(huì)出現(xiàn)用戶(hù)響應(yīng)不及時(shí)。
優(yōu) 化思路 :
在一些事件交互比較多的場(chǎng)景(比如長(zhǎng)列表滑動(dòng)/動(dòng)畫(huà)),盡量把大量計(jì)算邏輯(cell生命周期維護(hù)/動(dòng)畫(huà)每一幀屬性計(jì)算)移到Native端執(zhí)行,減少JavaScript線(xiàn)程的工作量,避免每一幀都要和Native進(jìn)行通信。
React Native性能優(yōu)化實(shí)踐
1. 信息中心
為了減少JavaScript-Native通信的次數(shù),我們?cè)O(shè)計(jì)了信息中心模塊。
JavaScript環(huán)境和Native環(huán)境各自維護(hù)一些狀態(tài)數(shù)據(jù),這些數(shù)據(jù)互不可見(jiàn),但是雙方都有讀取和同步狀態(tài)數(shù)據(jù)需要,為了減少JavaScript-Native數(shù)據(jù)交互次數(shù),我們開(kāi)發(fā)出信息中心模塊。
信息中心的目的是建立一種數(shù)據(jù)同步機(jī)制,減少JavaScript-Native的不必要數(shù)據(jù)交互。
信息中心
我們把需要兩端共享的數(shù)據(jù)分為不變數(shù)據(jù),定時(shí)更新數(shù)據(jù),實(shí)時(shí)更新數(shù)據(jù)三種類(lèi)型,針對(duì)不同的數(shù)據(jù)采用不同的數(shù)據(jù)同步策略。
?不變數(shù)據(jù):設(shè)備信息,系統(tǒng)信息,軟件信息等。
?定時(shí)更新數(shù)據(jù):對(duì)精確度要求不高的數(shù)據(jù),比如位置信息。
?實(shí)時(shí)更新數(shù)據(jù):登錄態(tài),網(wǎng)絡(luò)狀態(tài)等。
避免每次讀取JavaScript或Native數(shù)據(jù)時(shí),都發(fā)起一次數(shù)據(jù)交互請(qǐng)求,
開(kāi)發(fā)者也不必再關(guān)心數(shù)據(jù)同步問(wèn)題,降低了開(kāi)發(fā)復(fù)雜度。
2. Native驅(qū)動(dòng)的列表頁(yè)
為了解決React Native長(zhǎng)列表頁(yè),內(nèi)存占用大,卡頓,容易白屏的問(wèn)題,我們?cè)O(shè)計(jì)Native驅(qū)動(dòng)列表組件。
React Native列表頁(yè),用戶(hù)交互比較多,列表Cell的生命周期(創(chuàng)建,展示,刷新,移除,銷(xiāo)毀)都是JavaScript端維護(hù),Native端把列表的滾動(dòng)狀態(tài)實(shí)時(shí)傳遞給JavaScript端,JavaScript維護(hù)Cell的生命周期,再通知Native端刷新UI,多次事件交互,導(dǎo)致RN原生列表頁(yè)性能差,快速滑動(dòng)有白屏現(xiàn)象。為了減少,JavaScript線(xiàn)程計(jì)算壓力,減少JavaScript-Native交互,我們推出了Native驅(qū)動(dòng)列表。
Native驅(qū)動(dòng)的列表頁(yè)
Native驅(qū)動(dòng)列表的優(yōu)勢(shì)在于,JavaScript只提供數(shù)據(jù)源和Cell模版,Cell生命周期由Native維護(hù),減少JavaScript線(xiàn)程計(jì)算壓力,渲染更快,沒(méi)有JavaScript-Shadow-Main線(xiàn)程切換,減少JavaScript-Native的事件交互次數(shù),支持Cell回收/重用,節(jié)省內(nèi)存,提升速度,流暢度更高,接近Native原生列表,兼容舊系統(tǒng)的JavaScript Cell模版。
React Native應(yīng)用JavaScript線(xiàn)程和主線(xiàn)程,任何一個(gè)線(xiàn)程被阻塞,都會(huì)導(dǎo)致卡頓和掉幀現(xiàn)象。JavaScript端在處理完事件響應(yīng)或數(shù)據(jù)更新時(shí),需要通知Native端重新渲染UI,這個(gè)過(guò)程是異步的,因此會(huì)略有延遲。在React Native現(xiàn)在的框架下,主要優(yōu)化思路就是減少不必要的組件重繪,減少不必要的JavaScript-Native通信,減少JavaScript線(xiàn)程的計(jì)算壓力,可以把一些原先在JavaScript執(zhí)行的任務(wù),移植到Native端執(zhí)行,實(shí)現(xiàn)由JavaScript驅(qū)動(dòng)到Native驅(qū)動(dòng),都會(huì)帶來(lái)顯著性能提升。
參考文獻(xiàn)
1. https://www.freecodecamp.org/news/how-react-native-constructs-app-layouts-and-how-fabric-is-about-to-change-it-dd4cb510d055/
2. https://tech.showmax.com/2018/05/react-native-or-not-react-native/
3. https://reactnative.cn/docs/performance/
4. https://www.simform.com/react-native-app-performance/
作者簡(jiǎn)介
禹瀟瀟 58集團(tuán) HRG技術(shù)部
者:GSYTech
隨著 RN 團(tuán)隊(duì)關(guān)于 深入了解 React Native 的新架構(gòu) 文章的發(fā)布,這次新架構(gòu)帶來(lái)的調(diào)整主要在于以下四點(diǎn):
在 RN App 里,所有的 JS 代碼都會(huì)打包成一個(gè) JS Bundle 文件保存在本地運(yùn)行,當(dāng) RN App 運(yùn)行時(shí),一般會(huì)有三個(gè)線(xiàn)程:
在 RN 里 JS 線(xiàn)程和 Native 線(xiàn)程之前是通過(guò) bridge 來(lái)交互,而交互的數(shù)據(jù)必須被轉(zhuǎn)化為 JSON,而這個(gè)橋只能處理異步通信。
?
JavaScriptCore:JavaScript 引擎,React Native 用它執(zhí)行 JS 代碼;
Yoga:布局引擎,計(jì)算UI位置;
?
目前 RN 使用 Bridge Module 來(lái)讓 JS 和 Native 線(xiàn)程進(jìn)行通信,每次利用 Bridge 發(fā)送數(shù)據(jù)時(shí),都需要轉(zhuǎn)換為 JSON, 而收到數(shù)據(jù)時(shí)也需要進(jìn)行解碼。
「這就意味著 JavaScript 和 Native 直接是隔離的,也就是 JS 線(xiàn)程不能直接調(diào)用 Native 線(xiàn)程上的方法」。
另一個(gè)就是;「通過(guò) Bridge 發(fā)送的消息本質(zhì)上是異步的」,如果需要 JS 代碼和 Naitve 同步執(zhí)行在之前是無(wú)法實(shí)現(xiàn)。
?
例如,如果 JS 線(xiàn)程需要訪(fǎng)問(wèn) native modules(例如藍(lán)牙),它就需要向 native 線(xiàn)程發(fā)送消息,JS 線(xiàn)程就會(huì)通過(guò) Bridge 發(fā)送一個(gè) JSON 消息,然后消息在 native 線(xiàn)程上進(jìn)行解碼,最終將執(zhí)行所需的 native 代碼。
?
而在全新架構(gòu)中,「Bridge 將被一個(gè)名為 JavaScript Interface 的模塊所代替,它是一個(gè)輕量級(jí)的通用層」,用 C++ 編寫(xiě),JavaScript Engine 可以使用它直接執(zhí)行或者調(diào)用 native。
?
通用層代表著:JSI 讓 JavaScript 接口將與 Engine 分離,這意味著新架構(gòu)支持 「RN 直接使用其他 JavaScript 引擎,比如 Chakra、v8、Hermes 等等」。
?
那 JSI 如何讓 JavaScript 直接調(diào)用到原生方法?
「在 JSI 里 Native 方法會(huì)通過(guò) C++ Host Objects 暴露給 JS, 而 JS 可以持有對(duì)這些對(duì)象的引用,并且使用這些引用直接調(diào)用對(duì)應(yīng)的方法」。
這就類(lèi)似于 Web 里 JS 代碼可以保存對(duì)任何 DOM 元素的引用,并在它上面調(diào)用方法:
const container=document.createElement(‘div’);
?
舉個(gè)例子,在這里的 container 會(huì)包含一些在 C++ 中初始化的 DOM 元素的引用,這時(shí)候如果我們調(diào)用 container 上的任何方法,它就會(huì)調(diào)用 DOM 元素上的方法。
?
JSI 就是以類(lèi)似的方式運(yùn)行,JSI 將允許 JS 代碼保存對(duì) Native Modules 的引用,并且 JS 可以直接通過(guò)引用去調(diào)用 Native 上的方法。
總結(jié)起來(lái)就是:
Fabric 是新的渲染系統(tǒng),它將取代當(dāng)前的 UI Manager。
在 Fabric 之前,當(dāng) App 運(yùn)行時(shí),React 會(huì)執(zhí)行你的代碼并在 JS 中創(chuàng)建一個(gè) ReactElementTree,基于這棵樹(shù)渲染器會(huì)在 C++ 中創(chuàng)建一個(gè) ReactShadowTree。
?
UI Manager 會(huì)使用 Shadow Tree 來(lái)計(jì)算 UI 元素的位置,而一旦 Layout 完成,Shadow Tree 就會(huì)被轉(zhuǎn)換為由 Native Elements 組成的 HostViewTree(例如:RN 里的 會(huì)變成 Android 中的 ViewGroup 和 iOS 中的 UIView)。
?
而之前線(xiàn)程之間的通信都發(fā)生在 Bridge 上,這就意味著需要在傳輸和數(shù)據(jù)復(fù)制上耗費(fèi)時(shí)間。
?
例如如果一個(gè) ReactElementTree 節(jié)點(diǎn)恰好是一個(gè) <Image/>,那么 ReactShadowTree 的節(jié)點(diǎn)也會(huì)是一個(gè)圖像,但是這些數(shù)據(jù)必須被復(fù)制并分別存儲(chǔ)在兩個(gè)節(jié)點(diǎn)中。
?
另外由于 JS 和 UI 線(xiàn)程不同步,因此在某些情況下 App 可能會(huì)因?yàn)閬G幀而顯得卡頓(例如滾動(dòng)有大量數(shù)據(jù)的 FlatList )
「而得益于前面的 JSI, JS 可以直接調(diào)用 Native 方法,其實(shí)就包括了 UI 方法,所以 JS 和 UI 線(xiàn)程可以同步執(zhí)行從而提高列表、跳轉(zhuǎn)、手勢(shì)處理等的性能」。
使用新的 Fabric 渲染,用戶(hù)交互(如滾動(dòng)、手勢(shì)等)可以?xún)?yōu)先在主線(xiàn)程或 Native 線(xiàn)程中同步執(zhí)行,而 API 請(qǐng)求等其他任務(wù)使用異步執(zhí)行。
「另外新的 Shadow Tree 將成為 immutable,它會(huì)在 JS 和 UI 線(xiàn)程之間共享,以?xún)啥诉M(jìn)行直接交互」。
?
在以前 RN 必須維護(hù)兩個(gè)層次結(jié)構(gòu)的 DOM 節(jié)點(diǎn),但因?yàn)楝F(xiàn)在 Shadow Tree 可以共享,在減少內(nèi)存消耗的部分也會(huì)得到相應(yīng)的優(yōu)化。
?
在之前的架構(gòu)中 JS 使用的所有 Native Modules(例如藍(lán)牙、地理位置、文件存儲(chǔ)等)都必須在應(yīng)用程序打開(kāi)之前進(jìn)行初始化,這意味著即使用戶(hù)不需要某些模塊,但是它仍然必須在啟動(dòng)時(shí)進(jìn)行初始化。
Turbo Modules 基本上是對(duì)這些舊的 Native 模塊的增強(qiáng),正如在前面介紹的那樣,「現(xiàn)在 JS 將能夠持有這些模塊的引用,所以 JS 代碼可以?xún)H在需要時(shí)才加載對(duì)應(yīng)模塊,這樣可以將顯著縮短 RN 應(yīng)用的啟動(dòng)時(shí)間」。
「Codegen 主要是用于保證 JS 代碼和 C++ 的 JSI 可以正常通信的靜態(tài)類(lèi)型檢查器」,通過(guò)使用類(lèi)型化的 JS 作為參考來(lái)源,CodeGen 將定義可以被 Turbo 模塊和 Fabric 使用的接口,另外 Codegen 會(huì)在構(gòu)建時(shí)生成 Native 代碼,減少運(yùn)行時(shí)的開(kāi)支。
「從上面四點(diǎn)可以看到 2022 年 RN 將迎來(lái)性能和體驗(yàn)上的躍遷,本次即將到來(lái)的全新架構(gòu)將解決 RN 多年以后被人詬病的各種根本上的設(shè)計(jì)問(wèn)題」。
另外還要介紹的內(nèi)容就是 react-native-skia ,目前它還處于 alpha release 的階段,但是它也給 RN 帶來(lái)的新的可能。
?
眾所周知,F(xiàn)lutter 跨平臺(tái)的性能提升和解耦來(lái)自于直接使用 Skia 渲染而非系統(tǒng)控件,而如今 RN 也有類(lèi)似的支持。
?
react-native-skia 需要 react-native@>=0.66 的支持,而目前它上面的操作都還是十分原始的 canvas 行為,例如通過(guò) Circle 繪制圓形,通過(guò) blendMode 配置重疊模式等。
import {Canvas, Circle, Group} from "@shopify/react-native-skia";
export const HelloWorld=()=> {
const width=256;
const height=256;
const r=215;
return (
<Canvas style={{ flex: 1 }}>
<Group blendMode="multiply">
<Circle cx={r} cy={r} r={r} color="cyan" />
<Circle cx={width - r} cy={r} r={r} color="magenta" />
<Circle
cx={width/2}
cy={height - r}
r={r}
color="yellow"
/>
</Group>
</Canvas>
);
};
當(dāng)然它也支持直接使用 SkiaView ,然后通過(guò) canvas 來(lái)繪制你需要的圖形:
import {Skia, BlendMode, SkiaView, useDrawCallback} from "@shopify/react-native-skia";
const paint=Skia.Paint();
paint.setAntiAlias(true);
paint.setBlendMode(BlendMode.Multiply);
export const HelloWorld=()=> {
const width=256;
const height=256;
const r=215;
const onDraw=useDrawCallback((canvas)=> {
// Cyan Circle
const cyan=paint.copy();
cyan.setColor(Skia.Color("cyan"));
canvas.drawCircle(r, r, r, cyan);
// Magenta Circle
const magenta=paint.copy();
magenta.setColor(Skia.Color("magenta"));
canvas.drawCircle(width - r, r, r, magenta);
// Yellow Circle
const yellow=paint.copy();
yellow.setColor(Skia.Color("yellow"));
canvas.drawCircle(width/2, height - r, r, yellow);
});
return (
<SkiaView style={{ flex: 1 }} onDraw={onDraw} />
);
};
目前該庫(kù)支持 Image、Text、Shader、Effects、Shapes、Animations 等操作,而事實(shí)上該庫(kù)的實(shí)現(xiàn)和 Flutter 很是相似,比如:
?
在 Android 上的 SkiaDrawView 其實(shí)就是 TextureView ,繪制邏輯是作者自己寫(xiě)的 reactskia 上,這里只是借助了 TextureView 的 surface 和 TouchEvent 等的支持。
?
如下圖所示,是關(guān)于使用 react-native-skia 實(shí)現(xiàn)的一段 Demo
詳細(xì)可見(jiàn):https://shopify.github.io/react-native-skia/
可以預(yù)見(jiàn)目前的 react-native-skia 還有不少問(wèn)題需要解決,但是它讓 RN 可以更高效地使用豐富的 Canvas 能力,對(duì)于 RN 的未來(lái)而言不免是一次不錯(cuò)的嘗試。
在這里就分享一份由大佬親自收錄整理的學(xué)習(xí)PDF+架構(gòu)視頻+面試文檔+源碼筆記,高級(jí)架構(gòu)技術(shù)進(jìn)階腦圖、Android開(kāi)發(fā)面試專(zhuān)題資料,高級(jí)進(jìn)階架構(gòu)資料
這些都是我現(xiàn)在閑暇時(shí)還會(huì)反復(fù)翻閱的精品資料。里面對(duì)近幾年的大廠(chǎng)面試高頻知識(shí)點(diǎn)都有詳細(xì)的講解。相信可以有效地幫助大家掌握知識(shí)、理解原理,幫助大家在未來(lái)取得一份不錯(cuò)的答卷。
當(dāng)然,你也可以拿去查漏補(bǔ)缺,提升自身的競(jìng)爭(zhēng)力。
真心希望可以幫助到大家,Android路漫漫,共勉!
如果你有需要的話(huà),只需私信我【進(jìn)階】即可獲取
的,大家可能很疑惑:“都 2020 年了,怎么現(xiàn)在還發(fā)布組件庫(kù)呢?”
確實(shí),對(duì)于前端組件庫(kù)的大家庭來(lái)說(shuō),我們遲到了,但也請(qǐng)各位可以抽出幾分鐘看看一位初來(lái)乍到的新人的自我介紹:
Zarm 是眾安科技基于 React、React-Native 研發(fā)的一款適用于企業(yè)級(jí)的移動(dòng)端 UI 組件庫(kù)。
多
快
好
爽
在不久的未來(lái),我們會(huì)推出:
對(duì)不起,我們來(lái)晚了
在各個(gè)前端團(tuán)隊(duì)或自己研發(fā)、或使用第三方組件庫(kù)的時(shí)代,我們將致力于把 Zarm 打造成體驗(yàn)更好的基于 React、React-Native 的一款適用于企業(yè)級(jí)的移動(dòng)端 UI 組件庫(kù)。
最后,非常感謝你的閱讀,也非常歡迎有興趣的同學(xué)加入我們共建更好的 Zarm!
Github 開(kāi)源地址:https://github.com/ZhongAnTech/zarm
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。