金昱東
摘要:隨著移動(dòng)互聯(lián)網(wǎng)的飛速發(fā)展,移動(dòng)設(shè)備與平臺(tái)也在不斷更新迭代,多種平臺(tái)與操作系統(tǒng)共存。對(duì)用戶而言,多了一種選擇,而對(duì)于開發(fā)者而言,則大大增加了開發(fā)成本。為了實(shí)現(xiàn)相同的功能,為每個(gè)平臺(tái)單獨(dú)開發(fā)應(yīng)用無疑是一種非常耗費(fèi)人力資源的解決方案;而基于Web的跨平臺(tái)開發(fā)方案并不能保證在不同平臺(tái)提供統(tǒng)一流暢的用戶體驗(yàn)。因此降低開發(fā)成本、保證應(yīng)用體驗(yàn)是移動(dòng)平臺(tái)開發(fā)一直追求和關(guān)注的兩個(gè)核心點(diǎn)。本文論述了React Native相比其他跨平臺(tái)解決方案的優(yōu)勢(shì)與不同,并在React Native基礎(chǔ)上提供了一種代碼壓縮與動(dòng)態(tài)加載的解決方案,大幅減少動(dòng)態(tài)加載業(yè)務(wù)代碼的大小與傳輸時(shí)間,將其在實(shí)際開發(fā)中的實(shí)用性進(jìn)一步提高。
關(guān)鍵詞:軟件工程;移動(dòng)互聯(lián)網(wǎng);跨平臺(tái);ReactNative;動(dòng)態(tài)加載
中圖分類號(hào):TP311
文獻(xiàn)標(biāo)識(shí)碼:A
DOI:10.3969/j.issn.1003-6970.2016.02.020
引言
移動(dòng)端的操作系統(tǒng)目前呈現(xiàn)兩極分化,Android與iOS是目前最普及的兩個(gè)移動(dòng)操作系統(tǒng)。然而在為它們開發(fā)同一個(gè)應(yīng)用程序時(shí),通常都需要兩套開發(fā)人員來維護(hù)兩個(gè)不同的代碼倉庫。除了React Native外比較流行的解決辦法是使用Htm15等Web技術(shù)來實(shí)現(xiàn)“一套代碼,多處運(yùn)行”。然而由于設(shè)備與操作系統(tǒng)的差異性,基于Web的界面與交互在兩個(gè)平臺(tái)上的體驗(yàn)并不一致且相差甚遠(yuǎn)。因此它只解決了開發(fā)成本的問題,并沒有解決保證應(yīng)用體驗(yàn)的問題。然而React Native則很好的同時(shí)解決了這兩個(gè)問題。
另外值得一提的是在Android與iOS平臺(tái)都可以實(shí)現(xiàn)代碼的動(dòng)態(tài)加載,與Web同樣靈活。即在程序運(yùn)行的過程中更新業(yè)務(wù)代碼,可以用來實(shí)現(xiàn)bug修復(fù)或應(yīng)用升級(jí)等功能。
Android依托于Java的Classloader,而iOS依托于Objective-的動(dòng)態(tài)性。React Native主要使用JS作為開發(fā)語言,也非常好的支持了動(dòng)態(tài)加載機(jī)制,但缺點(diǎn)是打包后的JS代碼過多,不利于在質(zhì)量較低的移動(dòng)網(wǎng)絡(luò)下進(jìn)行傳輸。因此本論文提出了一種基于ReactNative的代碼壓縮方法,可以大幅精簡(jiǎn)JS代碼量,使其在移動(dòng)網(wǎng)絡(luò)下的穩(wěn)健性更高、實(shí)用性更強(qiáng)。
1 React Native的概念
React Native是Facebook在2015年發(fā)布的一套基于JavaScript的開源框架。React Native結(jié)合了Web應(yīng)用和Native應(yīng)用的優(yōu)勢(shì),可以使用JavaScript來開發(fā)iOS和Android原生應(yīng)用。在JavaScript中用React抽象操作系統(tǒng)原生的UI組件,代替DOM元素來渲染界面。因此它同時(shí)解決了以下的兩個(gè)問題:
1.1 減少開發(fā)成本
無論是Android還是iOS平臺(tái),React Native都使用JavaScript作為主要的開發(fā)語言,同時(shí)它也使用了React來簡(jiǎn)化和加速開發(fā)流程。由于Android與iOS中的系統(tǒng)UI組件大部分都相似(如文字、圖片、列表等),基礎(chǔ)功能(如本地存儲(chǔ)、動(dòng)畫、通知)也基本類似,因此React Native將這些組件統(tǒng)一抽象為一個(gè)Module,開發(fā)者不用再關(guān)心到底當(dāng)前編寫的Module運(yùn)行在哪個(gè)平臺(tái)上,只需要關(guān)注本身的業(yè)務(wù)邏輯即可。
而對(duì)于Android與iOS所各自特有的概念,ReactNative進(jìn)行單獨(dú)定義,如Android中的Toolbar、iOS中的TabBar等等。這樣開發(fā)者就可以針對(duì)不同平臺(tái)的特性,在現(xiàn)有代碼基礎(chǔ)上稍作修改以適配不同的移動(dòng)操作系統(tǒng)。Android軟件的設(shè)計(jì)與其他系統(tǒng)中軟件設(shè)計(jì)基本類似,可能在測(cè)試方法、測(cè)試用例的編寫上有所不同,但是畢竟開發(fā)流程并不同意。
Facebook在提出React Native的時(shí)候提出“Learnonce,write anywhere”,即不同的平臺(tái)代碼不會(huì)完全相同,但是會(huì)很相似。即也可以保證一套開發(fā)人員即可完成多平臺(tái)業(yè)務(wù)場(chǎng)景的開發(fā)與實(shí)現(xiàn),相較于傳統(tǒng)的開發(fā)方式有很大改觀。
1.2 提供一致的用戶體驗(yàn)
一般來講,在各自平臺(tái)使用原生的API開發(fā)的應(yīng)用是運(yùn)行效率最高的,即用戶體驗(yàn)最好的。而基于Web形式的應(yīng)用既需要加載時(shí)間,又需要有較強(qiáng)的處理器進(jìn)行DOM渲染,因此在不同平臺(tái)、不同設(shè)備上的體驗(yàn)非常不一致。雖然說JQuery等開源方案經(jīng)過優(yōu)化可以達(dá)到比較好的效果,但是離原生應(yīng)用差距還是較大。
舉例來說PhoneGap屬于較為成熟的基于Web的跨平臺(tái)解決方案,它在iOS上的流暢度和加載時(shí)間要遠(yuǎn)遠(yuǎn)好于Android,或Android中只能處理一些簡(jiǎn)單的靜態(tài)邏輯。如果僅僅使用PhoneGap為iOS開發(fā),就失去了跨平臺(tái)解決方案的意義。
React Native雖然使用JavaScript作為開發(fā)語言,但是它并不將界面UI渲染到Web上,而是使用JavaScript解釋器,將JS代碼解釋為原生的組件再渲染到界面上。因此,從用戶體驗(yàn)上來講,React Native的渲染效率應(yīng)該與原生應(yīng)用是一致的。它與Web類的開發(fā)框架的不同之處在下一章進(jìn)行詳細(xì)講解。
2 React Native運(yùn)行原理
由于Android版本的React Native剛處于測(cè)試階段,因此這里選取iOS作為分析對(duì)象,對(duì)不同平臺(tái)來說,它們的實(shí)現(xiàn)原理基本類似。
2.1 JS與原生模塊相互調(diào)用
在iOS中,React Native使用白帶的JavaScriptCore作為JS的解析引擎,但并沒有用到JavaScriptCore提供的一些可以讓JS與OC互調(diào)的特性,而是自己實(shí)現(xiàn)了一套機(jī)制,這套機(jī)制可以通用于所有JS引擎上,這么做應(yīng)該也是考慮到了項(xiàng)目的靈活性與iOS安全機(jī)制的問題。
實(shí)際上普通的JS與原生代碼的調(diào)用非常簡(jiǎn)掣,無論是Android還是iOS,原生代碼可以向JS傳遞消息,如iOS中webview提供的stringByEvaluatingjavaScriptFromString方法可以直接執(zhí)行一段JS腳本,并且可以獲取它的返回值。這個(gè)返回值其實(shí)就相當(dāng)于JS向原生代碼傳遞消息。React Native就是以此為基礎(chǔ),通過模塊配置表和響應(yīng)流程實(shí)現(xiàn)了原生代碼與JS代碼的無縫銜接調(diào)用。
2.2 模塊配置表
在React Native初始化的時(shí)候,為了使JS知道可調(diào)用的原生模塊Module方法與命名,需要將一份Module的配置表傳遞給JS,配置表里包含了所有的模塊和模塊中的方法信息。
React Native框架中提供了一個(gè)叫做RCTBridgeModule的接口。在React Native初始化的時(shí)候,可以通過runtime接口objc_getClassList獲取所有的類,然后逐個(gè)判斷是否實(shí)現(xiàn)了RCTBridgeModule接口,就可以找到所有的模塊類了。一個(gè)模塊中有很多方法,一些可以暴露給JS調(diào)用,另一些則不需要。需要暴露的方法使用RCT_EXPORT_METHOD宏定義包裹,其實(shí)是將方法名在編譯時(shí)重新定義為有固定前綴的方法,方便查找并記錄。
因此在原生代碼中定義好了接口與函數(shù),ReactNative就會(huì)根據(jù)運(yùn)行時(shí)環(huán)境獲取所有的模塊與其方法生成模塊配置表。這個(gè)配置表在原生代碼與JS代碼之間都維持一份相同的配置,便于原生代碼與JS代碼相互調(diào)用。
2.3 事件傳遞響應(yīng)流程
React Native的主要核心是自己實(shí)現(xiàn)了一套JS與原生代碼相互調(diào)用的機(jī)制,這也是與其他基于Web的跨平臺(tái)方案不同的地方。使用JS寫好業(yè)務(wù)代碼后,由React Native進(jìn)行解釋,偏向效率的邏輯執(zhí)行、UI渲染都會(huì)在原生階段進(jìn)行處理,因此React Native的執(zhí)行效率要比其他的跨平臺(tái)方案高很多。
JS與原生代碼的調(diào)用流程比較負(fù)責(zé),如下所示:
1、JS端調(diào)用原生模塊中的某個(gè)方法
2、將調(diào)用分解為模塊名、方法名、參數(shù),傳遞給MessageQueue進(jìn)行處理,生成callbackld
3、將模塊名、方法名、參數(shù)對(duì)應(yīng)模塊配置表,轉(zhuǎn)換成模塊id、方法id、參數(shù)id
4、原生模塊根據(jù)各個(gè)id確定被調(diào)用的方法與參數(shù)
5、執(zhí)行原生代碼邏輯,將執(zhí)行結(jié)果進(jìn)行回調(diào)
6、JS端根據(jù)callbackld接收?qǐng)?zhí)行結(jié)果
其中需要特別注意的是,JS并不會(huì)主動(dòng)傳遞數(shù)據(jù)給OC。在調(diào)用OC方法時(shí),會(huì)把模塊id、方法id放入到一個(gè)隊(duì)列中。等原生代碼過來調(diào)用JS的任意方法時(shí),將這個(gè)隊(duì)列返回再執(zhí)行這個(gè)隊(duì)列中需要執(zhí)行的方法。所以React Native是一種基于事件響應(yīng)機(jī)制的框架,在沒有操作時(shí)不會(huì)進(jìn)行無效的循環(huán)執(zhí)行。
3 代碼壓縮與動(dòng)態(tài)加載
3.1 動(dòng)態(tài)加載的概念
在移動(dòng)平臺(tái)安裝或更新軟件時(shí),通常使用apk或ipa文件進(jìn)行安裝,也就是所稱的安裝包。這個(gè)安裝包中包含了應(yīng)用全部功能的實(shí)現(xiàn)。然而通常情況下,用戶只有下載完整的安裝包才能使用最新版的軟件系統(tǒng)。動(dòng)態(tài)加載的概念則是只下載更新部分的特定代碼(文件形式),覆蓋到原來的代碼空間,這個(gè)概念與我們?cè)赪indows平臺(tái)上所說的補(bǔ)丁概念類似。
在移動(dòng)平臺(tái)的背景下,由于用戶的網(wǎng)絡(luò)環(huán)境可能更差、存儲(chǔ)空間更小,因此動(dòng)態(tài)加載就非常適用于移動(dòng)平臺(tái)軟件的更新與需求實(shí)現(xiàn)。另外一方面,由于移動(dòng)互聯(lián)網(wǎng)發(fā)展迅速、軟件迭代頻繁,動(dòng)態(tài)加載可以在不下載完整安裝包的情況下,讓用戶沒有感知的完成應(yīng)用的更新,更有利于修復(fù)線上bug與需求的快速實(shí)現(xiàn)。
React Native就完全支持動(dòng)態(tài)加載的這個(gè)特性,當(dāng)開發(fā)人員編寫好新的功能模塊時(shí),將業(yè)務(wù)代碼與所需要的資源(Assets)打包下發(fā)到軟件中,軟件再動(dòng)態(tài)加載業(yè)務(wù)代碼,實(shí)現(xiàn)動(dòng)態(tài)加載。主要流程如下圖所示:
需要注意的是,在下載更新包之后,需要對(duì)代碼的版本、安全性進(jìn)行驗(yàn)證,防止打錯(cuò)版本或第三方對(duì)軟件進(jìn)行篡改劫持。
3.2 簡(jiǎn)化JS代碼結(jié)構(gòu)
在React Native當(dāng)中,可以對(duì)JS業(yè)務(wù)代碼進(jìn)行打包處理,即JS代碼既可以在線訪問也可以離線訪問。使用如下命令可以將JS業(yè)務(wù)代碼進(jìn)行打包:
react-native Bundle-minify
這樣就可以將JS業(yè)務(wù)代碼打包成一個(gè)bunlde文件,在app運(yùn)行時(shí)指定這個(gè)文件的本地地址(或在線地址)就可以對(duì)其進(jìn)行訪問并解釋執(zhí)行。但是有一個(gè)非常明顯的缺陷是,使用React Native提供的bunlde命令打包出的JS文件代碼量非常的大,至少有5萬行以上。React Native應(yīng)用加載的流程如下圖所示:從圖中可以看到,第三步與第四步中是較為耗時(shí)的兩個(gè)過程。沒有經(jīng)過處理的bunlde文件體積通常在300KB以上,如果使用2G網(wǎng)絡(luò)傳輸則需要至少十秒以上,這對(duì)于用戶體驗(yàn)有非常大的影響。另外,過多的代碼量對(duì)移動(dòng)的設(shè)備的性能也會(huì)造成影響,5萬行代碼的執(zhí)行時(shí)間通常也在200到300毫秒之間,雖然說時(shí)間并不多,但是用戶可以明顯感受到加載界面時(shí)的卡頓現(xiàn)象。
因此在本研究當(dāng)中針對(duì)這兩個(gè)非常耗時(shí)的環(huán)節(jié)進(jìn)行優(yōu)化,提出了一種分離基礎(chǔ)JS與業(yè)務(wù)JS的方法,可以大大縮小代碼的下載與加載時(shí)間。如下圖所示:
如上圖所示,原有的Bundle文件中,同時(shí)夾雜了基礎(chǔ)JS代碼與業(yè)務(wù)JS代碼?;A(chǔ)JS代碼指的是業(yè)務(wù)代碼運(yùn)行時(shí)所依賴的基礎(chǔ)環(huán)境。我們?yōu)椴煌K打包出的的Bundle文件中都會(huì)包含基礎(chǔ)JS代碼,因此如果軟件中同時(shí)有多個(gè)Bundle文件時(shí),會(huì)造成空間的浪費(fèi)。經(jīng)過驗(yàn)證基礎(chǔ)JS代碼大概在5萬行左右,而業(yè)務(wù)JS根據(jù)業(yè)務(wù)復(fù)雜程度而定,一般不超過IOKB,這樣就非常有利于業(yè)務(wù)JS在網(wǎng)絡(luò)中進(jìn)行傳遞與動(dòng)態(tài)加載。
3.3 動(dòng)態(tài)加載與實(shí)驗(yàn)驗(yàn)證
在分離了JS代碼之后,就可以針對(duì)具體的業(yè)務(wù)場(chǎng)景進(jìn)行優(yōu)化。由于基礎(chǔ)JS文件較大,不適合在網(wǎng)絡(luò)中進(jìn)行傳輸,因此可以將其緩存在本地。而每次啟動(dòng)應(yīng)用進(jìn)行更新時(shí),只需要下載體積非常小的業(yè)務(wù)JS。流程如下圖所示:
應(yīng)用啟動(dòng)后,加載本地的基礎(chǔ)JS代碼,且在不同的業(yè)務(wù)場(chǎng)景切換時(shí)不再次加載。即基礎(chǔ)JS在各個(gè)業(yè)務(wù)場(chǎng)景間進(jìn)行共享。其次從網(wǎng)絡(luò)中下載業(yè)務(wù)JS代碼,由于單個(gè)業(yè)務(wù)場(chǎng)景邏輯本身實(shí)現(xiàn)并不需要非常大的代碼量,因此一般業(yè)務(wù)JS打包并壓縮之后可以保持在SKB左右,使用2G網(wǎng)絡(luò)進(jìn)行下載時(shí)僅僅需要1秒就可以完成。同時(shí)由于不再需要加載基礎(chǔ)JS,渲染業(yè)務(wù)場(chǎng)景的時(shí)間也會(huì)更少,經(jīng)過實(shí)驗(yàn)驗(yàn)證僅需10到50毫秒即可渲染完畢。相較于之前的200毫秒有較大的提升。如下表所示:
4 結(jié)論
React Native不同于其他的跨平臺(tái)開發(fā)框架,它同時(shí)解決了開發(fā)成本和運(yùn)行效率的兩個(gè)關(guān)鍵問題。在此基礎(chǔ)之上本論文優(yōu)化了其工程代碼結(jié)構(gòu),將基礎(chǔ)JS與業(yè)務(wù)JS相互隔離,兩部分可獨(dú)立更新與迭代替換。其次在使用React Native進(jìn)行動(dòng)態(tài)加載時(shí),僅需加載業(yè)務(wù)JS即可,大幅減少了在移動(dòng)網(wǎng)絡(luò)中傳輸?shù)臄?shù)據(jù)大小,加快了界面的載入速度,同時(shí)也明顯提升了應(yīng)用的用戶體驗(yàn)。