ReactNative从零到完整项目-嵌入到安卓原生应用(方式一)

项目连接:HelloRN
ReactNative使用手册

把React Native组件植入到Android应用中官方步骤:

首先,我有一句MMP,不知当讲不当讲,这是玩RN以来遇到的巨坑,当你看到这篇文章的时候,从开始完成这个项目到现在已经过去2天半了,我已经按照官方文档集成在5次以上,已经浏览文章30篇以上,包括在# facebook/react-nativeissues看了很多相关的问题,最终走出来了

1.  首先当然要了解你要植入的React Native组件。
2.  在Android项目根目录中使用npm来安装`react-native` ,这样同时会创建一个`node_modules/`的目录。
3.  创建js文件,编写React Native组件的js代码。
4.  在`build.gradle`文件中添加`com.facebook.react:react-native:+`,以及一个指向`node_nodules/`目录中的`react-native`预编译库的`maven`路径。
5.  创建一个React Native专属的`Activity`,在其中再创建`ReactRootView`。
6.  启动React Native的Packager服务,运行应用。
7.  根据需要添加更多React Native的组件。
8.  在真机上[运行](https://reactnative.cn/docs/0.40/running-on-device-android.html)、[调试](https://reactnative.cn/docs/0.40/debugging.html)。
9.  [打包](https://reactnative.cn/docs/0.40/signed-apk-android.html)。
10.  发布应用,升职加薪,走向人生巅峰!

算然官网给出了方法步骤,但是文档过于简单,而且还有很多巨坑,所以本文还是有一定价值的,当然在上面的步骤中有一些我们没必要关注,但是主要步骤还是有的,接下来就按照这个步骤去完成把RN嵌入到android原生项目中

把React Native组件植入到Android应用

第一步:引入react-native

在androidstudio的Terminal窗口中输入 npm init,接着会提示你输入一些东西(除了项目名字其他都可直接回车使用默认值),如下图

image.png

当我们输完的时候将工程切换到project模式下,可以看到工程多了一个package.json的文件

{
"name": "hellorn",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
},
"author": "赖天兵",
"license": "ISC"
}

看到这个文件有一种很熟悉但又陌生的感觉,但是它的作用都应该猜到了,这个和我们build.gradle中的配置是一样的作用,其实就是配置工程的一些属性

第二步:添加react和react_native模块

在Terminal窗口中输入:npm install --save react react-native并执行,然后就是静静的等待,如果有报错,自己手动敲"--"这个符号,因为在不同的系统下“--”可能是不一样的,完成后就可以看到工程多了一个node_modules模块

node_modules

第三步:在命令行中运行curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig(这步对Windows来说真的坑)

直接运行会提示你:'curl' 不是内部或外部命令,也不是可运行的程序

所以看来我的首先处理Windows运行curl命令的问题了

curl是利用URL语法在命令行方式下工作的开源文件传输工具。它被广泛应用在Unix、多种Linux发行版中,并且有DOS和Win32、Win64下的移植版本。
所以首先要解决Windows下支持curl命令的问题Windows下安装使用curl命令
提示有可能你找不到| curl-7.33.0-win64-ssl-sspi.zip

提示

再提示一下当你按照上面Windows下安装使用curl命令走到了下图步骤时

命令行步骤

这时候应该在命令窗口中输入curl -v -X OPTIONS https://www.baidu.com/,这是文档没有说的,而是直接输入到命令行里了,第一次看如果不知道这个,那会很懵逼的
不过遗憾的是我按照上门的连接文档一步步走下来还是无法在任何地方使用curl命令(我的电脑win10 64位)

所以我用了最简单的方法,直接复制我们下载的curl.exe到工程根目录,这样在本工程的根目录就能运行curl命令了

但是:真想爆粗口,还是生成不了.flowconfig文件,

所以真正最直接的解决方式来了:自己在项目的根目录下创建.flowconfig文本文件,然后打开.flowconfig连接复制文本到刚刚创建的文件中即可
image.png

走到这里突然想感叹一句,我饶了一大圈到底是为了啥

第三步:在package.json文件中的scripts里面配置启动脚本"start": "node node_modules/react-native/local-cli/cli.js start",

{
  "name": "hellorn",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "bundle-android": "react-native bundle --platform android --entry-file index.android.js --bundle-output app/src/main/assets/index.android.bundle --dev false"
  },
  "author": "赖天兵",
  "license": "ISC",
  "dependencies": {
    "react": "^16.2.0",
    "react-native": "^0.53.3"
  }
}

其实在官方文档中第三步已经完了,但是毕竟我是踏过了很多巨坑的,看了很多篇博客的我告诉你这步还没完,照常理这里还应该配置打包用index.android.js生成index.andriod.bundle的配置(你也可以尝试不做,绝对会出现一些经典的bug,这里就不提了,我会有一篇专门的文章记录这些bug)

配置生成发布、打包时所需要bundle文件的配置

在很多博客中会这样解决这个问题,像上面完整package.json中的scripts代码一样添加
, "bundle-android": "react-native bundle --platform android --entry-file index.android.js --bundle-output app/src/main/assets/index.android.bundle --dev false"
这一句在官方文档中没有说明,但是最好加上(虽然我加上后,至少我这里没有卵用,据说是新版本的RN不支持自动生成打包所需的bundle文件了),但是这里需要修改一下,很多博客都直接是
"bundle-android": "react-native bundle –platform android –dev false –entry-file index.android.js –bundle-output android/app/src/main/assets/index.android.bundle –sourcemap-output android/app/src/main/assets/index.android.map –assets-dest android/app/src/main/res/"
但是这是错的,打包的时候我们都要根据自己项目目录的结果做一些调整,不过在上面package.json中我已经做过调整了,但是还是无法自动完成打包bundle
解决:我们先在main下面创建assets文件夹,然后进入工程根目录打开cmd输入
react-native bundle --platform android --entry-file index.android.js --bundle-output app/src/main/assets/index.android.bundle --dev false(注意-output后面的参数根据自己的项目目录结构来写)

生成打包bundle文件

成功后的项目
image.png

你或许看过很多在安卓原始项目中嵌套RN,并按照他们步骤一步步完成了,但是最后就是看不到效果,这里就是其中一个重要的原因

如果这步完成了,那么恭喜你,你基本上能看到效果了,后面可能还要出bug,但是都是些容易解决的固定的了

第四步:在项目根目录中创建index.android.js文件

index.android.js

在index.android.js中编写我们的代码

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

class HelloWorldApp extends React.Component {
  render() {
return (
  <View style={styles.container}>
<Text style={styles.hello}>Hello world! I am from ReactNattive!!</Text>
  </View>
)
  }
}
var styles = StyleSheet.create({
  container: {
flex: 1,
justifyContent: 'center',
  },
  hello: {
fontSize: 20,
textAlign: 'center',
margin: 10,
  },
});
//这里的第一个参熟名字要和我们创建的这个工程项目名一样
AppRegistry.registerComponent('HelloRN', () => HelloWorldApp);

提示:在本系列RN博客的第二篇创建HelloWorld的时候说过,registerComponent()注册时名字必须和项目名字保持一致

第五步:添加ReactNative依赖

首先在APP的build.gradle中添加
dependencies { ... compile "com.facebook.react:react-native:+" // From node_modules. }

其次在工程的build.gradle中添加进入本地ReactNative仓库的路径,但是官方源文档写的路径是
$rootDir/../node_modules/react-native/android
如果你直接复制使用,恭喜你你又入坑了
因为我们通过之前第二步添加的添加react和react_native模块,默认是项目的根目录下,而官方文档给出的路径其实是多了“../”所以这就是一个坑(说到这里提一句,网上很多文章都是直接用的官方路径,但是项目结构又和我的一样,我真怀疑他们写那个文章的时候只是copy别人文章,自己根本没有成功,这也是很多人按照别人文章一步步到最后还是报错的又一个原因),这里路径应该对应自己工程中module的路径
我的工程目录

image.png

所以我的路径应该是

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

添加完成后记得同步以下哦,不过等待的时间过于漫长(最好开VPN,我是开了VPN才同步完成的),可以先往后继续看文章

第六步:在清单文件中添加网络请求权限,必须添加的

官方原话Next, make sure you have the Internet permission in your 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"/>

第七步:在清单文件中注册DevSettingsActivity,此步骤可以省略,功能就是重载JavaScript

如果您需要访问DevSettingsActivity添加到您的AndroidManifest.xml:
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
这只在开发服务器重新加载JavaScript时才真正用于开发模式,因此,如果需要,可以在发布版本中将其剥离。

第八步:把之前项目自动创建的MainActivity中代码改成如下代码,但是注意当APP需要支持5.0以下的机型那么需要使用com.android.support:appcompat包中的AppCompatActivity类,而不能直接使用Activity


import android.support.v7.app.AppCompatActivity;
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;


public class MainActivity extends AppCompatActivity 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.android.bundle")
                //这里需要注意,官方文档setJSMainModuleName在新  版本中找不到,替换为setJSMainModulePath
//                .setJSMainModuleName("index.android")
                //这里的路径是相对于根目录的,填入index.android即可
                .setJSMainModulePath("index.android")
                .addPackage(new MainReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        //注意这里的`moduleName`参数必须和工程名字,也是就是在`index.android.js`中AppRegistry.registerComponent()注册的名字一样
        mReactRootView.startReactApplication(mReactInstanceManager, "HelloRN", null);

        setContentView(mReactRootView);
    }

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

接下来(这个步骤是可以跳过的),我们需要将一些活动生命周期回调传递给ReactInstanceManager:

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

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

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

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

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

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

注意:如果你直接copy官网中代码的话,是会出错的,因为ReactNative版本更新了,方法名字更改和安卓更加同步了

也是可跳过的)我们还需要将按钮事件传递给React Native:

   @Override
    public void onBackPressed() {
        if (mReactInstanceManager != null) {
            mReactInstanceManager.onBackPressed();
        } else {
            super.onBackPressed();
        }
    }

作用:这允许JavaScript控制用户按下硬件后退按钮时发生的情况(例如,实现导航)。当JavaScript不处理背按时,您的invokeDefaultOnBackPressed方法将被调用。默认情况下,这只是完成你的Activity。

最后(这个步骤也是可以跳过的),我们需要连接开发菜单。默认情况下,这是通过(愤怒)激发设备来激活的,但这在模拟器中并不是很有用。所以我们在按下硬件菜单按钮时显示它(Ctrl + M如果您使用的是Android Studio模拟器,请使用它):

 @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
            mReactInstanceManager.showDevOptionsDialog();
            return true;
        }
        return super.onKeyUp(keyCode, event);
    }

第九步(此步骤可以跳过):配置权限以便开发中的红屏错误能正确显示

如果您的应用定位到Android API level 23或更高版本,请确保您已overlay为开发版本启用权限。你可以检查它Settings.canDrawOverlays(this);。这在开发版本中是必需的,因为必须在所有其他窗口之上显示原始开发错误。由于在API级别23中引入了新的权限系统,用户需要批准它。这可以通过将以下代码添加到onCreate()方法中的Activity文件中来实现。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);
    }
}

最后,onActivityResult()必须重写该方法(如下面的代码所示)以处理一致UX的权限Accepted或Denied。

@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...
            }
        }
    }
}

最后在明确下index.android.js和mReactRootView.startReactApplication(mReactInstanceManager, "HelloRN", null)和package.json及项目名字,到底那几个保持一致(我也是集成了5次以上,总结出来的,如果有不对请留言指出)

必须一样的:工程名字(HelloRN)和AppRegistry.registerComponent('HelloRN', () => HelloWorldApp);以及mReactRootView.startReactApplication(mReactInstanceManager, "HelloRN", null);三者必须一样。
当你出现以下错误,基本就是这个原因了

image.png

而package.json中的"name": "hellorn",算然给我的感觉应该也和其他一样才对,但是确实可以不一样,而且当你在第一步的时候输入项目名字,如果输了大写,还提示你必须小写。而且我们在初始化的时候"main": "index.js",这一步默认是index.js,从字段来看我们后面创建的index.android.js名字也应该叫“index.js”才对,毕竟package.json类似于我们的配置文件,但是我们没这样做,发现也没有任何问题。

运行应用

  • 首先运行服务
    在项目的根文件夹下,命令行运行如下命令,启动测试服务器(可以直接在Androidstudio的Termin中输入,但是注意当前所在位置)。
npm start
或者:
react-native start
  • 接下来像开发安卓项目一样直接运行项目

效果

效图

当出现这个界面时别提多嗨皮了,哭去了

如果报错
image.png

或是


image.png
解决:在app的build.gradle 中添加
|
|
        ndk {
            abiFilters "armeabi-v7a", "x86"
        }
    }

喜欢请点赞,或是关注,后续将完善发布更多的文章,你的鼓励就是我的动力(程序员最大的动力莫过于同行的鼓励)

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

推荐阅读更多精彩内容