期待已久的新課上線啦!解鎖React Native開發(fā)新姿勢(shì),一網(wǎng)打盡React Native最新與最熱技術(shù),點(diǎn)我Get!!!
在React Native的應(yīng)用場(chǎng)景中,有時(shí)候一個(gè)APP只有部分頁(yè)面是由React Native實(shí)現(xiàn)的,比如:我們常用的攜程App,它的首頁(yè)下的很多模塊都是由React Native實(shí)現(xiàn)的,這種開發(fā)模式被稱為混合開發(fā)。
混合開發(fā)的一些其他應(yīng)用場(chǎng)景:
在原有項(xiàng)目中加入RN頁(yè)面,在RN項(xiàng)目中加入原生頁(yè)面
原生頁(yè)面中嵌入RN模塊
RN頁(yè)面中嵌入原生模塊
以上這些都屬于React Native混合開發(fā)的范疇,那么如何進(jìn)行React Native混合開發(fā)呢?
在這篇文章中我將向大家介紹React Native混合開發(fā)的流程,需要掌握的技術(shù),以及一些經(jīng)驗(yàn)技巧,與該文章配套的還有React Native與Android 混合開發(fā)講解的視頻教程。
React Native混合開發(fā)的教程我們分為上下兩篇,上篇主要介紹如何在現(xiàn)有的Android應(yīng)用上進(jìn)行React Native混合開發(fā),下篇主要介紹如何在現(xiàn)有的iOS應(yīng)用上進(jìn)行React Native混合開發(fā)。
將React Native集成到現(xiàn)有的Android應(yīng)用中需要如下幾個(gè)主要步驟:
- 首先,你需要有一個(gè)React Native項(xiàng)目;
- 為已存在的Android應(yīng)用添加React Native所需要的依賴;
- 創(chuàng)建index.js并添加你的React Native代碼;
- 創(chuàng)建一個(gè)Activity來(lái)承載React Native,在這個(gè)Activity中創(chuàng)建一個(gè)ReactRootView來(lái)作為React Native服務(wù)的容器;
- 啟動(dòng)React Native的Packager服務(wù),運(yùn)行應(yīng)用;
- (可選)根據(jù)需要添加更多React Native的組件;
- 運(yùn)行、調(diào)試、打包、發(fā)布應(yīng)用;
- 升職加薪、迎娶白富美,走向人生巔峰!;
1. 創(chuàng)建一個(gè)React Native項(xiàng)目
在做混合開發(fā)之前我們首先需要?jiǎng)?chuàng)建一個(gè)沒(méi)有Android和iOS模塊的React Native項(xiàng)目。我們可以通過(guò)兩種方式來(lái)創(chuàng)建一個(gè)這樣的React Native項(xiàng)目:
- 通過(guò)
npm
安裝react-native的方式添加一個(gè)React Native項(xiàng)目; - 通過(guò)
react-native init
來(lái)初始化一個(gè)React Native項(xiàng)目;
通過(guò)npm
安裝react-native的方式添加一個(gè)React Native項(xiàng)目
第一步:創(chuàng)建一個(gè)名為RNHybridApp
的目錄,然后在該目錄下添加一個(gè)包含如下信息的package.json
:
{
"name": "RNHybrid",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
}
}
第二步:在為package.json添加react-native
在該目錄下執(zhí)行:
npm install --save react-native
執(zhí)行完上述命令之后,你會(huì)看到如下警告:
其中,有一條警告npm WARN react-native@0.55.4 requires a peer of react@16.3.1 but none is installed
告訴我們需要安裝react@16.3.1
:
npm install --save react@16.3.1
至此,一個(gè)不含Android和iOS模塊的React Native項(xiàng)目便創(chuàng)建好了。此過(guò)程所遇到的更多問(wèn)題可查閱:React Native與Android 混合開發(fā)講解的視頻教程
提示:npm 會(huì)在你的目錄下創(chuàng)建一個(gè)
node_modules
,node_modules
體積很大且是動(dòng)態(tài)生成了,建議將其添加到.gitignore
文件中;
通過(guò)react-native init來(lái)初始化一個(gè)React Native項(xiàng)目
除了上述方式之外,我們也可以通過(guò)react-native init
命令來(lái)初始化一個(gè)React Native項(xiàng)目。
react-native init RNHybrid
上述命令會(huì)初始化一個(gè)完成的名為RNHybrid的React Native項(xiàng)目,然后我們將里面的android
和ios
目錄刪除,替換成已存在Android和iOS項(xiàng)目。
2. 添加React Native所需要的依賴
在上文中我們已經(jīng)創(chuàng)建了個(gè)一個(gè)React Native項(xiàng)目,接下來(lái)我們來(lái)看一下如何將這個(gè)React Native項(xiàng)目和我們已經(jīng)存在的Native項(xiàng)目進(jìn)行融合。
在進(jìn)行融合之前我們需要將已經(jīng)存在的Native項(xiàng)目放到我們創(chuàng)建的RNHybrid下,比如:我有一個(gè)名為RNHybridAndroid
的Android項(xiàng)目,將其放到RNHybrid目錄下:
RNHybrid
├── RNHybridAndroid
├── package.json
├── node_modules
└── .gitignore
第一步:配置maven
接下來(lái)我們需要為已經(jīng)存在的RNHybridAndroid項(xiàng)目添加 React Native依賴,在RNHybrid/RNHybridAndroid/app/build.gradle
文件中添加如下代碼:
dependencies {
compile 'com.android.support:appcompat-v7:23.0.1'
...
compile "com.facebook.react:react-native:+" // From node_modules
}
然后,我們?yōu)镽NHybridAndroid項(xiàng)目配置使用的本地React Native maven目錄,在RNHybrid/RNHybridAndroid/build.gradle
文件中添加如下代碼:
allprojects {
repositories {
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
...
}
...
}
提示:為確保你配置的目錄正確,可以通過(guò)在Android Studio中運(yùn)行Gradle sync 看是否有 “Failed to resolve: com.facebook.react:react-native:0.x.x" 的錯(cuò)誤出現(xiàn),沒(méi)有錯(cuò)誤則說(shuō)明配置正確,否則說(shuō)明配置路由有問(wèn)題。
此過(guò)程所遇到的更多問(wèn)題可查閱:React Native與Android 混合開發(fā)講解的視頻教程
第二步:配置權(quán)限
接下來(lái)我們?yōu)锳PP運(yùn)行配置所需要的權(quán)限:檢查你項(xiàng)目中的AndroidManifest.xml
文件中看是否有如下權(quán)限:
<uses-permission android:name="android.permission.INTERNET" />
如果沒(méi)有,則需要將上述權(quán)限添加到AndroidManifest.xml
中。
另外,如果你需要用到RN的
Dev Settings
功能:
則需要在AndroidManifest.xml
文件中添加如下代碼:
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
提示:上述圖片就是RN 開發(fā)調(diào)試彈框中的
Dev Settings
功能,打開該功能會(huì)彈出上圖的一個(gè)界面,這個(gè)界面就是DevSettingsActivity。
第三步:指定要ndk需要兼容的架構(gòu)(重要)
Android不能同時(shí)加載多種架構(gòu)的so庫(kù),現(xiàn)在很多Android第三方sdks對(duì)abi的支持比較全,可能會(huì)包含armeabi, armeabi-v7a,x86, arm64-v8a,x86_64五種abi,如果不加限制直接引用會(huì)自動(dòng)編譯出支持5種abi的APK,而Android設(shè)備會(huì)從這些abi進(jìn)行中優(yōu)先選擇某一個(gè),比如:arm64-v8a,但如果其他sdk不支持這個(gè)架構(gòu)的abi的話就會(huì)出現(xiàn)crash。如下圖:
怎么解決呢:
在app/gradle
文件中添加如下代碼:
defaultConfig {
....?
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
上述代碼的意思是,限制打包的so庫(kù)只包含armeabi-v7a
與x86
。此過(guò)程所遇到的更多問(wèn)題可查閱:React Native與Android 混合開發(fā)講解的視頻教程
可參考:libgnustl_shared.so" is 32-bit instead of 64-bit
3.創(chuàng)建index.js并添加你的React Native代碼
通過(guò)上述兩步,我們已經(jīng)為RNHybridAndroid項(xiàng)目添加了React Native依賴,接下來(lái)我們來(lái)開發(fā)一些JS代碼。
在RNHybrid目錄下創(chuàng)建一個(gè)index.js
文件并添加如下代碼:
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('App1', () => App);
上述代碼,AppRegistry.registerComponent('App1', () => App);
目的是向React Native注冊(cè)一個(gè)名為App1
的組件,然后我會(huì)在第四步給大家介紹如何在Android中加載并顯示出這個(gè)組件。
另外,在上述代碼中我們引用了一個(gè)App.js
文件:
import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,
View
} from 'react-native';
type Props = {};
export default class App extends Component<Props> {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
this is App
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
}
});
這個(gè)App.js
文件代表了我們React Native的一個(gè)頁(yè)面,在這個(gè)頁(yè)面中顯示了this is App
的文本內(nèi)容。
以上就是為本次演示所添加的React Native代碼,你也可以根據(jù)需要添加更多的React Native代碼以及組件出來(lái)。
4. 為React Native創(chuàng)建一個(gè)Activity來(lái)作為容器
經(jīng)過(guò)上述3、4步,我們已經(jīng)為RNHybridAndroid項(xiàng)目添加了React Native依賴,并且創(chuàng)建一些React Native代碼和注冊(cè)了一個(gè)名為App1
的組件,接下來(lái)我們來(lái)學(xué)習(xí)下如何在RNHybridAndroid項(xiàng)目中使用這個(gè)App1
組件。
創(chuàng)建RNPageActivity
首先我們需要?jiǎng)?chuàng)建一個(gè)Activity來(lái)作為React Native的容器,
public class RNPageActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReactRootView = new ReactRootView(this);
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// 這個(gè)"App1"名字一定要和我們?cè)趇ndex.js中注冊(cè)的名字保持一致AppRegistry.registerComponent()
mReactRootView.startReactApplication(mReactInstanceManager, "App1", null);
setContentView(mReactRootView);
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
參數(shù)說(shuō)明
-
setBundleAssetName
:打包時(shí)放在assets
目錄下的JS bundle包的名字,App release之后會(huì)從該目錄下加載JS bundle; -
setJSMainModulePath
:JS bundle中主入口的文件名,也就是我們上文中創(chuàng)建的那個(gè)index.js
文件; -
addPackage
:向RN添加Native Moudle,在上述代碼中我們添加了new MainReactPackage()
這個(gè)是必須的,另外,如果我們創(chuàng)建一些其他的Native Moudle也需要通過(guò)addPackage
的方式將其注冊(cè)到RN中。需要指出的是RN除了這個(gè)方法外,也提供了一個(gè)addPackages
方法用于批量向RN添加Native Moudle; -
setUseDeveloperSupport
:設(shè)置RN是否開啟開發(fā)者模式(debugging,reload,dev memu),比如我們常用開發(fā)者彈框; -
setInitialLifecycleState
:通過(guò)這個(gè)方法來(lái)設(shè)置RN初始化時(shí)所處的生命周期狀態(tài),一般設(shè)置成LifecycleState.RESUMED
就行,和下文講的Activity容器的生命周期狀態(tài)關(guān)聯(lián); -
mReactRootView.startReactApplication
:它的第一個(gè)參數(shù)是mReactInstanceManager
,第二個(gè)參數(shù)是我們?cè)?code>index.js中注冊(cè)的組件的名字,第三個(gè)參數(shù)接受一個(gè)Bundle
來(lái)作為RN初始化時(shí)傳遞給JS的初始化數(shù)據(jù),它的具體用法我會(huì)在React Android 混合開發(fā)講解的視頻教程中再具體的講解;
在中AndroidManifest.xml
注冊(cè)一個(gè)RNPageActivity
Android系統(tǒng)要求,每一個(gè)要打開的Activity都要在AndroidManifest.xml
中進(jìn)行注冊(cè):
<activity
android:name=".RNPageActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
上述代碼中我們?yōu)?code>RNPageActivity添加了一個(gè)
@style/Theme.AppCompat.Light.NoActionBar
類型的theme,這也是React Native UI組件所要求的主題。
為ReactInstanceManager添加Activity的生命周期回調(diào)
一個(gè) ReactInstanceManager可以被多個(gè)activities或fragments共享,所以我們需要在Activity的生命周期中回調(diào)ReactInstanceManager的對(duì)于的方法。
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause(this);
}
}
@Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy(this);
}
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
}
}
從上述代碼中你會(huì)發(fā)現(xiàn)有個(gè)不屬于Activity生命周期中的方法onBackPressed
,添加它的目的主要是為了當(dāng)用戶單擊手機(jī)的返回鍵之后將事件傳遞給JS,如果JS消費(fèi)了這個(gè)事件,Native就不再消費(fèi)了,如果JS沒(méi)有消費(fèi)這個(gè)事件那么RN會(huì)回調(diào)invokeDefaultOnBackPressed
代碼。
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
此過(guò)程更細(xì)致的講解可查閱:React Native與Android 混合開發(fā)講解的視頻教程
添加開發(fā)者菜單
在RN中有個(gè)很好用的工具開發(fā)者菜單,我們平時(shí)調(diào)試RN應(yīng)用時(shí)對(duì)它的使用頻率很高,接下來(lái)我們來(lái)為RNHybridAndroid添加開著菜單。
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getUseDeveloperSupport()) {
if (keyCode == KeyEvent.KEYCODE_MENU) {//Ctrl + M 打開RN開發(fā)者菜單
mReactInstanceManager.showDevOptionsDialog();
return true;
}
}
return super.onKeyUp(keyCode, event);
}
通過(guò)上代碼即可監(jiān)聽(tīng)Ctrl + M
來(lái)打開RN開發(fā)者菜單。
另外,RN也提供了雙擊R來(lái)快速加載JS的功能,通過(guò)如下代碼即可打開該功能:
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getUseDeveloperSupport()) {
if (keyCode == KeyEvent.KEYCODE_MENU) {//Ctrl + M 打開RN開發(fā)者菜單
mReactInstanceManager.showDevOptionsDialog();
return true;
}
boolean didDoubleTapR = Assertions.assertNotNull(mDoubleTapReloadRecognizer).didDoubleTapR(keyCode, getCurrentFocus());
if (didDoubleTapR) {//雙擊R 重新加載JS
mReactInstanceManager.getDevSupportManager().handleReloadJS();
return true;
}
}
return super.onKeyUp(keyCode, event);
}
此過(guò)程更細(xì)致的講解可查閱:React Native與Android 混合開發(fā)講解的視頻教程
使用ReactActivity來(lái)作為RN容器
在上述的代碼中我們都是通過(guò)ReactInstanceManager
來(lái)創(chuàng)建和加載JS的,然后重寫了Activity的生命周期來(lái)對(duì)ReactInstanceManager
進(jìn)行回調(diào),另外,重寫了onKeyUp
來(lái)啟用開發(fā)者菜單等功能。
另外,查看RN的源碼你會(huì)發(fā)現(xiàn)在RN sdk中有個(gè)叫ReactActivity
的Activity,該Activity是RN官方封裝的一個(gè)RN容器。另外,在通過(guò)react-native init
命令初始化的一個(gè)項(xiàng)目中你會(huì)發(fā)現(xiàn)有個(gè)MainActivity
是繼承ReactActivity
的,接下來(lái)我們就來(lái)繼承ReactActivity
來(lái)封裝一個(gè)RN容器。
public class ReactPageActivity extends ReactActivity implements IJSBridge{
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "App1";
}
}
另外,我們需要實(shí)現(xiàn)一個(gè)MainApplication
并添加如下代碼:
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}
上述代碼的主要作用是為ReactActivity
提供ReactNativeHost
,查看源碼你會(huì)發(fā)現(xiàn)在ReactActivity
中使用了ReactActivityDelegate
,在ReactActivityDelegate
中會(huì)用到MainApplication
中提供的ReactNativeHost
:
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
}
另外實(shí)現(xiàn)了
MainApplication
之后需要在AndroidManifest.xml
中添加MainApplication
:
<application
android:name=".MainApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
以上就是通過(guò)繼承ReactActivity
的方式來(lái)作為RN容器的。
那么這兩種方式各有什么特點(diǎn):
- 通過(guò)
ReactInstanceManager
的方式:靈活,可定制性強(qiáng); - 通過(guò)繼承
ReactActivity
的方式:簡(jiǎn)單,可定制性差;
此過(guò)程更細(xì)致的講解可查閱:React Native與Android 混合開發(fā)講解的視頻教程
5. 運(yùn)行React Native
經(jīng)過(guò)上述的步驟,我們已經(jīng)完成了對(duì)一個(gè)現(xiàn)有Android項(xiàng)目RNHybridAndroid添加了RN,并且通過(guò)兩種方式分別創(chuàng)建了一個(gè)RNPageActivity
與ReactPageActivity
的Activity來(lái)加載我們?cè)贘S中注冊(cè)的名為App1
的RN 組件。
接下來(lái)我們來(lái)啟動(dòng)RN服務(wù)器,運(yùn)行RNHybridAndroid項(xiàng)目打開RNPageActivity
或ReactPageActivity
來(lái)查看效果:
npm start
在RNHybrid
的根目錄運(yùn)行上述命令,來(lái)啟動(dòng)一個(gè)RN本地服務(wù):
然后我們打開AndroidStudio,點(diǎn)擊運(yùn)行按鈕或者通過(guò)快捷鍵Ctrl+R
來(lái)將RNHybridAndroid
安裝到模擬器上:
6. 添加更多React Native的組件
我們可以根據(jù)需要添加更多的React Native的組件:
import { AppRegistry } from 'react-native';
import App from './App';
import App2 from './App2';
AppRegistry.registerComponent('App1', () => App);
AppRegistry.registerComponent('App2', () => App);
然后,在Native中根據(jù)需要加載指定名字的RN組件即可。
7. 調(diào)試、打包、發(fā)布應(yīng)用
調(diào)試
調(diào)試這種混合的RN應(yīng)用和調(diào)試一個(gè)純RN應(yīng)用時(shí)一樣的,都是通過(guò)上文中說(shuō)講到的RN 開發(fā)者菜單
,另外搭建也可以通過(guò)學(xué)習(xí)React Native技術(shù)精講與高質(zhì)量上線APP開發(fā)課程來(lái)掌握更多RN調(diào)試的技巧。
打包
雖讓,通過(guò)上述步驟,我們將RN和我們的RNHybridAndroid項(xiàng)目做了融合,但打包RNHybridAndroid你會(huì)發(fā)現(xiàn)里面并不包含JS部分的代碼,如果要將JS代碼打包進(jìn)Android Apk包中,可以通過(guò)如下命令:
react-native bundle --platform android --dev false --entry-file index.js --bundle-output RNHybridAndroid/app/src/main/assets/index.android.bundle --assets-dest RNHybridAndroid/app/src/main/res/
參數(shù)說(shuō)明
-
--platform android
:代表打包導(dǎo)出的平臺(tái)為Android; -
--dev false
:代表關(guān)閉JS的開發(fā)者模式; -
-entry-file index.js
:代表js的入口文件為index.js
; -
--bundle-output
:后面跟的是打包后將JS bundle包導(dǎo)出到的位置; -
--assets-dest
:后面跟的是打包后的一些資源文件導(dǎo)出到的位置;
提示:JS bundle一定要正確放到你的Android言語(yǔ)的assets目錄下這個(gè)和我們上文中配置的
setBundleAssetName("index.android.bundle")
進(jìn)行對(duì)應(yīng)。
發(fā)布應(yīng)用
通過(guò)上述步驟我們完成了將RN代碼打包并生成JS bundle,并放到了assets目錄下,接下來(lái)我們就可以來(lái)通過(guò)Android Studio或者命令的方式來(lái)release我們的RN混合Android應(yīng)用了。
我在之前發(fā)表過(guò)React Native發(fā)布APP之簽名打包APK的博文,
需要的同學(xué)可以去看一下,在這篇文章中就不在重復(fù)了。
更多React Native混合開發(fā)的實(shí)用技巧,可學(xué)習(xí)與此文章配套的視頻課程:《React Native與Android 混合開發(fā)講解》