将RN嵌入到现有的Android应用中

[参考]http://www.jianshu.com/p/99f2a4c21986
官方文档
RN的Guide文档详细地介绍了整个过程,不过其中有些地方没说清楚,可能会给初学者埋下深坑。自己走过一遍,把这些坑列出来。这里整篇地援引原文档,并对原有顺序作了调整。
开发环境准备
首先需要搭建开发环境 ,才能进行后面的步骤。

添加JS环境

命令行,cd到项目到根文件夹下,然后按顺序执行下列命令:

$ npm init
$ npm install --save react 
$ npm install --save react-native
$ curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig
注意一:init命令记住name参数

init命令,需要填写诸如name、version等参数,输入后按回车接着输入下一个参数。init之后会生出package.json文件。name最为重要,工程名,在后面的index.android.js和MyJReactActivity里都要用到。其它的都可以一路回车使用默认值。


package.json文件

注意二:别忘了引入react

上面npm install --save react,下载react相关的资源包。原文里缺了这步命令,最终导致App跑起来就crash,大概是这样的:
java.lang.RuntimeException: Error: Requiring unknown module "react" (index.android.bundle:2 )

注意三:添加start,Json格式

打开刚生出的 package.json文件,在“scripts”内添加“start”,注意符合Json格式,逗号别忘了。

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

curl命令从网络上下载文件,可从示例工程中复制

注意五:index.android.js里的name

按理说init的时候已经指定了“main”,也就是index.android.js,可是没有看到自动生成的文件。我们可以手动创建这份文件,名字就按照它给的来吧,作为hello world,我们照抄AwesomeProject的好了。这里特别要注意的是,最后的AppRegistry.registerComponent,第一个参数是前面package.json里设置的name,第二个参数是自定义的component。为什么是这样,现在还没去深究。

'use strict'

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

class HelloWorld extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.hello}>
          Hello world!
        </Text>
      </View>
    );
  }
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
  hello: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
});

AppRegistry.registerComponent('rntest', () => HelloWorld);

到此为止,Js的环境就配置完成了,后面开始Native部分的配置。

配置Native部分

项目app的build.gradle

里添加react-native依赖:
compile "com.facebook.react:react-native:+" // From node_modules

项目的gradle的repositories里添加url:
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        mavenLocal()
        jcenter()
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url "$rootDir/node_modules/react-native/android"
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

添加权限:

AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>/**设置调试 的权限**/
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
<!--添加RN的调试Activity:-->
<application...>
...
  <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
</application>
授权限

我们刚开始都是先用debug模式,以自己的开发计算机作为服务器。那份index.android.js就是hello world界面。手机在debug模式下从自己的计算机下载index页面。下载的过程会有个提示的dialog,显示它需要上面的权限。6.0及以上的系统,除了在Manifest里授权,还需要在系统设置里的“应用”里的“在其他应用的上层显示”里,找到我们的应用,勾选上允许。不同厂家的设置不太一样,我的是这样的:

设置

如果没有这些权限和设置,会抛这样的异常:
android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@fc0db15 -- permission denied for this window type

添加RN界面

MyReactActivity完整代码如下。需要注意的是,
setUseDeveloperSupport可以直接设为true,在debug期间
mReactRootView.startReactApplication()的第二个参数,必须是package.json里的name

package xxc.application1;

/**
 * Created by proud2008 on 2016/11/5.
 */

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
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;


/**
 * Created by Byron on 16/9/6.
 */
public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

    public static void startActivity(Context context){
        Intent intent = new Intent(context, MyReactActivity.class);
        context.startActivity(intent);
    }

    @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(true)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        mReactRootView.startReactApplication(mReactInstanceManager, "rntest", null);

        setContentView(mReactRootView);
    }

    @Override
    protected void onResume() {
        super.onResume();

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

    @Override
    protected void onPause() {
        super.onPause();

        if(mReactInstanceManager != null){
            mReactInstanceManager.onHostPause();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

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

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

        if(mReactInstanceManager != null){
            mReactInstanceManager.onBackPressed();
        }else{
            super.onBackPressed();
        }
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }
}
运行你的应用

还是在项目的根文件夹下,命令行运行如下命令,启动测试服务器。
$ npm start
或者:
$ react-native start

npm start成功

然后回到Android Studio,跟平常一样,把App运行起来。如果要运行在真机上,要注意在设备上运行的相关事项是否已经做过。我的测试机是6.0点系统,那每次连上usb,都需要先命令行运行一次:
adb reverse tcp:8081 tcp:8081

运行app

然后再运行app,否则手机无法连上测试服务器,会出现“红屏”(添加了DevSettingsActivity的情况下)。同样,如果测试服务器没有开启,也会出现同样的错误,也就是取不到打好包的js,控制台的crash log:
java.lang.RuntimeException: Could not get BatchedBridge, make sure your bundle is packaged correctly

错误1

Caused by: java.lang.IllegalAccessError: Method 'void android.support.v4.net.ConnectivityManagerCompat.<init>()' is inaccessible to class 'com.facebook.react.modules.netinfo.NetInfoModule' (declaration of 'com.facebook.react.modules.netinfo.NetInfoModule' appears in /data/app/xxc.application1-1/base.apk)
 at com.facebook.react.modules.netinfo.NetInfoModule.<init>(NetInfoModule.java:55)
 at com.facebook.react.shell.MainReactPackage.createNativeModules(MainReactPackage.java:67)
 at com.facebook.react.ReactInstanceManagerImpl.processPackage(ReactInstanceManagerImpl.java:793)
 at com.facebook.react.ReactInstanceManagerImpl.createReactContext(ReactInstanceManagerImpl.java:730)
 at com.facebook.react.ReactInstanceManagerImpl.access$600(ReactInstanceManagerImpl.java:91)
 at com.facebook.react.ReactInstanceManagerImpl$ReactContextInitAsyncTask.doInBackground(ReactInstanceManagerImpl.java:184)
 at com.facebook.react.ReactInstanceManagerImpl$ReactContextInitAsyncTask.doInBackground(ReactInstanceManagerImpl.java:169)
 at android.os.AsyncTask$2.call(AsyncTask.java:288)
 at java.util.concurrent.FutureTask.

解决
修改 app/build.gradle
compile 'com.android.support:appcompat-v7:23.0.1''
高版的会出问题,看生成的示例项目是用这个v7包,编译环境也是23
方法来自:https://github.com/facebook/react-native/issues/6152

错误2

Native: Got JS Exception: TypeError: undefined is not a function (evaluating '(bridgeConfig.remoteModuleConfig||[]).forEach')
 11-05 12:20:24.257 4879-5218/xxc.application1 E/unknown:React: Exception in native call from JS
 com.facebook.react.bridge.JSExecutionException: TypeError: undefined is not a function (evaluating '(bridgeConfig.remoteModuleConfig||[]).forEach')

因为项目根目录的build.gradle下没添加

 allprojects {    
     repositories {
         jcenter()        
             maven {            
                 // All of React Native (JS, Android binaries) is installed from npm
                 url "$rootDir/node_modules/react-native/android"            
             }    
      }
 }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容