将 React Native 整合进 Android 项目超详细图文教程

2,291 阅读6分钟

不得不吐个槽,按照官方文档,已经不可能顺利实现React Native与已有项目的整合,因为世界变化太快,Android Studio发展到了2.2,React Native 已经是0.33版。为了帮助大家顺利攻克这个知识点,我写了这个教程。

一、基础信息

在整合的过程中,一些坑是与所用手机型号、Android Studio版本等特定信息紧密相关的,所以有必要明确我所用的配置:

  • Android Stuidio 2.2稳定版
  • 64位win7操作系统
  • 红米note3双网通普配版
  • React Native 0.33版

二、具体步骤

1、创建Android项目

这一步按照AS新建项目向导一步步完成即可,完成后,需要做如下准备工作:

  • 在app module下的build.gradle文件的dependencies中添加React Native 依赖:compile "com.facebook.react:react-native:+"
  • 在Manifest文件中添加权限和Activity:
    
    
    
    

我们新建的项目的一些关键配置如下:

  • compile SDK 和target SDK都是24
  • 所用的支持包的版本是:
    compile 'com.android.support:appcompat-v7:24.2.1'

Note:网上有文章讲,使用的appcompat-v7支持包版本必须是23.0.1,compile SDK和target SDK也必须是23 。经我测试,使用最新版本24也可以。如果你的机器上不行,出现了这样的错误:

Caused by: java.lang.IllegalAccessError: Method 'void 
android.support.v4.net.ConnectivityManagerCompat.()' 
is inaccessible to class 
'com.facebook.react.modules.netinfo.NetInfoModule' 
(declaration of 'com.facebook.react.modules.netinfo.NetInfoModule' 
appears in /data/app/com.milter.www.awesomeproject2-2/base.apk)

可以尝试将appcompat-v7、compile SDK、target SDK调整为23,这是保险的做法。

2、将Android项目变成一个React Native项目

整合后的项目,实际上是三种项目的混合体,首先它是一个普通的Android项目,其次它是一个React Native项目,最后它还是一个Node.js项目。这就是为什么整合比较困难。

下面,我们需要将上步中创建的Android项目变成一个React Native项目。

  • 创建并修改package.json文件
    进入Android项目的根目录,按住shift键并右键单击鼠标,从弹出的菜单中选择:在此处打开命令窗口(W) 选项,在当前目录下打开一个命令窗口,如下所示:

command_line_window.png


图中的ReactNativeWithNativeApp就是第一步中创建的Android项目的名字。

在上面的命令行窗口中输入:
npm init
这个命令会引导你在ReactNativeWithNativeApp目录下创建一个package.json文件。如图所示:


packagejson.png

package.json文件的内容如下:

{
  "name": "reactnativewithnativeapp",
  "version": "1.0.0",
  "description": "integrate RN with existing app",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "milter",
  "license": "ISC",
  "dependencies": {
    "react-native": "^0.33.1"
  }
}

修改上面的 package.json文件,将其中的

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  }

修改为:

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

这样修改后,我们在项目根目录的命令行窗口中输入命令:
npm start
就相当于执行如下命令:
node node_modules/react-native/local-cli/cli.js start

Note:创建package.json文件,意味着原先的Android项目同时也变成了一个Node.js项目,下面,我们给这个Node.js项目引入react native 模块,进一步将它变成一个react native 项目

  • 引入React Native 模块
    还是在原先的命令行窗口中,输入如下命令:
    npm install --save react react-native

执行这个命令后,在项目根目录(ReactNativeWithNativeApp)下会创建一个node_modules目录里面内容如下:


node-modules.png

里面主要有react-native模块以及它所依赖的模块,还是挺多的。

  • 创建.flowconfig文件
    同一命令行窗口下,执行如下命令:
    curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig
    这一命令的作用是将命令中url指向的.flowconfig文件下载到项目的根目录。在上面的图packagejson中可以看到这个下载后的文件。

如果你不知道怎么才能使用curl命令,可参考这篇文章:
windows(64位)下使用curl命令

Tips:如果你不想使用curl命令,这里给一个简单的方法:在根目录中建一个.flowconfig文件,用记事本打开它。
在浏览器中打开网址:
raw.githubusercontent.com/facebook/re…
将网页内容拷贝进打开的.flowconfig文件中,保存并关闭它。

至此,我们的Android项目已经变成了一个Android项目和React Native项目的合体项目。下面,我们将创建React Native项目程序并整合到Android项目中。

3、创建RN程序

在根目录下创建index.android.js文件,内容如下:

'use strict';

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

class HelloWorld extends React.Component {
  render() {
    return (
      
        Hello, World
      
    )
  }
}
var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
  hello: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
});

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

这个文件是一个完全的React Native程序,现在我们需要把它整合到Android项目中去。
整合思路:在Android项目中创建一个Activity,然后利用React Native提供的工具,将上面的index.android.js程序包装进该Activity的contentView中。

4、将RN程序整合进Android项目

  • 基础配置
    在项目根目录的build.gradle中(注意:不是app模块中的build.gradle文件)添加依赖,如下所示:
    allprojects {
      repositories {
          jcenter()
         maven {
              // All of React Native (JS, Android binaries) is installed from npm
              url "$projectDir/../node_modules/react-native/android"
          }
      }
    maven是我们添加的内容。
  • 整合
    主要是修改MainActivity内容,如下所示:

    /* 这里省略了包名和import语句*/
    public class MainActivity extends AppCompatActivity
          implements DefaultHardwareBackBtnHandler {
    
      private ReactRootView mReactRootView;
      private ReactInstanceManager mReactInstanceManager;
      private LifecycleState mLifecycleState
              = LifecycleState.BEFORE_RESUME;
    
      @Override
      protected void onCreate(@Nullable Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
        /* 下面的版本判断代码官方文档中没有,
          如果不添加,在6.0以上的Android版本中会报错 */
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
              if (!Settings.canDrawOverlays(this)) {
                  Intent serviceIntent = new Intent(
                          Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                  startActivity(serviceIntent);
              }
          }
          mReactRootView = new ReactRootView(this);
          mReactInstanceManager = ReactInstanceManager.builder()
                  .setApplication(getApplication())
                  .setBundleAssetName("index.android.bundle")
                  .setJSMainModuleName("index.android")
                  .addPackage(new MainReactPackage())
                  .setUseDeveloperSupport(BuildConfig.DEBUG)
                  .setInitialLifecycleState(mLifecycleState)
                  .build();
    //下面代码中的"HelloWorld"来自index.android.js文件中最后一行代码
          mReactRootView.startReactApplication(mReactInstanceManager,
                  "HelloWorld", null);
    
          setContentView(mReactRootView);
      }
    
      @Override
      protected void onPause() {
          super.onPause();
    
          mLifecycleState = LifecycleState.BEFORE_RESUME;
    
          if (mReactInstanceManager != null) {
              mReactInstanceManager.onHostPause();
          }
      }
    
      @Override
      protected void onResume() {
          super.onResume();
    
          mLifecycleState = LifecycleState.RESUMED;
    
          if (mReactInstanceManager != null) {
              mReactInstanceManager.onHostResume(this, this);
          }
      }
    
      @Override
      protected void onDestroy() {
          super.onDestroy();
    
          mReactRootView.unmountReactApplication();
          mReactRootView = null;
    
          if (mReactInstanceManager != null) {
              mReactInstanceManager.destroy();
          }
      }
    
      @Override
      public void onActivityResult(int requestCode, int resultCode,
                                   Intent data) {
          if (mReactInstanceManager != null) {
              mReactInstanceManager.onActivityResult(this,requestCode,
                      resultCode, data);
          }
      }
    
      @Override
      public void onBackPressed() {
          if (mReactInstanceManager != null) {
              mReactInstanceManager.onBackPressed();
          }
          else {
              super.onBackPressed();
          }
      }
    
      @Override
      public void invokeDefaultOnBackPressed() {
          super.onBackPressed();
      }
    }

三、项目试运行

现在可以尝试运行下整合的项目。
首先,在项目根目录下的命令行窗口运行如下命令:
npm start
前面讲过,这个命令就相当于执行如下命令:
node node_modules/react-native/local-cli/cli.js start

然后,就可以点击Android Studio界面上的运行按钮啦!

不要高兴的太早,你很可能会遇到这个错误:

java.lang.UnsatisfiedLinkError: could find DSO to load: libreactnativejni.so

这个错误的原因是React Native提供的libreactnativejni.so文件是32位,而我们的项目中用了一些不兼容的64位so文件,二者混在一起产生的。

解决的办法就是禁止使用那些64位的so文件。为此,我们需要先做点准备工作。

第一,在项目根目录下的gradle.properties文件最后加上这样一句:
android.useDeprecatedNdk=true
第二、在app module下的build.gradle文件中添加如下内容:

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

第三、找出不兼容的64位so文件并禁止它们
在目录...\ReactNativeWithNativeApp\app\build\outputs\apk下找到app-debug.apk,并把它解压,查看一下,解压后的文件的lib目录下有没有这个目录:
arm64-v8a

一般情况下是没有的,此时我们就真正大功告成了!!!

如果有这个目录,看看里面的so文件,都是我们要禁止的,禁止的方法如下:
假设里面有一个 1.so文件,我们要在app module下的build.gradle文件中做如下修改:

android {
    ...
    defaultConfig {
        ...
        ndk{
            abiFilters "armeabi-v7a", "x86"
        }
        packagingOptions {
            exclude "lib/arm64-v8a/1.so"            
        }
        ...
    }
...
}

如果arm64-v8a目录下还有2.so、3.so等文件,处理方法与1.so一样。

好了,撒花庆祝!!:)

该项目已分享到GitHub,地址是:
github.com/like4hub/Re…