原创,如转发,请注明出处,谢谢。
本文目的:
开发一个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, 一图胜千言:
这是一个非常典型和简单的界面,顶部的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
开发要点
- 建立RN的依赖及目录结构
- 用JS来开发RN组件
- 在你Android 原生app里加ReactRootView控件,这实际上是一个容器,用来容纳RN组件的
- 开启RN服务,运行你的程序(注:在新版RN中,你不需要自己启动服务,直接使用react-native run-android会自动启动,在官方教程里也没说.2018/4/4)
- 校验一下RN的表现是不是如你所愿
更早期的准备工作
原文 http://facebook.github.io/react-native/docs/getting-started.html
这段原文没有描述,是在Getting Started描述的,要点是:安装
- Nodejs 6.x (不要装8.X)
- Python2 至于python3行不行,没有官方说明,感觉肯定是不行的,语法差异大
- Jdk8 这个不用说了, 自己下个Oracle jdk8 (openjdk没试过,不推荐)
- 装react-native-cli
npm install -g react-native-cli - 装Android Studio, sdk等。具体步骤省略。
准备工作
设置目录结构
为了更加平滑的体验,在RN工程根目录下新建一个文件夹,约定好是android, 在里面把工程文件放进去。 其实,是把原来原生开发android的源码,让它成为RN下的一个子目录。当然理论上你也可以另行采用自己的目录结构,以后再试验。安装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
设置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"
}
...
}
...
}设置权限permissions
修改AndroidManifest,保证有互联网权限,及声明facebook的设置activity
代码集成
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 (
);
}
}
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();
}
});
}
}
测试你的集成效果
- 运行你的packager服务
yarn start
这里很多人上当了,根本不需要这一步,否则你可能遭遇磁盘文件读写错误。 - 运行app
原文是“Now build and run your Android app as normal.” 这里的as normal让人很无语,很多人直接在android studio里点了run, 结果失败了。
更正确的姿势是这几步:
- 打包
确保先手工在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"
}
未来继续补充
结束