期待已久的新課上線啦!解鎖React Native開發新姿勢,一網打盡React Native最新與最熱技術,點我Get!!!
在React Native的應用場景中,有時候一個APP只有部分頁面是由React Native實現的,比如:我們常用的攜程App,它的首頁下的很多模塊都是由React Native實現的,這種開發模式被稱爲混合開發。
混合開發的一些其他應用場景:
在原有項目中加入RN頁面,在RN項目中加入原生頁面
原生頁面中嵌入RN模塊
RN頁面中嵌入原生模塊
以上這些都屬於React Native混合開發的範疇,那麼如何進行React Native混合開發呢?
在這篇文章中我將向大家介紹React Native混合開發的流程,需要掌握的技術,以及一些經驗技巧,與該文章配套的還有React Native與Android 混合開發講解的視頻教程。
React Native混合開發的教程我們分爲上下兩篇,上篇主要介紹**如何在現有的Android應用上進行React Native混合開發,下篇主要介紹如何在現有的iOS應用上進行React Native混合開發**。
將React Native集成到現有的Android應用中需要如下幾個主要步驟:
- 首先,你需要有一個React Native項目;
- 爲已存在的Android應用添加React Native所需要的依賴;
- 創建index.js並添加你的React Native代碼;
- 創建一個Activity來承載React Native,在這個Activity中創建一個ReactRootView來作爲React Native服務的容器;
- 啓動React Native的Packager服務,運行應用;
- (可選)根據需要添加更多React Native的組件;
- 運行、調試、打包、發佈應用;
- 升職加薪、迎娶白富美,走向人生巔峯!;
1. 創建一個React Native項目
在做混合開發之前我們首先需要創建一個沒有Android和iOS模塊的React Native項目。我們可以通過兩種方式來創建一個這樣的React Native項目:
- 通過
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 [email protected] requires a peer of [email protected] but none is installed
告訴我們需要安裝[email protected]
:
npm install --save [email protected]
至此,一個不含Android和iOS模塊的React Native項目便創建好了。此過程所遇到的更多問題可查閱:React Native與Android 混合開發講解的視頻教程
提示:npm 會在你的目錄下創建一個
node_modules
,node_modules
體積很大且是動態生成了,建議將其添加到.gitignore
文件中;
通過react-native init來初始化一個React Native項目
除了上述方式之外,我們也可以通過react-native init
命令來初始化一個React Native項目。
react-native init RNHybrid
上述命令會初始化一個完成的名爲RNHybrid的React Native項目,然後我們將裏面的android
和ios
目錄刪除,替換成已存在Android和iOS項目。
2. 添加React Native所需要的依賴
在上文中我們已經創建了個一個React Native項目,接下來我們來看一下如何將這個React Native項目和我們已經存在的Native項目進行融合。
在進行融合之前我們需要將已經存在的Native項目放到我們創建的RNHybrid下,比如:我有一個名爲RNHybridAndroid
的Android項目,將其放到RNHybrid目錄下:
RNHybrid
├── RNHybridAndroid
├── package.json
├── node_modules
└── .gitignore
第一步:配置maven
接下來我們需要爲已經存在的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。
第三步:指定要ndk需要兼容的架構(重要)
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
3.創建index.js並添加你的React Native代碼
通過上述兩步,我們已經爲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. 爲React Native創建一個Activity來作爲容器
經過上述3、4步,我們已經爲RNHybridAndroid項目添加了React Native依賴,並且創建一些React Native代碼和註冊了一個名爲App1
的組件,接下來我們來學習下如何在RNHybridAndroid項目中使用這個App1
組件。
創建RNPageActivity
首先我們需要創建一個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
註冊一個RNPageActivity
Android系統要求,每一個要打開的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添加Activity的生命週期回調
一個 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 混合開發講解的視頻教程
使用ReactActivity來作爲RN容器
在上述的代碼中我們都是通過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 混合開發講解的視頻教程
5. 運行React Native
經過上述的步驟,我們已經完成了對一個現有Android項目RNHybridAndroid添加了RN,並且通過兩種方式分別創建了一個RNPageActivity
與ReactPageActivity
的Activity來加載我們在JS中註冊的名爲App1
的RN 組件。
接下來我們來啓動RN服務器,運行RNHybridAndroid項目打開RNPageActivity
或ReactPageActivity
來查看效果:
npm start
在RNHybrid
的根目錄運行上述命令,來啓動一個RN本地服務:
然後我們打開AndroidStudio,點擊運行按鈕或者通過快捷鍵Ctrl+R
來將RNHybridAndroid
安裝到模擬器上:
6. 添加更多React Native的組件
我們可以根據需要添加更多的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組件即可。
7. 調試、打包、發佈應用
調試
調試這種混合的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 混合開發講解》