原創,如轉發,請注明出處,謝謝。
本文目的:
開發一個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和react@16.2.0
此時會生成 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
<uses-permission android:name="android.permission.INTERNET" />
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />代碼集成
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();
}
});
}
}
測試你的集成效果
- 運行你的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"
}
未來繼續補充
結束