Android/iOS已有原生項(xiàng)目集成ReactNative

  1. 背景
    近期由于項(xiàng)目需要链蕊,在已經(jīng)開(kāi)發(fā)了幾個(gè)版本的原生App中集成了ReactNative请祖,新版的模塊都使用RN開(kāi)發(fā)您机。
    此次集成的工作大部分是參照RN中文文檔進(jìn)行的污尉,但是中文文檔也有一些坑和描述不充分的地方,所以我參照著文檔把自己集成的過(guò)程以及遇到的問(wèn)題整理了下往产,希望能給遇到相同問(wèn)題的人起到參考作用。
    ReactNative中文文檔官方地址:http://reactnative.cn/docs/0.45/integration-with-existing-apps.html#content
  2. 集成過(guò)程
    2.1 Android
    2.1.1 在應(yīng)用中添加js代碼
    打開(kāi)控制臺(tái)某宪,進(jìn)入項(xiàng)目根目錄仿村,執(zhí)行以下幾個(gè)命令:
$npm init
$npm install --save react react-native
$curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig

npm init命令是創(chuàng)建package.json文件,之后會(huì)要求輸入信息兴喂,除了name之外其他的直接按enter默認(rèn)就行了蔼囊。
npm install則創(chuàng)建了node_modules目錄并把react和react-native下載到了其中。執(zhí)行此命令時(shí)衣迷,要是控制臺(tái)輸出了版本不一致的警告信息畏鼓,例如:

npm WARN react-native@0.45.1 requires a peer of react@16.0.0-alpha.12 but none was installed.

則繼續(xù)執(zhí)行 npm i -S react@16.0.0-alpha.12 (這里版本跟警告信息一致)。
至于第三步curl命令壶谒,其實(shí)質(zhì)是下載.flowconfig配置文件云矫,這個(gè)文件用于約束js代碼的寫(xiě)法。非必需汗菜,可跳過(guò)让禀。
執(zhí)行完成之后我的package.json是這樣的:

{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"react": "^16.0.0-alpha.12",
"react-native": "^0.45.1"
}
}

接下來(lái)還需要把啟動(dòng)腳本放進(jìn)去:
"start": "node node_modules/react-native/local-cli/cli.js start"
注意下啟動(dòng)腳本的路徑挑社,如果集成的項(xiàng)目目錄結(jié)構(gòu)不一樣的話會(huì)報(bào)找不到啟動(dòng)腳本之類(lèi)的錯(cuò)誤,到時(shí)根據(jù)具體目錄結(jié)構(gòu)修改就好了巡揍。

2.1.2 在項(xiàng)目根目錄下創(chuàng)建index.android.js文件:

'use strict';
import React from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';
class test extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.hello}>Hello, My Hybrid App!</Text>
      </View>
    )
  }
}
var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
  hello: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
});
AppRegistry.registerComponent('test', () => test);

2.1.3 打開(kāi)你的android項(xiàng)目痛阻,在app中 build.gradle 文件中添加 React Native 依賴(lài):

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

接著,在 AndroidManifest.xml 清單文件中聲明網(wǎng)絡(luò)權(quán)限:

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

如果需要訪問(wèn) DevSettingsActivity 界面腮敌,也需要在 AndroidManifest.xml 中聲明:

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

在項(xiàng)目的 build.gradle 文件中為 React Native 添加一個(gè) maven 依賴(lài)的入口阱当,必須寫(xiě)在 "allprojects" 代碼塊中:

allprojects {
    repositories {
        ...
        maven {
            // All of React Native (JS, Android binaries) is installed from npm
            url "../node_modules/react-native/android"
        }
    }
    ...
}

這里maven路徑官網(wǎng)上寫(xiě)的是$rootDir/../node_modules/react-native/android,這個(gè)跟目錄結(jié)構(gòu)有關(guān)糜工,跟我一樣在根目錄上操作的話是不需要$rootDir的

2.1.4 同步gradle
遇到以下錯(cuò)誤:

Warning:Conflict with dependency 'com.google.code.findbugs:jsr305'. Resolved versions for app (3.0.0) and test app (2.0.1) differ. See http://g.co/androidstudio/app-test-app-conflict for details.

在build.bundle里面加入:

allprojects {
    ...
    configurations.all {
        resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.0'
    }
}

2.1.5 添加原生代碼
2.1.5.1 新建一個(gè)Activity弊添,代碼如下:

public class MyReactActivity 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")
                .setJSMainModuleName("index.android")
                .addPackage(new MainReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();

        // 注意這里的HelloWorld必須對(duì)應(yīng)“index.android.js”中的
        // “AppRegistry.registerComponent()”的第一個(gè)參數(shù)
        mReactRootView.startReactApplication(mReactInstanceManager, "test", 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();
        }
    }
    @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);
    }
}

2.1.5.2 在AndroidManifest.xml中聲明:

<activity
            android:name=".MyReactActivity"
            android:label="@string/app_name"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

應(yīng)用在真機(jī)上調(diào)試時(shí)需開(kāi)啟懸浮窗權(quán)限才能正確顯示紅屏錯(cuò)誤,官網(wǎng)上有前往開(kāi)啟的代碼啤斗,但是我覺(jué)得有點(diǎn)麻煩了表箭,直接進(jìn)應(yīng)用管理中心開(kāi)啟就好了,真正發(fā)布的時(shí)候也不需要顯示紅屏錯(cuò)誤钮莲。

2.1.6 運(yùn)行程序
2.1.6.1 在根目錄下使用npm start命令開(kāi)啟后臺(tái)服務(wù)免钻,然后運(yùn)行應(yīng)用,不出意外的話應(yīng)該可以看到一片紅色崔拥。
這是后臺(tái)啟動(dòng)的窗口:

111.png

App首次啟動(dòng)頁(yè)面:

S70618-170654.jpg

紅屏沒(méi)有出現(xiàn)的話注意檢查下懸浮窗權(quán)限是否開(kāi)啟极舔。
搖晃下手機(jī),會(huì)出現(xiàn)以下界面:

S70618-170659.jpg

點(diǎn)擊Dev Setting進(jìn)入設(shè)置頁(yè)面链瓦,再點(diǎn)擊 Debug server host &...設(shè)置服務(wù)器的IP跟端口拆魏,這個(gè)時(shí)候要注意下手機(jī)跟電腦要在同一個(gè)局域網(wǎng)下:

![S70618-171637.jpg](http://upload-images.jianshu.io/upload_images/6495829-8cc83a0eb2f6fd0c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

設(shè)置之后返回到頁(yè)面,再次搖晃下手機(jī)慈俯,就出現(xiàn)成功的頁(yè)面啦渤刃!

S70618-171646.jpg
S70618-172035.jpg

2.2 iOS
2.2.1 新建package.json文件
可以新建也可以直接前面android項(xiàng)目的package.json拷貝到根目錄下。
注意這里iOS使用的最新版本的react native在安裝pod之后會(huì)有點(diǎn)問(wèn)題贴膘,可能是官網(wǎng)上的中文文檔沒(méi)有及時(shí)更新的原因卖子,我這里暫時(shí)用的還是之前我集成的版本:

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node ../ExampleIOS/node_modules/react-native/local-cli/cli.js start"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^16.0.0-alpha.6",
    "react-native": "^0.43.4"
  }
}

官網(wǎng)在一開(kāi)始建議把iOS代碼都放到一個(gè) ios/文件夾下,但這對(duì)于一個(gè)已經(jīng)開(kāi)發(fā)多次的項(xiàng)目來(lái)說(shuō)操作并不簡(jiǎn)單刑峡,所以這一步也可以不做洋闽,只需把package.json里面啟動(dòng)腳本的路徑修改一下即可。
修改之后的路徑為:

"start": "node ../ExampleIOS/node_modules/react-native/local-cli/cli.js start"

2.2.2 安裝依賴(lài)包
在根目錄下執(zhí)行命令:

npm install

安裝過(guò)程中可能會(huì)遇到跟2.1.1一樣的警告信息突梦,這時(shí)操作也一樣诫舅。
2.2.3 安裝pod
2.2.3.1 如果你的項(xiàng)目還沒(méi)安裝pod則先執(zhí)行pod init,然后在創(chuàng)建了的Podfile文件下加入:

# 'node_modules'目錄一般位于根目錄中
    # 但是如果你的結(jié)構(gòu)不同宫患,那你就要根據(jù)實(shí)際路徑修改下面的`:path`
    pod 'React', :path => '../ExampleIOS/node_modules/react-native', :subspecs => [
      'Core',
      'DevSupport', # 如果RN版本 >= 0.43刊懈,則需要加入此行才能開(kāi)啟開(kāi)發(fā)者菜單
      'RCTText',
      'RCTNetwork',
      'RCTWebSocket', # 這個(gè)模塊是用于調(diào)試功能的
      # 在這里繼續(xù)添加你所需要的模塊
    ]
    # 如果你的RN版本 >= 0.42.0,請(qǐng)加入下面這行
    pod "Yoga", :path => "../ExampleIOS/node_modules/react-native/ReactCommon/yoga"

然后執(zhí)行pod install 命令開(kāi)始安裝∏味铮看到以下圖片就說(shuō)明安裝成功了当宴。

333.png

2.2.3.2 如果你已經(jīng)安裝了pod,則直接在Podfile中加入上面的內(nèi)容泽疆,然后使用pod update --verbose --no-repo-update命令更新pod就可以了户矢。
在這里有個(gè)需要注意的地方,安裝pod之后就要關(guān)閉項(xiàng)目殉疼,使用.xcworkspace文件打開(kāi)項(xiàng)目了梯浪。

2.2.4 代碼集成
2.2.4.1 創(chuàng)建index.ios.js文件
在項(xiàng)目根目錄下創(chuàng)建index.ios.js文件,內(nèi)容跟之前的index.android.js一樣即可瓢娜。

'use strict';

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

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

AppRegistry.registerComponent('test', () => test);

2.2.4.2 在需要挑戰(zhàn)的ViewContrller中引入RCTRootView.h挂洛,跳轉(zhuǎn)代碼:

NSURL *jsCodeLocation = [NSURL
                             URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"];
    RCTRootView *rootView =
    [[RCTRootView alloc] initWithBundleURL : jsCodeLocation
                         moduleName        : @"test"
                         initialProperties :@{}
                          launchOptions    : nil];
    UIViewController *vc = [[UIViewController alloc] init];
    vc.view = rootView;
    [self presentViewController:vc animated:YES completion:nil];

2.2.5 使用npm start開(kāi)啟后臺(tái),啟動(dòng)程序:

3335.png

3336.png

由于蘋(píng)果的安全訪問(wèn)限制可能會(huì)出現(xiàn)訪問(wèn)不到的原因眠砾,這時(shí)候需要往info.plist加入下面這段代碼:

<key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>

然后重新運(yùn)行程序就可以看到以下結(jié)果啦B簿ⅰ!

3337.png
33338.png
  1. 大功告成褒颈!接下來(lái)我把項(xiàng)目上傳到github柒巫,有需要的可以下下來(lái)參考下:
    Android:
    https://git.oschina.net/GStick/androidiosfeatreactnative.git
    iOS:
    https://git.oschina.net/GStick/androidiosfeatreactnative.git
  2. 之后會(huì)繼續(xù)更新集成之后開(kāi)發(fā)中用到的東西,像加載離線bundle文件谷丸、交互之類(lèi)的堡掏,有時(shí)間的話還是Android/iOS雙管齊下,敬請(qǐng)期待刨疼。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泉唁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子揩慕,更是在濱河造成了極大的恐慌亭畜,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迎卤,死亡現(xiàn)場(chǎng)離奇詭異拴鸵,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)止吐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)侨糟,“玉大人碍扔,你說(shuō)我怎么就攤上這事★踔兀” “怎么了不同?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我二拐,道長(zhǎng)服鹅,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任百新,我火速辦了婚禮企软,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘饭望。我一直安慰自己仗哨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布铅辞。 她就那樣靜靜地躺著厌漂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪斟珊。 梳的紋絲不亂的頭發(fā)上苇倡,一...
    開(kāi)封第一講書(shū)人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音囤踩,去河邊找鬼旨椒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛高职,可吹牛的內(nèi)容都是我干的钩乍。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼怔锌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼寥粹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起埃元,我...
    開(kāi)封第一講書(shū)人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涝涤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后岛杀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體阔拳,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年类嗤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了糊肠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡遗锣,死狀恐怖货裹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情精偿,我是刑警寧澤弧圆,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布赋兵,位于F島的核電站,受9級(jí)特大地震影響搔预,放射性物質(zhì)發(fā)生泄漏霹期。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一拯田、第九天 我趴在偏房一處隱蔽的房頂上張望历造。 院中可真熱鬧,春花似錦勿锅、人聲如沸帕膜。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)垮刹。三九已至,卻和暖如春张弛,著一層夾襖步出監(jiān)牢的瞬間荒典,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工吞鸭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寺董,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓刻剥,卻偏偏與公主長(zhǎng)得像遮咖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子造虏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容