React Native嵌入Android原生項目中

React Native嵌入Android原生項目中

開發環境準備

首先你要搭建好React Native for Android開發環境, 沒有搭建好的可以參考:React Native for Android Windows環境搭建

用Android Studio新建Android原生項目

我創建了一個名叫ReactNativeDemo的原生項目。

把React Native集成到原生項目當中

利用Windows命令行在項目根目錄(ReactNativeDemo文件夾)下執行下面三行命令:

npm init
npm install --save react react-native
curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig

這將在項目根目錄(ReactNativeDemo文件夾)創建node_modules文件夾(模塊),并添加React Native依賴。

針對上面三條命令的解釋

npm init



注意:
name的填寫由圖可知填默認的是不行的,它的要求是不能有大寫字母并且不能以數字開頭;
entry point的填寫入口文件名稱,默認的是index.js,我們建立的入口文件是index.android.js,所以填寫index.android.js。只要填寫的名稱與自己定義的入口文件名稱一致就行。
其他的項根據自己需求填寫即可。

這個步驟會在項目的根目錄產生一個名稱為package.json的文件,我們還需要修改我們的package.json文件:
在"scripts"節點下添加"start": "node node_modules/react-native/local-cli/cli.js start"。

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node node_modules/react-native/local-cli/cli.js start"
  }

其中的test節點是自動生成的,我們可以把它刪除,最后我的package.json為:

{
  "name": "reactnativedemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.android.js",
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^15.4.1",
    "react-native": "^0.38.0"
  }
}

npm install --save react react-native



curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig

curl是利用URL語法在命令行方式下工作的開源文件傳輸工具。它被廣泛應用在Unix、多種Linux發行版中,并且有DOS和Win32、Win64下的移植版本。
所以可知上面這句話的意思是在對應網址下下載.flowconfig文件。
在windows下我們要使用curl命令會提示:curl不是內部和外部命令,也不是可執行文件或批處理命令。。。
我們在windows下要使用curl命令比較麻煩。解決方法就是我們用下載工具從https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig 上把.flowconfig下載下來復制到項目根目錄,或者是在項目根目錄下新建一個.flowconfig文件用瀏覽器訪問這個網址其中的內容把其中的內容復制到文件當中。

建立index.android.js文件

在項目的根目錄建立index.android.js文件并把下面的代碼復制進去:

'use strict';

import React from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';

class Root extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.hello}>Welcome!</Text>
      </View>
    )
  }
}
var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
  hello: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
});

AppRegistry.registerComponent('ReactNativeView', () => Root);

添加依賴

在項目的根目錄的build.gradle中加入:

allprojects {
    repositories {
        jcenter()
        maven {
            //添加react native依賴,url路徑根據實際的來寫,本文的如下:
            url "$rootDir/node_modules/react-native/android"
        }
    }
}

注意:Android項目默認的依賴包的源為jcenter(),其中并不包含最新版的 React Native(它只到0.20.1)。
新版的React Native只在npm里發布,所以你需要增加一下依賴包的源。在編譯完后,檢查項目External Libraries的
react-native版本如果為0.20.1,則說明maven的依賴源沒有添加成功。這時候應該是maven的路徑出問題了,你要檢查
路徑是否正確,正確的結果為:

在項目的模塊(app)中的build.gradle文件中添加:
文件頭添加(可選):

apply from: "$rootDir/node_modules/react-native/react.gradle"

無法編譯通過的時候可以嘗試添加上面這句。

 dependencies {
     ...
     compile "com.facebook.react:react-native:+"
 }

如果你想總是使用一個特定的版本,你需要把+替換成你已經下載的React Native的版本號,
這個版本號應該與package.json中的react-native的版本號("react-native": "^0.38.0")一致的。如本例中的0.38.0:

 dependencies {
     ...
     compile "com.facebook.react:react-native:0.38.0"
 }

添加原生Activity文件:

官方教程的寫法:

MyReactActivity

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import com.facebook.react.BuildConfig;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.shell.MainReactPackage;

public class MyReactActivity extends Activity 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")
                .setJSMainModuleName("index.android") //對應index.android.js
                .addPackage(new MainReactPackage())
                //.setUseDeveloperSupport(BuildConfig.DEBUG) //開發者支持,BuildConfig.DEBUG的值默認是false,無法使用開發者菜單
                .setUseDeveloperSupport(true) //開發者支持,開發的時候要設置為true,不然無法使用開發者菜單
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        //這里的ReactNativeView對應index.android.js中AppRegistry.registerComponent('ReactNativeView', () => ReactNativeView)的ReactNativeView
        mReactRootView.startReactApplication(mReactInstanceManager, "ReactNativeView", null);
        setContentView(mReactRootView);
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }

    @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
    protected void onDestroy() {
        super.onDestroy();

        if (mReactInstanceManager != null) {
            mReactInstanceManager.onHostDestroy(this);
        }
    }

    @Override
    public void onBackPressed() {
        if (mReactInstanceManager != null) {
            mReactInstanceManager.onBackPressed();
        } else {
            super.onBackPressed();
        }
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        //當我們點擊菜單的時候打開發者菜單,一個彈窗(此處需要懸浮窗權限才能顯示)
        if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
            mReactInstanceManager.showDevOptionsDialog();
            return true;
        }
        return super.onKeyUp(keyCode, event);
    }
}

<font color="#EF7A7A">注意:</font>

官方教程的寫法中這里:

 @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mReactRootView = new ReactRootView(this);
    mReactInstanceManager = ReactInstanceManager.builder()
            .setApplication(getApplication())
            .setBundleAssetName("index.android.bundle")
            .setJSMainModuleName("index.android") //對應index.android.js
            .addPackage(new MainReactPackage())
            //.setUseDeveloperSupport(BuildConfig.DEBUG) //開發者支持,BuildConfig.DEBUG的值默認是false,無法使用開發者菜單
            .setUseDeveloperSupport(true) //開發者支持,開發的時候要設置為true,不然無法使用開發者菜單
            .setInitialLifecycleState(LifecycleState.RESUMED)
            .build();
    //這里的ReactNativeView對應index.android.js中AppRegistry.registerComponent('ReactNativeView', () => ReactNativeView)的ReactNativeView
    mReactRootView.startReactApplication(mReactInstanceManager, "ReactNativeView", null);
    setContentView(mReactRootView);
}

的setUseDeveloperSupport(BuildConfig.DEBUG)方法,設置開發者支持,BuildConfig.DEBUG的值默認是false,無法使用開發者支持(開發者菜單、即時預覽等),所以我們要把BuildConfig.DEBUG改為true。

另一種原生Activity寫法

MyReactNativeActivity

import com.facebook.react.ReactActivity;

public class MyReactNativeActivity extends ReactActivity {
    /**
     * 這里的ReactNativeView對應index.android.js中AppRegistry.registerComponent('ReactNativeView', () => Root)的ReactNativeView
     */
    @Override
    protected String getMainComponentName() {
        return "ReactNativeView";
    }
}

兩種原生Activity寫法的對比:

第一種(官方例子的)

這種寫法的優勢是可以利用React Native來寫我們界面中的某一塊區域,就是利用原生布局的addView()方法把mReactRootView加入到布局中,比如:

activity_my_react.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="#9DB16D"
        android:textSize="40sp"
        android:text="原生控件TextView" />

    <LinearLayout
        android:id="@+id/layout"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

MyReactActivity修改

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my_react);
    mReactLayout = (LinearLayout) findViewById(R.id.layout);

    mReactRootView = new ReactRootView(this);
    mReactInstanceManager = ReactInstanceManager.builder()
            .setApplication(getApplication())
            .setBundleAssetName("index.android.bundle")
            .setJSMainModuleName("index.android")//對應index.android.js
            .addPackage(new MainReactPackage())
            //.setUseDeveloperSupport(BuildConfig.DEBUG) //開發者支持,BuildConfig.DEBUG的值默認是false,無法使用開發者菜單
            .setUseDeveloperSupport(true) //開發者支持,開發的時候要設置為true,不然無法使用開發者菜單
            .setInitialLifecycleState(LifecycleState.RESUMED)
            .build();
    //這里的ReactNativeView對應index.android.js中AppRegistry.registerComponent('ReactNativeView', () => Root)的ReactNativeView
    mReactRootView.startReactApplication(mReactInstanceManager, "ReactNativeView", null);
    mReactLayout.addView(mReactRootView);
}

index.android.js

import React, { Component } from 'react';
import {
  View,
  Text,
  StyleSheet
} from 'react-native'

export default class Root extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>React Native組件</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#65A35F',
  },
  welcome: {
    fontSize: 40,
    textAlign: 'center',
    margin: 10,
  }
});

AppRegistry.registerComponent('ReactNativeView', () => Root);

第二種

這種方式是優勢是寫法簡單。但是無法局部使用React Native來布局。

AndroidManifest.xml相關

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="top.cokernut.reactnativetonative">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

    <application
        android:allowBackup="true"
        android:name=".MyApplication"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MyReactActivity"
            android:label="MyReactActivity">
        </activity>
        <activity
            android:name=".MyReactNativeActivity"
            android:label="MyReactNativeActivity"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
        </activity>
        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
    </application>

</manifest>

網絡權限:

<uses-permission android:name="android.permission.INTERNET" />

懸浮窗權限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

有懸浮窗權限才能顯示:

如果遇到React Native的一些組件不能使用可以嘗試在注冊Activity時添加主題為Theme.AppCompat.Light.NoActionBar,看能否解決問題,因為一些組件依賴于這個主題:

<activity
    android:name=".MyReactNativeActivity"
    android:label="@string/app_name"
    android:theme="@style/Theme.AppCompat.Light.NoActionBar">
</activity>

開發設置界面:

<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

運行應用

啟動開發服務器

在項目的根目錄下運行:

npm start

這個命令運行的是我們package.json中配置的:

"scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start"
  },

可以用瀏覽器訪問http://localhost:8081/index.android.bundle?platform=android 看看是否可以看到打包后的腳本。
第一次訪問通常需要十幾秒,并且在命令行可以看到進度條。

構建與運行你的程序

兩種方法:

  1. 直接利用Android Studio像平常一樣運行項目
  2. 在命令行中項目目錄下運行gradlew installDebug

如果你使用的是Android Studio為你構建而不是Gradle構建(gradlew installDebug),你要確保你在安裝應用之前運行了npm start,以防止它們之間出現沖突。
效果:

MyReactActivity:

MyReactNativeActivity:

在Android Studio中打包成獨立安裝程序(release)

你可以使用Android Studio來創建你的App的發布版本!像以前創建原生應用程序的發布版本一樣簡單,只是有一個額外步驟:
在你打包你的發布版本之前要創建一個bundle文件,這個bundle文件會創建在項目的assets目錄中,并且這個文件會包含在你的apk包中,
在你的項目根目錄中運行:

react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output app/src/main/assets/index.android.bundle --assets-dest app/src/main/res/

app/src/main根據實際情況改為自己項目中的目錄,參考assets文件夾的目錄。


結果為:

如果報錯:

ENOENT: no such file or directory

你需要在你的app模塊中建立assets文件夾和index.android.bundle。

現在你可以對你的應用程序進行打包發布了。

debug模式release模式React Native JS代碼調試的區別:

debug模式: 修改完js代碼打開開發者菜單點擊Reload就可以看到更新后的效果,或者是開啟Live Reload(點擊Enable Live Reload)
這樣我們修改了js文件只要保存就會自動Reload。
release模式: 修改完js代碼需要重新生成index.android.bundle 文件,點擊run之后才能看到效果。因為正式版發布后是無法
依賴本地服務器去更新index.android.bundle,需要把index.android.bundle打包到apk中才能運行。

更新React Naive版本

1.打開項目目錄下的package.json文件,然后在dependencies模塊下找到react-native,將當前版本號改到最新(或指定)版本號,如:

{
"name": "reactnativedemo",
"version": "1.0.0",
"description": "",
"main": "index.android.js",
"scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start"
},
"author": "",
"license": "ISC",
"dependencies": {
    "react": "^15.4.1",
    "react-native": "^0.38.0"
}
}

react-native的npm包的最新版本可以去這里查看,或使用npm info react-native命令查看。

2.項目的根目錄執行:

npm install

安裝最新的React Native版本,成功后可能會出現如下類似警告:

npm WARN react-native@0.38.0 requires a peer of react@15.4.1 but none was installed.

3.根據警告執行:

npm install –save react@15.4.1

更新最新的React且項目下package.json 的 dependencies下的react版本會被修改為 15.4.1

4.新版本的npm包通常還會包含一些動態生成的文件,這些文件是在運行react-native init創建新
項目時生成的,比如iOS和Android的項目文件。為了使老項目的項目文件也能得到更新
(不重新init),你需要在命令行中運行:

react-native upgrade

這一命令會檢查最新的項目模板,然后進行如下操作:

  • 如果是新添加的文件,則直接創建。
  • 如果文件和當前版本的文件相同,則跳過。
  • 如果文件和當前版本的文件不同,則會提示你一些選項:查看兩者的不同,選擇保留你的版本或是用新的模板覆蓋。你可以按下h鍵來查看所有可以使用的命令。

注意:如果你有修改原生代碼,那么在使用upgrade升級前,先備份,再覆蓋。覆蓋完成后,使用比對工具找出差異,將你之前修改的代碼逐步搬運到新文件中。

5.執行:

react-native -v

通過如上命令來看最新的版本,檢測是否升級成功!

問題與解決方案

打不開開發者菜單

查看AndroidManifest.xml文件中是否加入了懸浮窗權限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

并且你要在手機上開啟你要調試的應用的懸浮窗權限,不同的手機開啟方式不同,可以自行搜索開啟方法。

錯誤 "/data/data/package-name/lib-main/libgnustl_shared.so" is 32-bit instead of 64-bit

取消掉所有的64位的.so文件,全部加載32位的.so文件。

  1. 在項目的根目錄的build.gradle中加入:

android.useDeprecatedNdk=true.

  1. 在項目的模塊(app)中的build.gradle文件中添加:
android {
    ...
    defaultConfig {
        ...
        ndk {
            abiFilters "armeabi-v7a", "x86"
        }

        packagingOptions {
            exclude "lib/arm64-v8a/librealm-jni.so"
        }
    }
}

錯誤:Could not get BatchedBridge,make sure your bundle is packaged correctly

  1. build模式選擇了release模式,引發的這個錯誤,你可以檢查一下是否是debug模式,如果不是改為debug再試一下。
  2. ReactInstanceManager的setUseDeveloperSupport(BuildConfig.DEBUG)方法值是否正確,設置開發者支持,BuildConfig.DEBUG的值默認是false,無法使用開發者支持(開發者菜單、即時預覽等),所以我們要把BuildConfig.DEBUG改為true。

錯誤

解決方法:

把Android Studio自動生成的文件夾androidTest和test刪除,并修改項目的模塊(app)的build.gradle文件:

修改前:

修改后:

其他問題可以參考React Native for Android Windows環境搭建

<font size=5>源代碼</font>

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容