React Native学习笔记(二):集成到现有原生应用

前言
正如React Native中文网上所说,如果你正准备从头开始制作一个新的应用,那么React Native会是个非常好的选择。但如果你只想给现有的原生应用中添加一两个视图或是业务流程,React Native也同样不在话下。只需简单几步,你就可以给原有应用加上新的基于React Native的特性、画面和视图等。
其相关步骤中文网上已经讲的很清楚,不过有时候我就是看到有什么不顺眼的东西,或者感觉什么不合理的地方,就想着能不能用跟合理或者更顺眼的方式去实现。很不幸在这里也让我看到了某些很碍眼给我感觉很扯淡莫名其妙的步骤。所以这里我的实现跟中文网上的有稍微那么一丢丢不一样,当然整体来说还是差不多的,毕竟也是参照这个来实现的。

首先声明下,我这里是在原有的Android Studio项目的基础上集成React Native页面的。
好了,下面是开始实现步骤。。。

  • 中文网上开始步骤是这样的:

首先创建一个空目录用于存放React Native项目,然后在其中创建一个/android子目录,把你现有的Android项目拷贝到/android子目录中。
慢着,别动,我想要吐槽的就是这一步,我们的应用是基于Android Studio上开发的,还建什么空目录啊,还把我们的Android项目放到他的/android子目录上,那我们还怎么用Android Studio打开我们的项目啊,整个目录结构都被破坏了好吧,还怎么让我们继续原生开发啊,已经不适合在Android Studio上开发了好伐。(不过中文网作为技术这么成熟的网站,其内容也主要是翻译facebook官网的内容,所以它肯定有它的考量,或者完美的解决方案,我作为一个技术渣渣在此大言不惭只是想一吐为快罢了,因为我还不知道它后面具体是怎么考量的。其实刚开始我有试过按它这个步骤来,不过马上你就会发现一个很尴尬的问题,我究竟该怎么继续用Android Studio打开这个项目?不过可以初步看出的是,它是为了使整个项目结构和用“react-native init ProjectName”命令所生成的项目结构保持一致而这样做的)
所以我直接忽略这一步,后面所提到的项目根目录还是我们原来的原生项目根目录,没有新建空目录,没有android子目录,所有的操作依然在Android studio上完成,这样,你既可以继续在Android Studio上高效的完成原生开发,也能进行相应的RN集成操作,包括一些命令执行,也是用Android Studio自带的命令终端。

  • 接下来,跟中文网上一样,安装JavaScript依赖包,下面我就直接引用中文上上的内容了

在项目根目录下创建一个名为package.json的空文本文件,然后填入以下内容:

{
"name": "MyReactNativeApp",
"version": "0.0.1",
"private": true,
"scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start"
    },
"dependencies": {
    "react": "16.0.0-alpha.12",//适配最新RN版本,跟原文上不太一致。
    "react-native": "0.51.0"
    }
}

示例中的version字段没有太大意义(除非你要把你的项目发布到npm仓库)。scripts中是用于启动packager服务的命令。dependencies中的react和react-native的版本取决于你的具体需求。一般来说我们推荐使用最新版本。你可以使用npm info react和npm info react-native来查看当前的最新版本。另外,react-native对react的版本有严格要求,高于或低于某个范围都不可以。本文无法在这里列出所有react native和对应的react版本要求,只能提醒读者先尝试执行npm install,然后注意观察安装过程中的报错信息,例如require react@某.某.某版本, but none was installed,然后根据这样的提示,执行npm i -S react@某.某.某版本。如果你使用多个第三方依赖,可能这些第三方各自要求的react版本有所冲突,此时应优先满足react-native所需要的react版本。其他第三方能用则用,不能用则只能考虑选择其他库。
接下来我们使用npm(node包管理器,Node package manager)来安装React和React Native模块。 请打开一个终端/命令提示行,进入到项目目录中(即包含有package.json文件的目录),然后运行下列命令来安装:
$ npm install
这些模块会被安装到项目根目录下的node_modules/目录中(所有通过npm install命令安装的模块都会放在这个目录中。这个目录我们原则上不复制、不移动、不修改、不上传,随用随装)。

  • 把React Native添加到你的应用中

配置maven
在你的app中 build.gradle 文件中添加 React Native 依赖:

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

如果想要指定特定的React Native版本,可以用具体的版本号替换 +,当然前提是你从npm里下载的是这个版本 。
在项目的 build.gradle 文件中为 React Native 添加一个 maven 依赖的入口,必须写在 “allprojects” 代码块中:

allprojects {
repositories {
...
    maven {
    // All of React Native (JS, Android binaries) is installed from npm
    url "$rootDir/node_modules/react-native/android"   //注意:这里我们的node_modules目录就在根目录下,所以不需要跳转到上一目录(原文$root/../node_modules/...)
    }
}
...
}

确保依赖路径的正确!以免在 Android Studio 运行Gradle同步构建时抛出 “Failed to resolve: com.facebook.react:react-native:0.x.x” 异常。
配置权限
接着,在 AndroidManifest.xml 清单文件中声明网络权限:

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

如果需要访问 DevSettingsActivity 界面(即开发者菜单),则还需要在 AndroidManifest.xml 中声明:

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

开发者菜单一般仅用于在开发时从Packager服务器刷新JavaScript代码,所以在正式发布时你可以去掉这一权限。
代码集成
Now we will actually modify the native Android application to integrate React Native.
React Native组件1. 创建一个index.js文件
首先在项目根目录中创建一个空的index.js文件。(注意在0.49版本之前是index.android.js文件)
index.js是React Native应用在Android上的入口文件。而且它是不可或缺的!它可以是个很简单的文件,简单到可以只包含一行require/import导入语句。本教程中为了简单示范,把全部的代码都写到了index.js里(当然实际开发中我们并不推荐这样做)。
2. 添加你自己的React Native代码
在index.js中添加你自己的组件。这里我们只是简单的添加一个组件,然后用一个带有样式的组件把它包起来。

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('MyReactNativeApp', () => HelloWorld);
  1. 配置权限以便开发中的红屏错误能正确显示
    如果你的应用会运行在Android 6.0(API level 23)或更高版本,请确保你在开发版本中有打开悬浮窗(overlay)权限。你可以在代码中使用Settings.canDrawOverlays(this);来检查。之所以需要这一权限,是因为我们会把开发中的报错显示在悬浮窗中(仅在开发阶段需要)。在Android 6.0(API level 23)中用户需要手动同意授权。具体请求授权的做法是在onCreate()中添加如下代码。其中OVERLAY_PERMISSION_REQ_CODE是用于回传授权结果的字段。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (!Settings.canDrawOverlays(this)) {
        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
        Uri.parse("package:" + getPackageName()));
        startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
    }
}

Finally, the onActivityResult() method (as shown in the code below) has to be overridden to handle the permission Accepted or Denied cases for consistent UX.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
            // SYSTEM_ALERT_WINDOW permission not granted...
            }
        }
    }
}

掌握核心科技:ReactRootView
我们还需要添加一些原生代码来启动React Native的运行时环境并让它开始渲染。首先需要在一个Activity中创建一个ReactRootView对象,然后在这个对象之中启动React Native应用,并将它设为界面的主视图。
如果你想在安卓5.0以下的系统上运行,请用 com.android.support:appcompat 包中的 AppCompatActivity 代替 Activity 。

public class MyReactActivity extends Activity 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.bundle")//注:原文里是“index.andrioid.bundle”,新版本(react native版本0.49以后)上已经部分android/iOS了
        .setJSMainModulePath("index")    //注:原文这里是.setJSMainModuleName("index.android"),可能是旧版本上的方法,我拷过去后发现在Android Studio上爆红了,也就是没有这个方法了,
                                                    //不过发现有这个.setJSMainModulePath()这个方法,应该是新版本的替代方法
        .addPackage(new MainReactPackage())
        .setUseDeveloperSupport(BuildConfig.DEBUG)
        .setInitialLifecycleState(LifecycleState.RESUMED)
        .build();

    // 注意这里的MyReactNativeApp必须对应“index.js”中的
    // “AppRegistry.registerComponent()”的第一个参数
    mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);

    setContentView(mReactRootView);
}

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

如果你使用的是 Android Studio , 可以使用Alt + Enter快捷键来自动为MyReactActivity类补上缺失的import语句。注意BuildConfig应该是在你自己的包中自动生成,无需额外引入。千万不要从com.facebook…的包中引入!
我们需要把 MyReactActivity 的主题设定为 Theme.AppCompat.Light.NoActionBar ,因为里面有许多组件都使用了这一主题。

@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();
    }
}
//我们还需要把后退按钮事件传递给React Native:
@Override
public void onBackPressed() {
    if (mReactInstanceManager != null) {
        mReactInstanceManager.onBackPressed();
    } else {
        super.onBackPressed();
    }
}

This allows JavaScript to control what happens when the user presses the hardware back button (e.g. to implement navigation). When JavaScript doesn’t handle a back press, your invokeDefaultOnBackPressed method will be called. By default this simply finishes your Activity.
Finally, we need to hook up the dev menu. By default, this is activated by (rage) shaking the device, but this is not very useful in emulators. So we make it show when you press the hardware menu button (use Ctrl + M if you’re using Android Studio emulator):
@Overridepublic boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
mReactInstanceManager.showDevOptionsDialog();
return true;
}
return super.onKeyUp(keyCode, event);
}
现在activity已就绪,可以运行一些JavaScript代码了。

  • 测试集成结果

You have now done all the basic steps to integrate React Native with your current application. Now we will start the React Native packager to build the index.bundle package and the server running on localhost to serve it.
1. 运行Packager
运行应用首先需要启动开发服务器(Packager)。你只需在项目根目录中执行以下命令即可:
$ npm start
2. 运行你的应用
保持packager的窗口运行不要关闭,然后像往常一样编译运行你的Android应用(在命令行中执行./gradlew installDebug或是在Android Studio中编译运行)。

中文网上内容到此结束,如果到这里运行你的项目能顺利到达MyReactActivity并正确显示“Hello World”的话,恭喜你成功了,而我就没那么幸运了,下面说下我运行时遇到的各种坑及最终解决方法,这也是我写此文的主要目的,就叫它坑爹的“八阿哥”消灭记吧。同样的问题可能有不一样的原因,这里主要说下我遇到的这些问题的最终解决方案。
Bug 1:运行跳转到MyReactActivity,报错“undefined is not an object evaluating ReactInternals.ReactCurrentOwner”如图:
这里写图片描述
网上大部分人是说RN的版本问题,估计也差不了,通过查阅资料,我的最终解决方案如下:
首先查看使用的 React Native 是否为0.45以上版本,如果是请把react更新到16.0.0-alpha.12,执行命令:

yarn add [email protected]
或者
npm add [email protected] (根据自己的管理器来定使用yarn还是npm)
再次开启服务:
npm start

Bug 2:报错:java.lang.UnsatisfiedLinkError: dlopen failed: “/data/app/com.xxx.xxx-2/lib/arm64/xxx.so” is 32-bit instead of 64-bit
这个Bug就比较坑爹也比较恶心了,说明下,我开始在模拟器上是没问题的,才兴奋感叹终于成功了,跑真机上一运行,跳转到MyReactActivity,我了个乖乖,应用又炸了,还不是RN的红屏报错,在AS的Log栏里找到了这个错误信息。
根据上面错误信息内容,应该是.so文件不匹配的问题,于是啊,继续百度,最终找到了解决方法:
1.在build.gradle中配置如下:

android {
    ...
    defaultConfig {
    ....
        ndk {
        moduleName"native"
        abiFilters"armeabi","armeabi-v7a","x86","mips"
        }
    }
    ...
}

2.在gradle.properties中添加配置(如果没有自己创建):android.useDeprecatedNdk=true。
Bingo,搞定

参考文献:
React Native中文网—集成到原生应用

猜你喜欢

转载自blog.csdn.net/zsp765098084/article/details/78914501
今日推荐