ReactNative从入门到不放弃 第一季: 和原生混合开发第一步2018.4.4

原创,如转发,请注明出处,谢谢。

本文目的:

开发一个RN和原生Android的混合开发Demo, 描述在其中遇到的搭建环境的关键点。以帮助新入坑同学们不放弃,一起前行。

背景:

React Native的去年的许可证风波已经过去了,但是各种搭建环境,及国内连接外部网络的不确定性(你懂的)仍存在,所以按理想的直接以RN来开发整个Android App及IOS App是有风险,对于企业来说更不允许。

现在是2018年4月份,React Native一直在演进。不少同学都是根据官网http://facebook.github.io/react-native/docs/getting-started.html
及国人做的中文站
https://reactnative.cn/docs/0.51/getting-started.html
作为教材的。 但是实际上,英文的看上去难懂,中文的不是最新的,有的已经过时。

要玩React Native, 你需要有三样东西:毅力,毅力,还是毅力。
因为在这个过程中,遇到的问题有的可以从网上找到,例如StackOverFlow, 有的则找不到,需要你自己灵活运用android原生开发知识,js知识, 脚本知识来分析。

本文仅供学术交流,不用于商业目的。

成果展示

先说说最终成果,免得您要拖到文章底部才能看到。是生成了这样一个Android App, 一图胜千言:

device-2018-04-04-132402.png

这是一个非常典型和简单的界面,顶部的bar是android原生开发的, 下面大片区域是RN开发的。这种应用场景可能是:一个图表,一个快速上线的业务功能。

参考资料

文章原文所有权属于Facebook
http://facebook.github.io/react-native/docs/integration-with-existing-apps.html

工具链

工具链的版本很讲究,建议你和我用一样的,否则不保证是否有各样的错误
python2.7 (python3的path路径已被我手工删除,直接上python2)
node 6.13.1 windows的msi安装包,会自动安装npm。 目前时点,一定不要装node8系列
java 1.8.1_121 仅为测试使用Oracle jdk
Android Studio3.0

开发要点

  1. 建立RN的依赖及目录结构
  2. 用JS来开发RN组件
  3. 在你Android 原生app里加ReactRootView控件,这实际上是一个容器,用来容纳RN组件的
  4. 开启RN服务,运行你的程序(注:在新版RN中,你不需要自己启动服务,直接使用react-native run-android会自动启动,在官方教程里也没说.2018/4/4)
  5. 校验一下RN的表现是不是如你所愿

更早期的准备工作

原文 http://facebook.github.io/react-native/docs/getting-started.html
这段原文没有描述,是在Getting Started描述的,要点是:安装

  1. Nodejs 6.x (不要装8.X)
  2. Python2 至于python3行不行,没有官方说明,感觉肯定是不行的,语法差异大
  3. Jdk8 这个不用说了, 自己下个Oracle jdk8 (openjdk没试过,不推荐)
  4. 装react-native-cli
    npm install -g react-native-cli
  5. 装Android Studio, sdk等。具体步骤省略。

准备工作

  1. 设置目录结构
    为了更加平滑的体验,在RN工程根目录下新建一个文件夹,约定好是android, 在里面把工程文件放进去。 其实,是把原来原生开发android的源码,让它成为RN下的一个子目录。当然理论上你也可以另行采用自己的目录结构,以后再试验。

  2. 安装js依赖
    去改项目根目录下的package.json文件,主要以改以下name, version
    {
    "name": "MyReactNativeApp",
    "version": "0.0.1",
    "private": true,
    "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start"
    }
    }
    保证你安装了yarn, 并使用yarn安装react-native和react@16.2.0
    此时会生成 node_modules目录

在你主app中添加RN

  1. 设置maven
    改app的build.gradle
    dependencies {
    compile 'com.android.support:appcompat-v7:23.0.1'
    ...
    compile "com.facebook.react:react-native:+" // From node_modules
    }
    改根目录的build.gradle
    allprojects {
    repositories {
    maven {
    // All of React Native (JS, Android binaries) is installed from npm
    url "$rootDir/../node_modules/react-native/android"
    }
    ...
    }
    ...
    }

  2. 设置权限permissions
    修改AndroidManifest,保证有互联网权限,及声明facebook的设置activity
    <uses-permission android:name="android.permission.INTERNET" />
    <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

  3. 代码集成
    1)创建一个index.js 文件
    没错,不是原来的app.js文件
    2)修改index.js文件,即添加RN的代码
    示例代码,请照猫画虎的修改
    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);

接下来有若干处修改,都是在Activity里进行的,这里不一一描述,直接show you the code。

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.compat.BuildConfig;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;

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 Activity implements DefaultHardwareBackBtnHandler {
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;
    private final int OVERLAY_PERMISSION_REQ_CODE = 1;  // Choose any value
    private ImageView iv_return_back;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        LinearLayout ll_parent_container;

        mReactRootView = new ReactRootView(this);
        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setBundleAssetName("index.android.bundle")
                //.setJSMainModulePath("index")//这个报错了,注释掉正常了
                .addPackage(new MainReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();
        // The string here (e.g. "MyReactNativeApp") has to match
        // the string in AppRegistry.registerComponent() in index.js
        mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);

        //原来官方的代码,rn的组件充满了全屏
        //setContentView(mReactRootView);

        //换成我们的代码,rn组件只占屏幕的一部分
        setContentView(R.layout.activity_main);
        ll_parent_container = (LinearLayout)findViewById(R.id.ll_parent_container);
        ll_parent_container.addView(mReactRootView);

        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);
            }
        }

        initViews();
    }

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

    @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(this);
        }
        if (mReactRootView != null) {
            mReactRootView.unmountReactApplication();
        }
    }

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

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

    /**
     * init views
     */
    private void initViews(){
        iv_return_back = (ImageView)findViewById(R.id.iv_return_back);
        iv_return_back.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "Quit退出", Toast.LENGTH_SHORT).show();
                MainActivity.this.finish();
            }
        });
    }
}

测试你的集成效果

  1. 运行你的packager服务
    yarn start
    这里很多人上当了,根本不需要这一步,否则你可能遭遇磁盘文件读写错误。
  2. 运行app
    原文是“Now build and run your Android app as normal.” 这里的as normal让人很无语,很多人直接在android studio里点了run, 结果失败了。

更正确的姿势是这几步:

  1. 打包
    确保先手工在android源码目录中建立assets目录(已经有的不需要再建)
    react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/com/hisense/myreactmix/src/main/res/
    里面有2个路径,当然是要按照你的项目实际情况替换一下了,我直接写在bat中了,这样每次运行省事了,命名为runbundle.bat

2)运行:
react-native run-android
也为了省事,存到bat中,命名为runandroid.bat

注意:每次都需要打包再运行,哪怕改一行。

源码下载

伸手党的福音
下载地址:
https://github.com/zhugscn/AwesomeProjectRN

常见错误

Q1. 编译时提示权限错误,不能删除目录
A: 参考以下办法
1)win7或以上,尽可能让Everyone有读写执行等所有权限
2)cmd命令终端使用管理员权限打开
3)使用Android studio进行一次clean

Q2: 无法推送到多台android设备中的某一台
A: RN一般只保证同时连接一台adb时运转良好, 请拔掉别的adb调试线

Q3: 提示打开so库错误,dlopen failed: "xxx/libgnustl_shared.so" is 32-bit instead of 64-bit
A: 改gradle添加以下代码

        //stephen add for rn
        ndk{
            //用来解决这个报错java.lang.UnsatisfiedLinkError: dlopen failed: "xxx/libgnustl_shared.so" is 32-bit instead of 64-bit
            abiFilters "armeabi-v7a","x86"
        }
        packagingOptions {
            //exclude "lib/arm64-v8a/libimagepipeline.so"
        }

未来继续补充

结束

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

推荐阅读更多精彩内容