在React Native的應用場景中,有時候一個APP只有部分頁面是由React Native實現的,好比:咱們經常使用的攜程App,它的首頁下的不少模塊都是由React Native實現的,這種開發模式被稱爲混合開發。html
混合開發的一些其餘應用場景:java
在原有項目中加入RN頁面,在RN項目中加入原生頁面node
原生頁面中嵌入RN模塊react
RN頁面中嵌入原生模塊android
以上這些都屬於React Native混合開發的範疇,那麼如何進行React Native混合開發呢?ios
在這篇文章中我將向你們介紹React Native混合開發的流程,須要掌握的技術,以及一些經驗技巧,與該文章配套的還有React Native與Android 混合開發講解的視頻教程。git
React Native混合開發的教程咱們分爲上下兩篇,上篇主要介紹**如何在現有的Android應用上進行React Native混合開發,下篇主要介紹如何在現有的iOS應用上進行React Native混合開發**。github
將React Native集成到現有的Android應用中須要以下幾個主要步驟:npm
在作混合開發以前咱們首先須要建立一個沒有Android和iOS模塊的React Native項目。咱們能夠經過兩種方式來建立一個這樣的React Native項目:json
npm
安裝react-native的方式添加一個React Native項目;react-native init
來初始化一個React Native項目;npm
安裝react-native的方式添加一個React Native項目第一步:建立一個名爲RNHybridApp
的目錄,而後在該目錄下添加一個包含以下信息的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
在該目錄下執行:
npm install --save react-native
複製代碼
執行完上述命令以後,你會看到以下警告:
其中,有一條警告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
複製代碼
至此,一個不含Android和iOS模塊的React Native項目便建立好了。此過程所遇到的更多問題可查閱:React Native與Android 混合開發講解的視頻教程
提示:npm 會在你的目錄下建立一個
node_modules
,node_modules
體積很大且是動態生成了,建議將其添加到.gitignore
文件中;
除了上述方式以外,咱們也能夠經過react-native init
命令來初始化一個React Native項目。
react-native init RNHybrid
複製代碼
上述命令會初始化一個完成的名爲RNHybrid的React Native項目,而後咱們將裏面的android
和ios
目錄刪除,替換成已存在Android和iOS項目。
在上文中咱們已經建立了個一個React Native項目,接下來咱們來看一下如何將這個React Native項目和咱們已經存在的Native項目進行融合。
在進行融合以前咱們須要將已經存在的Native項目放到咱們建立的RNHybrid下,好比:我有一個名爲RNHybridAndroid
的Android項目,將其放到RNHybrid目錄下:
RNHybrid
├── RNHybridAndroid
├── package.json
├── node_modules
└── .gitignore
複製代碼
接下來咱們須要爲已經存在的RNHybridAndroid項目添加 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
}
複製代碼
而後,咱們爲RNHybridAndroid項目配置使用的本地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"
}
...
}
...
}
複製代碼
提示:爲確保你配置的目錄正確,能夠經過在Android Studio中運行Gradle sync 看是否有 「Failed to resolve: com.facebook.react:react-native:0.x.x" 的錯誤出現,沒有錯誤則說明配置正確,不然說明配置路由有問題。 此過程所遇到的更多問題可查閱:React Native與Android 混合開發講解的視頻教程
接下來咱們爲APP運行配置所須要的權限:檢查你項目中的AndroidManifest.xml
文件中看是否有以下權限:
<uses-permission android:name="android.permission.INTERNET" />
複製代碼
若是沒有,則須要將上述權限添加到AndroidManifest.xml
中。
另外,若是你須要用到RN的
Dev Settings
功能:
則須要在AndroidManifest.xml
文件中添加以下代碼:
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
複製代碼
提示:上述圖片就是RN 開發調試彈框中的
Dev Settings
功能,打開該功能會彈出上圖的一個界面,這個界面就是DevSettingsActivity。
Android不能同時加載多種架構的so庫,如今不少Android第三方sdks對abi的支持比較全,可能會包含armeabi, armeabi-v7a,x86, arm64-v8a,x86_64五種abi,若是不加限制直接引用會自動編譯出支持5種abi的APK,而Android設備會從這些abi進行中優先選擇某一個,好比:arm64-v8a,但若是其餘sdk不支持這個架構的abi的話就會出現crash。以下圖:
怎麼解決呢:
在app/gradle
文件中添加以下代碼:
defaultConfig {
....
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
複製代碼
上述代碼的意思是,限制打包的so庫只包含armeabi-v7a
與x86
。此過程所遇到的更多問題可查閱:React Native與Android 混合開發講解的視頻教程
可參考:libgnustl_shared.so" is 32-bit instead of 64-bit
經過上述兩步,咱們已經爲RNHybridAndroid項目添加了React Native依賴,接下來咱們來開發一些JS代碼。
在RNHybrid目錄下建立一個index.js
文件並添加以下代碼:
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('App1', () => App);
複製代碼
上述代碼,AppRegistry.registerComponent('App1', () => App);
目的是向React Native註冊一個名爲App1
的組件,而後我會在第四步給你們介紹如何在Android中加載並顯示出這個組件。
另外,在上述代碼中咱們引用了一個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,
}
});
複製代碼
這個App.js
文件表明了咱們React Native的一個頁面,在這個頁面中顯示了this is App
的文本內容。
以上就是爲本次演示所添加的React Native代碼,你也能夠根據須要添加更多的React Native代碼以及組件出來。
通過上述三、4步,咱們已經爲RNHybridAndroid項目添加了React Native依賴,而且建立一些React Native代碼和註冊了一個名爲App1
的組件,接下來咱們來學習下如何在RNHybridAndroid項目中使用這個App1
組件。
首先咱們須要建立一個Activity來做爲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();
// 這個"App1"名字必定要和咱們在index.js中註冊的名字保持一致AppRegistry.registerComponent()
mReactRootView.startReactApplication(mReactInstanceManager, "App1", null);
setContentView(mReactRootView);
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
複製代碼
參數說明
setBundleAssetName
:打包時放在assets
目錄下的JS bundle包的名字,App release以後會從該目錄下加載JS bundle;setJSMainModulePath
:JS bundle中主入口的文件名,也就是咱們上文中建立的那個index.js
文件;addPackage
:向RN添加Native Moudle,在上述代碼中咱們添加了new MainReactPackage()
這個是必須的,另外,若是咱們建立一些其餘的Native Moudle也須要經過addPackage
的方式將其註冊到RN中。須要指出的是RN除了這個方法外,也提供了一個addPackages
方法用於批量向RN添加Native Moudle;setUseDeveloperSupport
:設置RN是否開啓開發者模式(debugging,reload,dev memu),好比咱們經常使用開發者彈框;setInitialLifecycleState
:經過這個方法來設置RN初始化時所處的生命週期狀態,通常設置成LifecycleState.RESUMED
就行,和下文講的Activity容器的生命週期狀態關聯;mReactRootView.startReactApplication
:它的第一個參數是mReactInstanceManager
,第二個參數是咱們在index.js
中註冊的組件的名字,第三個參數接受一個Bundle
來做爲RN初始化時傳遞給JS的初始化數據,它的具體用法我會在**React Android 混合開發講解的視頻教程**中再具體的講解;AndroidManifest.xml
註冊一個RNPageActivityAndroid系統要求,每個要打開的Activity都要在AndroidManifest.xml
中進行註冊:
<activity
android:name=".RNPageActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
複製代碼
上述代碼中咱們爲
RNPageActivity
添加了一個@style/Theme.AppCompat.Light.NoActionBar
類型的theme,這也是React Native UI組件所要求的主題。
一個 ReactInstanceManager能夠被多個activities或fragments共享,因此咱們須要在Activity的生命週期中回調ReactInstanceManager的對於的方法。
@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();
}
}
複製代碼
從上述代碼中你會發現有個不屬於Activity生命週期中的方法onBackPressed
,添加它的目的主要是爲了當用戶單擊手機的返回鍵以後將事件傳遞給JS,若是JS消費了這個事件,Native就再也不消費了,若是JS沒有消費這個事件那麼RN會回調invokeDefaultOnBackPressed
代碼。
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
複製代碼
此過程更細緻的講解可查閱:React Native與Android 混合開發講解的視頻教程
在RN中有個很好用的工具開發者菜單,咱們平時調試RN應用時對它的使用頻率很高,接下來咱們來爲RNHybridAndroid添加開着菜單。
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getUseDeveloperSupport()) {
if (keyCode == KeyEvent.KEYCODE_MENU) {//Ctrl + M 打開RN開發者菜單
mReactInstanceManager.showDevOptionsDialog();
return true;
}
}
return super.onKeyUp(keyCode, event);
}
複製代碼
經過上代碼便可監聽Ctrl + M
來打開RN開發者菜單。
另外,RN也提供了雙擊R來快速加載JS的功能,經過以下代碼便可打開該功能:
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getUseDeveloperSupport()) {
if (keyCode == KeyEvent.KEYCODE_MENU) {//Ctrl + M 打開RN開發者菜單
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);
}
複製代碼
此過程更細緻的講解可查閱:React Native與Android 混合開發講解的視頻教程
在上述的代碼中咱們都是經過ReactInstanceManager
來建立和加載JS的,而後重寫了Activity的生命週期來對ReactInstanceManager
進行回調,另外,重寫了onKeyUp
來啓用開發者菜單等功能。
另外,查看RN的源碼你會發如今RN sdk中有個叫ReactActivity
的Activity,該Activity是RN官方封裝的一個RN容器。另外,在經過react-native init
命令初始化的一個項目中你會發現有個MainActivity
是繼承ReactActivity
的,接下來咱們就來繼承ReactActivity
來封裝一個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";
}
}
複製代碼
另外,咱們須要實現一個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
,查看源碼你會發如今ReactActivity
中使用了ReactActivityDelegate
,在ReactActivityDelegate
中會用到MainApplication
中提供的ReactNativeHost
:
protected ReactNativeHost getReactNativeHost() {
return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost();
}
複製代碼
另外實現了
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">
...
複製代碼
以上就是經過繼承ReactActivity
的方式來做爲RN容器的。
那麼這兩種方式各有什麼特色:
ReactInstanceManager
的方式:靈活,可定製性強;ReactActivity
的方式:簡單,可定製性差;此過程更細緻的講解可查閱:React Native與Android 混合開發講解的視頻教程
通過上述的步驟,咱們已經完成了對一個現有Android項目RNHybridAndroid添加了RN,而且經過兩種方式分別建立了一個RNPageActivity
與ReactPageActivity
的Activity來加載咱們在JS中註冊的名爲App1
的RN 組件。
接下來咱們來啓動RN服務器,運行RNHybridAndroid項目打開RNPageActivity
或ReactPageActivity
來查看效果:
npm start
複製代碼
在RNHybrid
的根目錄運行上述命令,來啓動一個RN本地服務:
而後咱們打開AndroidStudio,點擊運行按鈕或者經過快捷鍵Ctrl+R
來將RNHybridAndroid
安裝到模擬器上:
咱們能夠根據須要添加更多的React Native的組件:
import { AppRegistry } from 'react-native';
import App from './App';
import App2 from './App2';
AppRegistry.registerComponent('App1', () => App);
AppRegistry.registerComponent('App2', () => App);
複製代碼
而後,在Native中根據須要加載指定名字的RN組件便可。
調試這種混合的RN應用和調試一個純RN應用時同樣的,都是經過上文中說講到的RN 開發者菜單
,另外搭建也能夠經過學習React Native技術精講與高質量上線APP開發課程來掌握更多RN調試的技巧。
雖讓,經過上述步驟,咱們將RN和咱們的RNHybridAndroid項目作了融合,但打包RNHybridAndroid你會發現裏面並不包含JS部分的代碼,若是要將JS代碼打包進Android Apk包中,能夠經過以下命令:
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/
複製代碼
參數說明
--platform android
:表明打包導出的平臺爲Android;--dev false
:表明關閉JS的開發者模式;-entry-file index.js
:表明js的入口文件爲index.js
;--bundle-output
:後面跟的是打包後將JS bundle包導出到的位置;--assets-dest
:後面跟的是打包後的一些資源文件導出到的位置;提示:JS bundle必定要正確放到你的Android言語的assets目錄下這個和咱們上文中配置的
setBundleAssetName("index.android.bundle")
進行對應。
經過上述步驟咱們完成了將RN代碼打包並生成JS bundle,並放到了assets目錄下,接下來咱們就能夠來經過Android Studio或者命令的方式來release咱們的RN混合Android應用了。
我在以前發表過React Native發佈APP之簽名打包APK的博文, 須要的同窗能夠去看一下,在這篇文章中就不在重複了。
更多React Native混合開發的實用技巧,可學習與此文章配套的視頻課程:《React Native與Android 混合開發講解》