【稀饭】react native 系列教程之已有项目接入React Native

来源:互联网 发布:迅雷资源助手mac版 编辑:程序博客网 时间:2024/04/29 05:33

概述

本文是基于目前公司的一个真实项目编写的,由于是边实践边记录,遇到什么问题和如何解决的,所以你看这篇文章的时候,可能有时候会觉得不是很流畅,特此说明。

引入React Native

build.gradle配置

compile 'com.facebook.react:react-native:+'

react-native的res使用到了23sdk的资源,因此编译的sdk要求是23

compileSdkVersion 23buildToolsVersion '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界面

image

实现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方法

@Overrideprotected 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参数了。

image

打包

在我们开发完后,需要将应用进行打包,这里说明下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。。。

开始排查定位错误因素。。。

  • gradle 版本2.2.0,引入react.gradle,assembleRelease报错
  • gradle 版本2.1.0-2.1.3,引入react.gradle,assembleRelease报错
  • gradle 版本2.1.3,去掉react.gradle的引入,assembleRelease可以正常打包

在去掉脚本的引入因素之前,尝试修改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文件。

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界面。

image

嗯,这样看起来好像初步实现了需求,但是在思考下,如果每次某个模块修改了,就需要更新整个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";    }};@Overrideprotected 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";    }};@Overrideprotected 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,  Text,  View,} 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, { Component } from 'react';import {  AppRegistry,  Text,  View,} 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/

image

image

最后,打包、安装、运行即可。

但是,你会发现发现eln1和eln2这两个模块并没多少代码,它们的bundle文件就达到来的500多k了,那后面岂不是更大。是的,这是因为react-native在生成bundle文件的时候,会把你import到的模块都打包进去。比如eln1和eln2都使用到了react和react-native模块,那它们的bundle都打包了这两个模块文件。所以,如何优化bundle文件也是个问题,这里给出了58和携程对bundle拆分的方案,满满的干货。

  • 基于 React Native 的 58 同城 App 开发实践
  • React Native Bundle拆分

58是通过生成一个common bundle,然后和不同模块的bundle进行diff拆分,客户端再进行合并;而携程是直接修改react-native bundle脚本命令,过滤不需要的依赖模块。

总结

本文讲述了,在原有的android项目上集成RN,并就遇到的问题,自己摸索着,记录着,也有对项目多模块多业务方案的一点思考。而每个人的现有项目各不相同,遇到的问题也不尽相同,但就像和我一样,一步一步踩着坑过来,你也会成功的,踩坑的过程就是你成长的步伐。

1 0
原创粉丝点击