1 熱發(fā)布
網(wǎng)頁發(fā)布 VS APP發(fā)布
網(wǎng)頁發(fā)布:服務(wù)端上線新的網(wǎng)頁代碼,用戶端通過鏈接直接訪問。
APP發(fā)布:?發(fā)布新的包裝包(應(yīng)用市場(chǎng):上傳新的包裝包;應(yīng)用內(nèi)升級(jí):服務(wù)端發(fā)布新的包裝包),用戶端下載,安裝。
網(wǎng)頁發(fā)布屬于熱發(fā)布,完全?受控于服務(wù)端
APP發(fā)布的限制:
應(yīng)用市場(chǎng)審核
用戶端下載,安裝
APP熱發(fā)布的目的是實(shí)現(xiàn)類似于網(wǎng)頁的發(fā)布方式,排除?更新對(duì)于應(yīng)用市場(chǎng)和用戶的依賴,完全由服務(wù)端控制。
2 APP熱發(fā)布的方式
2.1 H5熱發(fā)布
功能完全由H5實(shí)現(xiàn),APP使用WebView來加載。
優(yōu)勢(shì):完全熱發(fā)布
劣勢(shì):
- 性能-H5的性能相比Native差得比較多,交互體驗(yàn)較差。
- 緩存-H5的緩存受制于系統(tǒng)WebView,在無網(wǎng)或緩存過期情況下無法呈現(xiàn)界面。
2.2 數(shù)據(jù)熱發(fā)布
數(shù)據(jù)由服務(wù)端控制,動(dòng)態(tài)發(fā)布,APP根據(jù)數(shù)據(jù)來展現(xiàn)UI,實(shí)現(xiàn)UI有限的動(dòng)態(tài)性(UI動(dòng)態(tài)支持預(yù)先在APP中定義好)。
這里的數(shù)據(jù)也包括腳本數(shù)據(jù)的情況,APP通過腳本引擎執(zhí)行腳本,根據(jù)結(jié)果來執(zhí)行對(duì)應(yīng)邏輯。
優(yōu)勢(shì):數(shù)據(jù)層面熱發(fā)布
劣勢(shì):不能實(shí)現(xiàn)UI和邏輯的完全熱發(fā)布
2.3 插件化熱發(fā)布
APP的獨(dú)立功能作為插件,動(dòng)態(tài)下載,動(dòng)態(tài)運(yùn)行,插件發(fā)布由服務(wù)端動(dòng)態(tài)控制。
優(yōu)勢(shì):功能?性熱發(fā)布
劣勢(shì):使用非系統(tǒng)公開API,有失效風(fēng)險(xiǎn)
2.4 React Native
還有一種思路,APP作為基礎(chǔ)容器,應(yīng)用功能作為腳本文件,動(dòng)態(tài)下發(fā),?通過解析引擎動(dòng)態(tài)執(zhí)行,展現(xiàn)為Native UI,同時(shí)控制數(shù)據(jù)和業(yè)務(wù)邏輯。
腳本文件發(fā)布由服務(wù)端動(dòng)態(tài)控制,能夠?qū)崿F(xiàn)整個(gè)APP或者部分功能完全熱發(fā)布。
React Native是facebook開源的構(gòu)建跨平臺(tái)應(yīng)用的框架,它的原理是,通過JavascriptCore引擎,解析JSX(類似XML的Javascript擴(kuò)展語言)腳本代碼,生成Virtual DOM,再轉(zhuǎn)換為Native視圖,同時(shí)通過橋接JS和Native,實(shí)現(xiàn)事件交互。
React Native通過Virtual DOM,以diff的方式交給Native渲染,?其diff算法相當(dāng)高效,能夠保證良好的Native性能。這是它優(yōu)于H5的地方。
優(yōu)勢(shì):
- 完全熱發(fā)布
- 良好的Native性能
- 跨平臺(tái)復(fù)用:Andriod, iOS, H5。
- 大廠出品:持續(xù)維護(hù)性(兩周一個(gè)release版本);開放性,三方開源庫支持。
劣勢(shì):
- 成熟度不夠。
- 目前支持Android4.1和iOS7.0以上系統(tǒng)版本。
對(duì)動(dòng)態(tài)性要求較高的APP,React Native提供了一種解決方案,實(shí)現(xiàn)類似網(wǎng)頁的發(fā)布效果,同時(shí)保證良好的Native性能
使用React Native的APP:
Facebook,QQ,QQ音樂,QQ空間(發(fā)現(xiàn)Tab)
此外,Weex是阿里開源的移動(dòng)端跨平臺(tái)開發(fā)框架,原理與React Native類似,只不過它的腳本語言換成了Vue(也是JS框架)。
3 React Native原理
3.1 React Native集成
React Native(以下簡(jiǎn)稱RN)的集成參考官網(wǎng):
https://facebook.github.io/react-native/docs/getting-started.html
3.2 RN目錄結(jié)構(gòu)
- *.android.js:Android 的JS文件
- *.ios.js:iOS的JS文件
- android:Android的Native工程
- ios:iOS的Native工程
- node_modules:RN基于NodeJS的基礎(chǔ)庫。包括基本組件,以及打包腳本等等。
3.3 RN原理
?
Java層
Java層為應(yīng)用的入口,啟動(dòng)C++層的JS解析器,執(zhí)行JS,并通過C++傳遞的渲染指令,從而構(gòu)建Native UI等。核心邏輯在ReactCore中完成。在UI層面,ReactCore完成了對(duì)Native視圖組件的封裝,它與JS層定義的組件是一一映射的。
Java層集成眾多優(yōu)秀開源庫,圖片處理使用的是Fresco,網(wǎng)絡(luò)通信使用的是okhttp。JS層的組件會(huì)通過這些基礎(chǔ)庫來實(shí)現(xiàn)功能,如Image組件使用Fresco來加載遠(yuǎn)程圖片,fetch組件使用okhttp來進(jìn)行網(wǎng)絡(luò)請(qǐng)求。
C++層
C++層主要封裝了JavaScriptCore(Android和iOS通用的JS引擎),用來實(shí)現(xiàn)JS的解析和執(zhí)行。基于JavaScriptCore,意味著JS可以使用ES6的新特性,如class、箭頭操作符等。
Bridge?實(shí)現(xiàn)Java和 JS之間的通信。
JSLoader用來加載JS文件,包括assets目錄和本地文件。
JS層
主要實(shí)現(xiàn)UI布局和事件分發(fā),主要有以下幾個(gè)部分:
Component:JS層通JS/JSX編寫的Component來構(gòu)建Virtual DOM,Virtual DOM是DOM在內(nèi)存中的一種輕量級(jí)表達(dá)方式,可以通過不同的渲染引擎生成不同平臺(tái)下的UI。Component的存在讓計(jì)算 DOM diff 更高效,從而使得映射到Native的視圖?渲染有很好的性能。
Layout:React使用css-layout,css-layout使用JS實(shí)現(xiàn)了flexbox ,能編譯成Native代碼,最終達(dá)到跨平臺(tái)的展示目的。
Lifecycle&Data:React 組件通過內(nèi)部的 state 變量控制生命周期及事件回調(diào)。如getInitialState方法用于定義組件初始狀態(tài),后續(xù)組件可通過 state 屬性讀取該狀態(tài)。當(dāng)?調(diào)用setState 方法就修改狀態(tài)值時(shí),RN會(huì)自動(dòng)調(diào)用 render 方法,重新渲染組件。
4 Android的RN集成
Android上,應(yīng)用是通過Activity來承載的。
復(fù)雜應(yīng)用通常由多個(gè)模塊組成,每個(gè)模塊對(duì)應(yīng)一個(gè)Activity。簡(jiǎn)單來說,我們可以以Activity為模塊單元。
4.1 Activity與bundle
RN通過bundle來承載應(yīng)用。bundle包括JS腳本代碼及其引用的資源(圖片)。
在Android上,我們?yōu)槊總€(gè)Activity綁定一個(gè)bundle,來實(shí)現(xiàn)模塊的獨(dú)立管理。
在Native中,我們通過BaseReactActivity?來連接RN,它通過JsBundle和Component與Bundle關(guān)聯(lián)起來。
Bundle兩種形式,一個(gè)是Assets,打包在apk中一起發(fā)布;一個(gè)File,它是動(dòng)態(tài)更新下載的。在有File時(shí)使用File,沒有File時(shí)使用Assets。
Activity的注冊(cè)我們只在Manifest中定義幾個(gè)Activity模板。例如,
- SingleTaskActivity:launch mode為SingleTask
- StandardActivity:launch mode為Standard
- WebViewActivity:process為單獨(dú)的web進(jìn)程
Activity跳轉(zhuǎn)時(shí),指定對(duì)應(yīng)的模板Activity,傳遞bundle信息即可。
4.2 JS
JS文件結(jié)構(gòu)(avcdemo.android.js)
import React, {Component} from 'React'; //引入React組件
import {
AppRegistry,
View,
Text,
Image
} from 'react-native'; //引入RN組件
class AVCDemo extends Component { //定義Component
render() { //render定義視圖結(jié)構(gòu)
return(
<View style={styles.full}>
<Text style={styles.text}>Hello RN</Text>
<Image source={require('./res/demo/kaola.png')} style={styles.img}></Image>
</View>
)
}
}
var styles = { //定義樣式
full: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
},
text: {
fontSize: 80
},
img: {
width: 200,
height: 200
}
}
AppRegistry.registerComponent('demo', () => AVCDemo); //注冊(cè)Component
4.3 Native Activity
DemoActivity
public class DemoActivity extends AppReactActivity {
@Override
protected String getMainComponentName() {
return "demo";
}
@Nullable
@Override
protected String getBundleAssetName() {
return "avcdemo.android.bundle";
}
}
可以看到Activity通過bundle最終綁定到Component。
4.4 Component
Component有幾個(gè)基本概念:props,state,render,生命周期。
props:靜態(tài)屬性,提供定制參數(shù)給調(diào)用方。如Image的source。
state:動(dòng)態(tài)屬性,內(nèi)部使用。當(dāng)通過setState方法改變時(shí),會(huì)調(diào)用render方法進(jìn)行重新渲染。比如,我們獲取網(wǎng)絡(luò)數(shù)據(jù)回來時(shí)需要改變界面,這時(shí)就需要將網(wǎng)絡(luò)數(shù)據(jù)作為一個(gè)動(dòng)態(tài)屬性,同時(shí)render方法中使用?該屬性來進(jìn)行渲染。
render:渲染方法,定義Component的視圖結(jié)構(gòu)。
與Activity類似,Component也有生命周期。
4.5 RN組件
下面來看看RN對(duì)應(yīng)用實(shí)現(xiàn)的支持。首先是組件。
實(shí)現(xiàn)應(yīng)用的組件大致分為4個(gè)層面:
- UI
- 網(wǎng)絡(luò)
- 存儲(chǔ)
- 線程
4.5.1 UI
UI層面又可以細(xì)分為:UI組件,動(dòng)畫,交互事件。
UI組件
RN提供了較為豐富的基礎(chǔ)組件,以及部分復(fù)雜組件。
UI組件本質(zhì)上會(huì)做一個(gè)到Native組件的映射。
RN UI組件 | Android UI組件 | |
---|---|---|
View | -> | ViewGroup |
Image | -> | ImageView |
Text | -> | TextView |
TextInput | -> | EditText |
ViewPagerAndroid | -> | ViewPager |
WebView | -> | WebView |
ScrollView | -> | ScrollView |
ListView | -> | ScrollView |
RN的ListView并沒有采用Native的ListView,而是基于RN的ScrollView,在js層來處理ListView的邏輯。
動(dòng)畫
RN提供了兩種動(dòng)畫方式:Animated和LayoutAnimation。
Animated與Android的屬性動(dòng)畫類似,可以實(shí)現(xiàn)透明度,縮放和位移等效果。
LayoutAnimation與Android LayoutAnimation類似,作用于?加入和移除視圖樹。
交互事件
交互事件主要有兩種:點(diǎn)擊和Touch,按鍵。
RN提供Touchablexxx(TouchableOpacity,TouchableHighlight等)系列組件來實(shí)現(xiàn)點(diǎn)擊、長按、滑動(dòng)交互。
onPressIn:點(diǎn)擊開始;
onPressOut:點(diǎn)擊結(jié)束或者離開;
onPress:?jiǎn)螕羰录卣{(diào);
onLongPress:長按事件回調(diào)。
事件攔截(類似于Android的onInterceptTouchEvent)
onStartShouldSetResponder
onMoveShouldSetResponder
按鍵處理,RN提供了Andorid的back鍵處理BackAndroid
4.5.2 網(wǎng)絡(luò)
RN提供fetch API用于網(wǎng)絡(luò)請(qǐng)求,它本質(zhì)是基于OkHttp實(shí)現(xiàn)。
它支持GET,POST等基本方法,包括header的傳遞,以及響應(yīng)json格式化。
另外,它也提供XMLHttpRequest和WebSocket的支持。
4.5.3 存儲(chǔ)
RN提供了簡(jiǎn)單的持久存儲(chǔ)組件AsyncStorage。
它支持key-value存儲(chǔ),效果類似于Android的SharedPreference,實(shí)際上它是存儲(chǔ)在本地的sqlite數(shù)據(jù)庫中。
如果需要傳統(tǒng)的sqlite存儲(chǔ),需要自己實(shí)現(xiàn)。會(huì)有一些開源組件供實(shí)現(xiàn)參考:https://github.com/jbrodriguez/react-native-android-sqlite。
4.5.4 線程
RN的應(yīng)用邏輯是運(yùn)行JS線程上的,獨(dú)立于Android的UI線程。JS線程上的更新批量傳遞給Native作UI更新。
RN提供Timers來實(shí)現(xiàn)異步邏輯。 可以通過JS本身的異步接口來實(shí)現(xiàn)(async/await)。
4.6 RN擴(kuò)展
RN官方提供的組件能夠滿足基本業(yè)務(wù)需求,如果要實(shí)現(xiàn)復(fù)雜應(yīng)用,需要進(jìn)行擴(kuò)展。
目前很多RN開源組件:https://github.com/jondot/awesome-react-native。
另外,也可以自己定制。定制分為幾個(gè)方面:
- JS調(diào)用Native
- 定制JS層組件
- 使用Native UI組件
4.6.1 JS調(diào)用Native
當(dāng)JS層無法滿足需求時(shí),通過需要Native層支持提供接口,來供JS層調(diào)用。RN提供Native Modules的方式來支持。
簡(jiǎn)單來說,就是Native按照規(guī)則定義好接口,同時(shí)注冊(cè)到RN中,JS層直接調(diào)用即可。
顯然,這種方式定義的Native Module不能動(dòng)態(tài)發(fā)布,它需要固化在APP中。
4.6.2 定制?JS層組件
Component組件
以ImageText為例(ImageText.js)
'use strict';
import React, {Component} from 'React';
import {
View,
Image,
Text,
} from 'react-native';
class ImageText extends Component {
props: {
imgUrl?: string,
text?: string,
}
render() {
return(
<View style={styles.container}>
<Image style={styles.img} source={{uri: this.props.imgUrl}}></Image>
<Text style={styles.text}>{this.props.text}</Text>
</View>
)
}
}
var styles = {
container: {
flexDirection: 'row',
marginRight: 8,
},
img: {
width: 14,
height: 14,
},
text: {
},
}
module.exports = ImageText;
?然后將Component輸出:
module.exports = ImageText;
方法組件
以網(wǎng)絡(luò)請(qǐng)求為例,網(wǎng)絡(luò)請(qǐng)求通常會(huì)加上一些能用參數(shù)。可以定義一個(gè)網(wǎng)絡(luò)請(qǐng)求組件(Requestor.js)。
'use strict';
import HOST from './Host';
function get(url, headers) {
if (headers == null) {
headers = {};
}
headers[HOST.API_VERSION_KEY] = HOST.API_VERSION_VALUE;
console.log("headers: " + JSON.stringify(headers));
return fetch(url, {
headers: headers
})
}
module.exports = {get};
需要將方法組件輸出:
module.exports = {get};
常量組件
'use strict';
var apiVersion = 200;
module.exports = {
HOST: "http://api.kkmoving.com",
API_VERSION_KEY: "apiVersion",
API_VERSION_VALUE: apiVersion
};
4.6.3 使用Native UI組件
Native UI組件定義參考Native UI Components
簡(jiǎn)單來說,就是定義好Native UI組件,然后通過ViewManager注冊(cè)到RN中,同時(shí)在JS層定義Component,關(guān)聯(lián)到Native UI組件。然后,這個(gè)Component就可以使用。
?需要注意的是,如果Native UI組件要求自定義大小,需要額外實(shí)現(xiàn)LayoutShadowNode并關(guān)聯(lián)到ViewManager,LayoutShadowNode提供MeasureFunction回調(diào)來實(shí)現(xiàn)組件的丈量。具體實(shí)現(xiàn)可以參考RN的Text組件實(shí)現(xiàn)(Native 代碼:ReactTextViewManager,ReactTextView,ReactTextShadowNode)。
4.7 RN工程打包
RN打包分為兩個(gè)部分,一個(gè)是bundle打包,一個(gè)是APP打包。
bundle打包,是指bundle的JS文件和圖片資源打包為獨(dú)立的文件,?作為更新包供遠(yuǎn)程下載。這是動(dòng)態(tài)bundle。
bundle打包命令:
react-native bundle --platform android --dev false \
--entry-file xxx.android.js \
--bundle-output xxx.android.bundle \
--assets-dest xxx
APP打包,首先執(zhí)行bundle打包,將生成的靜態(tài)bundle跟隨app包一起發(fā)布。保證無網(wǎng)時(shí)能夠展現(xiàn)應(yīng)用基本功能。
在Android上,跟隨打包會(huì)將JS文件打包到assets目錄中,同時(shí)將圖片資源打包到drawable目錄中。
RN打包Android的方式是通過gradle,在正常apk打包過程之前,會(huì)編譯RN的bundle。但默認(rèn)只能打包一個(gè)bundle,如果需要支持打包多個(gè)bundle,需要修改react.gradle文件(node_modules/react-native/react.gradle)。
RN在開發(fā)調(diào)試比較方便,通過react-native run-android
運(yùn)行應(yīng)用,JS文件編輯后,可以直接在應(yīng)用上reload來實(shí)現(xiàn)更新,不需要重新安裝APP,類似網(wǎng)頁直接刷新的效果。
4.8 熱發(fā)布實(shí)現(xiàn)
RN熱發(fā)布的策略相對(duì)簡(jiǎn)單。
在APP端,初始發(fā)布APP時(shí),自帶靜態(tài)bundle,并每個(gè)bundle維護(hù)一個(gè)版本號(hào)。
應(yīng)用在適當(dāng)?shù)臅r(shí)機(jī),檢查bundle是否需要更新。如果有更新,則下載動(dòng)態(tài)bundle。
?應(yīng)用優(yōu)先加載動(dòng)態(tài)bundle,如無動(dòng)態(tài)bundle剛加載靜態(tài)bundle。
在服務(wù)端,?提供動(dòng)態(tài)bundle的發(fā)布,版本維護(hù),更新檢測(cè)和下載。
這是最簡(jiǎn)單的策略,當(dāng)然要做得精細(xì),還需要考慮差量更新,bundle拆包(后面會(huì)講)。
5 ?RN實(shí)踐之商品詳情頁
界面實(shí)現(xiàn)(Demo)
- ScrollView
- ViewPager
- 帶樣式的Text
- 遠(yuǎn)程加載的Image
- 浮層
- 動(dòng)畫
- 地址選擇
- RN WebView
在線更新(Demo)
- 檢查更新
- 下載/解壓
- 應(yīng)用更新
6 踩過的坑
6.1 混淆設(shè)置
在RN中如果要使用Proguard,需要做兩個(gè)設(shè)置:
- 在build.gradle中設(shè)置:
def enableProguardInReleaseBuilds = true
- 在proguard.pro中,基于RN官方Proguard模板,另外設(shè)置:
-keep class com.facebook.** { *; }
這一點(diǎn)比較坑,官方文檔和各種模板中都沒有提到。如果不加一句,release運(yùn)行時(shí)會(huì)直接crash。
6.2 Android Studio設(shè)置
- gradle.properties不能設(shè)置
org.gradle.configureondemand=true
具體原因未知
7 待解決的問題
- RN基礎(chǔ)庫較大(6M)。
- Bundle文件過大(基礎(chǔ)Bundle 500k左右)。Bundle拆包,將RN庫的JS拆離到單獨(dú)的基礎(chǔ)Bundle。
- 復(fù)雜組件的JS實(shí)現(xiàn)。
- 性能分析和優(yōu)化。