【稀饭】react native 系列教程之已有项目接入React Native
概述本文是基于目前公司的一个真实项目编写的,由于是边实践边记录,遇到什么问题和如何解决的,所以你看这篇文章的时候,可能有时候会觉得不是很流畅,特此说明。 引入React Nativebuild.gradle配置 compile 'com.facebook.react:react-native:+'
react-native的res使用到了23sdk的资源,因此编译的sdk要求是23 compileSdkVersion 23
buildToolsVersion '23.0.3'
但这样如果你项目中使用到了HttpClient这个类的话,由于sdk 23版本已经将其移除掉,所以要多加配置 android { useLibrary 'org.apache.http.legacy' }
项目原来的gradle版本是1.2.3,但这句配置需要升级到最新版本2.0.0 dependencies { classpath 'com.android.tools.build:gradle:2.2.0' }
gradle-wrapper.properties distributionUrl=https://services.gradle.org/distributions/gradle-2.14.1-all.zip
react-native的minSdkVersion是16 android:minSdkVersion="16"
如果你在AndroidManifest.xml配置了该项,并且低于16,为了编译通过,需配置overrideLibrary <uses-sdk tools:overrideLibrary="com.facebook.react" android:minSdkVersion="14" android:targetSdkVersion="21" />
还需添加react native的DevSettingActivity <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
multiDex然后试着编译运行,结果报错,原因是由于引进react-native,方法超出了64k限制,需要拆分dex。 再配置build.gradle defaultConfig {
multiDexEnabled true
}
然后自己的Application继承MultiDexApplication,或者重写attachBaseContext方法 protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
RN配置本地仓库这下编译通过了,但是发现react-native版本是0.21,并不是最新版本的,所以这里我们要将项目目录修改为react-native项目目录。 创建了DX目录,将原来的项目android移到二级目录,然后剩下的几个文件和node_modules可以从react-native初始项目中拷贝过来(也可以执行npm init&npm install命令,但是太慢了),修改package.json里面的name为项目名称。 react-native项目中android项目的文件夹名称是为‘android’,刚好和我们原来的android项目一致,但是是否一定要取名为‘android’有待验证。 接着,修改android项目的根目录下的build.gradle allprojects {
repositories {
mavenLocal()
jcenter()
maven {
// All of React Native (JS,Obj-C sources,Android binaries) is installed from npm
//使用本地仓库,使react native 版本是最新的
url "$rootDir/../node_modules/react-native/android"
}
}
}
添加了本地仓库,url填写的是node_modules目录下的react-native 好了,重新编译一下,react-native版本是0.31的了(目前官网最新的版本是0.34,本地还没有更新)。 本地打开RN界面实现ReactApplication接口首先需要在自己的Application,比如本项目中的ElnApplication实现ReactApplication接口,重写getReactNativeHost方法,给RN提供一个默认的ReactNativeHost public class ElnApplication extends BaseApplication implements ReactApplication{
//...省略其它代码
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
@Override
protected String getJSMainModuleName() {
//定义js入口文件名称
return super.getJSMainModuleName();
}
@Nullable
@Override
protected String getBundleAssetName() {
//定义存放在项目asset文件夹下的bundle文件名称
return super.getBundleAssetName();
}
@Nullable
@Override
protected String getJSBundleFile() {
//自定义bundle文件路径
return super.getJSBundleFile();
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
}
创建Activity继承ReactActivity新建TestRnActivity类,并继承ReactActivity public class TestRnActivity extends ReactActivity {
@Override
protected String getMainComponentName() {
return "eln";//这个名称与js端AppRegistry.registerComponent要一致,可以注册多个入口,例如TestRnActivity2
}
}
编写js代码接着打开项目的index.android.js,修改代码 import React,{ Component } from 'react';
import {
AppRegistry,Text,View,} from 'react-native';
class Eln extends Component {
render(){
return(
<View>
<Text>我是RN页面第一个入口</Text>
</View>
);
}
}
//eln字符串必修与TestRnActivity$getMainComponentName一致
AppRegistry.registerComponent('eln',() => Eln);
然后和普通RN项目运行一样,运行项目,就看到可以打开RN界面了。 本地给RN界面传递参数那在打开RN界面时,有时候需要传递参数,那该如何呢? 打开TestRnActivity.java重写getLaunchOptions方法 @Override
protected Bundle getLaunchOptions() {//给js层传递数据,js层通过组件的props获取数据
Bundle bundle = new Bundle();
bundle.putString("des","我是从native传递过来的");
return bundle;
}
然后js代码调用 class Eln extends Component {
render(){
return(
<View>
<Text>我是RN页面第一个入口</Text>
<Text>{this.props.des}</Text>
</View>
);
}
}
这样就可以获取到des参数了。 打包在我们开发完后,需要将应用进行打包,这里说明下RN和android项目混合开发的打包事项 混淆按照官网的混淆配置还是报错 Caused by: java.lang.NoSuchFieldError: no field with name='mHybridData' signature='Lcom/facebook/jni/HybridData;' in class Lcom/facebook/react/cxxbridge/CatalystInstanceImpl;
at com.facebook.react.cxxbridge.ModuleRegistryHolder.initHybrid(Native Method)
at com.facebook.react.cxxbridge.ModuleRegistryHolder.<init>(Proguard:26)
at com.facebook.react.cxxbridge.i.a(Proguard:63)
at com.facebook.react.cxxbridge.CatalystInstanceImpl.<init>(Proguard:106)
at com.facebook.react.cxxbridge.CatalystInstanceImpl.<init>(Proguard:50)
at com.facebook.react.cxxbridge.c.a(Proguard:483)
at com.facebook.react.p.a(Proguard:868)
at com.facebook.react.p.a(Proguard:103)
at com.facebook.react.q.a(Proguard:203)
at com.facebook.react.q.doInBackground(Proguard:182)
at android.os.AsyncTask$2.call(AsyncTask.java:287)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:841)
混淆配置增加这句 -keep class com.facebook.** { *; }
配置bundle gradle打包命令在build.gradle配置 apply from: "../../node_modules/react-native/react.gradle"
然后执行在项目下执行命令(dev环境) gradlew assembleDevRelease 遇到各种编译问题。。。。 执行gradlew assembleDevRelease命令异常 Unsupported major.minor version 52.0
修改gradle.properties android.useDeprecatedNdk=true
和gradle版本 classpath 'com.android.tools.build:gradle:2.1.0'
还是报错duplicate file。。。 开始排查定位错误因素。。。
在去掉脚本的引入因素之前,尝试修改buildTools和gradle版本号,还是报各种错。。。 使用bundle手动打包命令react-native bundle
--platform android --dev false --entry-file index.android.js --bundle-output android/eln_base/assets/index.android.bundle --assets-dest android/eln_base/res/
目的是将bundle包生成放在android项目的assets文件夹下 然后项目去掉react.gradle脚本的引入,执行assembleDevRelease,成功打包,解压压缩包,在assets下可以看到多了两个bundle文件。 安装运行,也可以正常打开RN界面 多业务分模块考虑到真实项目场景,可能不止一个RN入口,有多个业务模块需要使用到RN,但是它们的入口可能又不同,如一开始的图,比如在‘发现’大模块下,有两个小功能模块需要使用RN技术来实现,那么此时就需要各自打开各自的RN界面,那么这种需求如何实现呢? 单bundle你可能想到了,那就是,一个新的入口,那么我就再建一个ReactActivity。没错的,那么我们创建下TestRnActivity2类。 同TestRnActivity一样,继承ReactActivity,但是getMainComponentName返回不同的名称,加以区别。 public class TestRnActivity2 extends ReactActivity {
@Override
protected String getMainComponentName() {
return "eln2";
}
}
接着,js端,打开index.android.js,编写eln2 class Eln extends Component {
render(){
return(
<View>
<Text>我是RN页面第一个入口</Text>
<Text>{this.props.des}</Text>
</View>
);
}
}
class Eln2 extends Component {
render(){
return(
<View>
<Text>我是RN页面第二个入口</Text>
</View>
);
}
}
AppRegistry.registerComponent('eln',() => Eln);
AppRegistry.registerComponent('eln2',() => Eln2);
可以看到我们registerComponent了两个组件,eln和eln2。 最后按上面的打包流程,在assets下生成bundle文件,再打包成apk,安装运行。 点击‘测试RN2’,进入第二个RN界面。 嗯,这样看起来好像初步实现了需求,但是在思考下,如果每次某个模块修改了,就需要更新整个bundle。是否可以这样:各自模块独立,更新也独立? 多bundle使用多bundle的方案,首先需要让各自的模块加载自己的bundle文件。 修改TestRnActivity和TestRnActivity2,分别重写getReactNativeHost方法 TestRnActivity.java private final ReactNativeHost mReactNativeHost = new ReactNativeHost(ElnApplication.getInstance()) {
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
@Nullable
@Override
protected String getBundleAssetName() {
//定义存放在项目asset文件夹下的bundle文件名称
return "eln1.android.bundle";
}
@Override
protected String getJSMainModuleName() {
//定义TestRnActivity2启动入口的js文件
return "eln1.android";
}
};
@Override
protected ReactNativeHost getReactNativeHost() {//重写ReactNativeHost
return mReactNativeHost;
}
TestRnActivity.java private final ReactNativeHost mReactNativeHost = new ReactNativeHost(ElnApplication.getInstance()) {
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
@Nullable
@Override
protected String getBundleAssetName() {
//定义存放在项目asset文件夹下的bundle文件名称
return "eln2.android.bundle";
}
@Override
protected String getJSMainModuleName() {
//定义TestRnActivity2启动入口的js文件
return "eln2.android";
}
};
@Override
protected ReactNativeHost getReactNativeHost() {//重写ReactNativeHost
return mReactNativeHost;
}
两个模块的bundle文件分别取名为eln1.android.bundle和eln2.android.bundle,它们的js入口文件分别为eln1.android.js和eln2.android.js 接着,需要在js层编写这两个文件。在RN项目目录下创建eln1.android.js和eln2.android.js(和之前的index.android.js同级) eln1.android.js import React,{ Component } from 'react';
import {
AppRegistry,} from 'react-native';
class Eln extends Component {
render(){
return(
<View> <Text>我是RN页面第一个入口</Text> <Text>{this.props.des}</Text> </View> ); } } AppRegistry.registerComponent('eln',() => Eln);
eln2.android.js import React,} from 'react-native';
class Eln2 extends Component {
render(){
return(
<View>
<Text>我是RN页面第二个入口</Text>
</View>
);
}
}
AppRegistry.registerComponent('eln2',() => Eln2);
然后使用react-native bundle命令分别生成这两个bundle文件 react-native bundle --platform android --dev false --entry-file eln1.android.js --bundle-output android/eln_base/assets/eln1.android.bundle --assets-dest android/eln_base/res/
react-native bundle --platform android --dev false --entry-file eln2.android.js --bundle-output android/eln_base/assets/eln2.android.bundle --assets-dest android/eln_base/res/
最后,打包、安装、运行即可。 但是,你会发现发现eln1和eln2这两个模块并没多少代码,它们的bundle文件就达到来的500多k了,那后面岂不是更大。是的,这是因为react-native在生成bundle文件的时候,会把你import到的模块都打包进去。比如eln1和eln2都使用到了react和react-native模块,那它们的bundle都打包了这两个模块文件。所以,如何优化bundle文件也是个问题,这里给出了58和携程对bundle拆分的方案,满满的干货。
58是通过生成一个common bundle,然后和不同模块的bundle进行diff拆分,客户端再进行合并;而携程是直接修改react-native bundle脚本命令,过滤不需要的依赖模块。 总结本文讲述了,在原有的android项目上集成RN,并就遇到的问题,自己摸索着,记录着,也有对项目多模块多业务方案的一点思考。而每个人的现有项目各不相同,遇到的问题也不尽相同,但就像和我一样,一步一步踩着坑过来,你也会成功的,踩坑的过程就是你成长的步伐。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |