Android/iOS已有原生项目集成ReactNative

  1. 背景
    近期由于项目需要,在已经开发了几个版本的原生App中集成了ReactNative,新版的模块都使用RN开发。
    此次集成的工作大部分是参照RN中文文档进行的,但是中文文档也有一些坑和描述不充分的地方,所以我参照着文档把自己集成的过程以及遇到的问题整理了下,希望能给遇到相同问题的人起到参考作用。
    ReactNative中文文档官方地址:http://reactnative.cn/docs/0.45/integration-with-existing-apps.html#content
  2. 集成过程
    2.1 Android
    2.1.1 在应用中添加js代码
    打开控制台,进入项目根目录,执行以下几个命令:
$npm init
$npm install --save react react-native
$curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig

npm init命令是创建package.json文件,之后会要求输入信息,除了name之外其他的直接按enter默认就行了。
npm install则创建了node_modules目录并把react和react-native下载到了其中。执行此命令时,要是控制台输出了版本不一致的警告信息,例如:

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

则继续执行 npm i -S react@16.0.0-alpha.12 (这里版本跟警告信息一致)。
至于第三步curl命令,其实质是下载.flowconfig配置文件,这个文件用于约束js代码的写法。非必需,可跳过。
执行完成之后我的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"
}
}

接下来还需要把启动脚本放进去:
"start": "node node_modules/react-native/local-cli/cli.js start"
注意下启动脚本的路径,如果集成的项目目录结构不一样的话会报找不到启动脚本之类的错误,到时根据具体目录结构修改就好了。

2.1.2 在项目根目录下创建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 打开你的android项目,在app中 build.gradle 文件中添加 React Native 依赖:

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

接着,在 AndroidManifest.xml 清单文件中声明网络权限:

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

如果需要访问 DevSettingsActivity 界面,也需要在 AndroidManifest.xml 中声明:

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

在项目的 build.gradle 文件中为 React Native 添加一个 maven 依赖的入口,必须写在 "allprojects" 代码块中:

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

这里maven路径官网上写的是$rootDir/../node_modules/react-native/android,这个跟目录结构有关,跟我一样在根目录上操作的话是不需要$rootDir的

2.1.4 同步gradle
遇到以下错误:

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 新建一个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必须对应“index.android.js”中的
        // “AppRegistry.registerComponent()”的第一个参数
        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>

应用在真机上调试时需开启悬浮窗权限才能正确显示红屏错误,官网上有前往开启的代码,但是我觉得有点麻烦了,直接进应用管理中心开启就好了,真正发布的时候也不需要显示红屏错误。

2.1.6 运行程序
2.1.6.1 在根目录下使用npm start命令开启后台服务,然后运行应用,不出意外的话应该可以看到一片红色。
这是后台启动的窗口:

111.png

App首次启动页面:

S70618-170654.jpg

红屏没有出现的话注意检查下悬浮窗权限是否开启。
摇晃下手机,会出现以下界面:

S70618-170659.jpg

点击Dev Setting进入设置页面,再点击 Debug server host &...设置服务器的IP跟端口,这个时候要注意下手机跟电脑要在同一个局域网下:

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

设置之后返回到页面,再次摇晃下手机,就出现成功的页面啦!

S70618-171646.jpg
S70618-172035.jpg

2.2 iOS
2.2.1 新建package.json文件
可以新建也可以直接前面android项目的package.json拷贝到根目录下。
注意这里iOS使用的最新版本的react native在安装pod之后会有点问题,可能是官网上的中文文档没有及时更新的原因,我这里暂时用的还是之前我集成的版本:

{
  "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"
  }
}

官网在一开始建议把iOS代码都放到一个 ios/文件夹下,但这对于一个已经开发多次的项目来说操作并不简单,所以这一步也可以不做,只需把package.json里面启动脚本的路径修改一下即可。
修改之后的路径为:

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

2.2.2 安装依赖包
在根目录下执行命令:

npm install

安装过程中可能会遇到跟2.1.1一样的警告信息,这时操作也一样。
2.2.3 安装pod
2.2.3.1 如果你的项目还没安装pod则先执行pod init,然后在创建了的Podfile文件下加入:

# 'node_modules'目录一般位于根目录中
    # 但是如果你的结构不同,那你就要根据实际路径修改下面的`:path`
    pod 'React', :path => '../ExampleIOS/node_modules/react-native', :subspecs => [
      'Core',
      'DevSupport', # 如果RN版本 >= 0.43,则需要加入此行才能开启开发者菜单
      'RCTText',
      'RCTNetwork',
      'RCTWebSocket', # 这个模块是用于调试功能的
      # 在这里继续添加你所需要的模块
    ]
    # 如果你的RN版本 >= 0.42.0,请加入下面这行
    pod "Yoga", :path => "../ExampleIOS/node_modules/react-native/ReactCommon/yoga"

然后执行pod install 命令开始安装。看到以下图片就说明安装成功了。

333.png

2.2.3.2 如果你已经安装了pod,则直接在Podfile中加入上面的内容,然后使用pod update --verbose --no-repo-update命令更新pod就可以了。
在这里有个需要注意的地方,安装pod之后就要关闭项目,使用.xcworkspace文件打开项目了。

2.2.4 代码集成
2.2.4.1 创建index.ios.js文件
在项目根目录下创建index.ios.js文件,内容跟之前的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 在需要挑战的ViewContrller中引入RCTRootView.h,跳转代码:

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开启后台,启动程序:

3335.png

3336.png

由于苹果的安全访问限制可能会出现访问不到的原因,这时候需要往info.plist加入下面这段代码:

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

然后重新运行程序就可以看到以下结果啦!!

3337.png
33338.png
  1. 大功告成!接下来我把项目上传到github,有需要的可以下下来参考下:
    Android:
    https://git.oschina.net/GStick/androidiosfeatreactnative.git
    iOS:
    https://git.oschina.net/GStick/androidiosfeatreactnative.git
  2. 之后会继续更新集成之后开发中用到的东西,像加载离线bundle文件、交互之类的,有时间的话还是Android/iOS双管齐下,敬请期待。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容