React Native Android混合開發實戰教程

CrazyCodeBoy發表於2018-08-28

在React Native的應用場景中,有時候一個APP只有部分頁面是由React Native實現的,比如:我們常用的攜程App,它的首頁下的很多模組都是由React Native實現的,這種開發模式被稱為混合開發。

混合開發的一些其他應用場景:

在原有專案中加入RN頁面,在RN專案中加入原生頁面

RNHybrid

原生頁面中嵌入RN模組

Native-RN-page

RN頁面中嵌入原生模組

RN-Native-page

以上這些都屬於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-install--save-react-native.png

其中,有一條警告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_modulesnode_modules體積很大且是動態生成了,建議將其新增到.gitignore檔案中;

通過react-native init來初始化一個React Native專案

除了上述方式之外,我們也可以通過react-native init命令來初始化一個React Native專案。

react-native init RNHybrid
複製程式碼

上述命令會初始化一個完成的名為RNHybrid的React Native專案,然後我們將裡面的androidios目錄刪除,替換成已存在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
}
複製程式碼

app-build.gradle

然後,我們為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"
        }
        ...
    }
    ...
}
複製程式碼

maven-directory-to-build.gradle

提示:為確保你配置的目錄正確,可以通過在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功能:

DevSettingsActivity

則需要在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。如下圖:

libgnustl_shared.so

怎麼解決呢:

app/gradle 檔案中新增如下程式碼:

defaultConfig {
....
    ndk {
        abiFilters "armeabi-v7a", "x86"
    }
}
複製程式碼

上述程式碼的意思是,限制打包的so庫只包含armeabi-v7ax86此過程所遇到的更多問題可查閱: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開發者選單。

ctrl+m-android

另外,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,並且通過兩種方式分別建立了一個RNPageActivityReactPageActivity的Activity來載入我們在JS中註冊的名為App1的RN 元件。

接下來我們來啟動RN伺服器,執行RNHybridAndroid專案開啟RNPageActivityReactPageActivity來檢視效果:

npm start
複製程式碼

RNHybrid的根目錄執行上述命令,來啟動一個RN本地服務:

npm-start

然後我們開啟AndroidStudio,點選執行按鈕或者通過快捷鍵Ctrl+R來將RNHybridAndroid安裝到模擬器上:

this-is-app-android

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 混合開發講解》

參考

相關文章