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, 一图胜千言:

ReactNative从入门到不放弃 第一季: 和原生混合开发第一步2018.4.4_第1张图片
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和[email protected]
    此时会生成 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

  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 (

Hello, World

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

未来继续补充

结束

你可能感兴趣的:(ReactNative从入门到不放弃 第一季: 和原生混合开发第一步2018.4.4)