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

未來繼續補充

結束

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容