天天看點

React Native 內建到已有項目

前言

       React Native已經出現很久了,有很多應用也在進行嘗試,前面我們也講述了怎麼建立React Native工程以及怎麼搭建原生語言與js的開發環境。

       但是在實際應用中,很多項目都不是從零開始的,而是在已有項目中進行嘗試,這就需要将React Native內建到已有項目,這裡我們就來講講怎麼內建到已有項目。

需求

       這裡我們會用Android Studio建立一個工程,改工程包含有一個首頁面,裡面裡有一個跳轉按鈕,能夠跳轉到用React Native實作的頁面。

建立Android工程

       這裡由于React Native是從Android 4.1開始支援的,是以我們直接建立最小版本号為16的工程。

Supported operating systems are >= Android 4.1 (API 16) and >= iOS 7.0.

       建立工程很容易就完成了,現在我們加一個跳轉按鈕不過目前還不能跳轉。最終展示界面如下:

React Native 內建到已有項目

       Activity中處理代碼如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setViewListener();
    }

    private void setViewListener() {
        findViewById(R.id.to_react).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "go  to  React", Toast.LENGTH_LONG).show();
            }
        });
    }
}
           

       目前這裡彈出Toast 提示,之後改為跳轉到React Native界面。

內建

       這裡我們安裝官方指定的步驟來進行內建,官方內建的連結為:檢視連結,在內建之前希望你已經配置好了React Native 環境,比如Node.js, watchman等。下面我們來分步驟內建。

初始化

       首先在指令行進入到你所建立的工程下,第一步輸入如下指令:

npm init

       這一步主要在工程下建立package.json檔案,輸入指令後界面輸出如下内容:

React Native 內建到已有項目

       這裡需要輸入name,這裡name就是JS中AppRegistry.registerComponent(‘RNDemo’, () => xxxx)中的RNDemo,也就是項目名。不過這裡目前隻能輸入小寫。之後會輸入version等資訊,可以跳過某些内容直接回車。需要輸入的内容展現如下:

Press ^C at any time to quit.
name: (RNDemo1) rndeme1
version: () 
description: deme
entry point: (index.js) index.android.js
test command: test
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /Users/doc/ReactNative/RNDemo1/package.json:

{
  "name": "rndeme1",
  "version": "1.0.0",
  "description": "deme",
  "main": "index.android.js",
  "scripts": {
    "test": "test"
  },
  "author": "",
  "license": "ISC"
}

Is this ok? (yes) 
           

       這裡輸入yes就好了。最終這些内容都會生成在package.json中。我們打開package.json看看最終生成的内容:

{
  "name": "rndeme1",
  "version": "1.0.0",
  "description": "deme",
  "main": "index.android.js",
  "scripts": {
    "test": "test"
  },
  "author": "",
  "license": "ISC"
}
           

       這裡可以看到與我們上面輸入的内容一緻,隻不過name這個單詞拼寫錯了。。。

建立node module

       在上面的指令執行完成後,輸入如下指令:

npm install –save react react-native // save 前面是兩個橫線

       等待一段時間,我們可以在項目的根目錄發現生成了一個node module的檔案夾,這一步也可以省略,直接從已有項目中拷貝過來。

建立.flowconfig

       flowconfig是給flow用的,flow的作用前面已經講過了,主要用來做靜态代碼檢查。我們可以輸入如下指令在生成.flowconfig檔案。

curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig

       運作完成後,你可以在檔案夾下看到建立了一個.flowconfig,不過這個檔案是影藏的,可以Mac下你可以ls -al來檢視。

修改package.json

       上面的檔案建立完成後,我們需要修改package.json 檔案,在scripts節點下添加如下的語句:

“start”: “node node_modules/react-native/local-cli/cli.js start”

       最終修改後的package.json如下:

{
  "name": "rndeme1",
  "version": "1.0.0",
  "description": "deme",
  "main": "index.android.js",
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "test"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^15.3.2",
    "react-native": "^0.35.0"
  }
}
           

       可以看到在建立node module時,已經修改了json檔案,添加了react的依賴。

建立index.androd.js

       在工程的跟目錄下建立index.android.js,代碼如下:

'use strict';

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: ,
    justifyContent: 'center',
  },
  hello: {
    fontSize: ,
    textAlign: 'center',
    margin: ,
  },
});

AppRegistry.registerComponent('rndeme1', () => HelloWorld);
           

       這代碼直接從官方拷貝的,隻需要主要registerComponent的第一個參數,改為我們上面輸入的内容。

修改gradle

       因為我們将react內建到已有項目,而android項目是靠gradle來進行建構編譯的,是以這裡我們需要對應修改相應的内容。

       1:添加react-native依賴

dependencies {
    ...
    compile "com.facebook.react:react-native:+" // From node_modules.
}
           

       這裡需要注意的是,這裡修改是app目錄下的build.gradle檔案

       2:添加maven

allprojects {
    repositories {
        ...
        maven {
            // All of React Native (JS, Android binaries) is installed from npm
            url "$rootDir/../node_modules/react-native/android"
        }
    }
    ...
}
           

       這裡修改的是跟目錄下的build.gradle,放置在allprojects節點下。

添權重限

       React Native是需要網絡權限的,因為他是從遠端伺服器拉取的jsBundle。是以我們在AndroidManifest.xml下添加網絡權限。

添加native code

       前面我們已經建立好js檔案了,也配置好了其他的一些内容,這裡我們需要添加一個activity來展示React Native,這裡我們建立一個MyReactActivity的頁面。代碼從網絡拷貝:

public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

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

        mReactRootView = new ReactRootView(this);
        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setBundleAssetName("index.android.bundle")
                .setJSMainModuleName("index.android")
                .addPackage(new MainReactPackage())
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                //.setUseOldBridge(true) // uncomment this line if your app crashes
                .build();
        mReactRootView.startReactApplication(mReactInstanceManager, "HelloWorld", null);

        setContentView(mReactRootView);
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }
}
           

       我們這裡就不在處理其他比如onResume等回調了。手動将所有的import導入,最後對該Activity設定theme。設定的主題為:android:theme=”@style/Theme.AppCompat.Light.NoActionBar”,因為有些元件是依賴這個主題的。

運作

       到這一步我們按照官方的内容就算已經內建完成了,是不是可以開始運作了,我們來試着運作一下,首先啟動server,輸入如下指令:

npm start
[email protected] start /Users/doc/ReactNative/RNDemo1
> node node_modules/react-native/local-cli/cli.js start

Scanning  folders for symlinks in /Users/doc/ReactNative/RNDemo1/node_modules (ms)
 ┌────────────────────────────────────────────────────────────────────────────┐ 
 │  Running packager on port                                             │ 
 │                                                                            │ 
 │  Keep this packager running while developing on any JS projects. Feel      │ 
 │  free to close this tab and run your own packager instance if you          │ 
 │  prefer.                                                                   │ 
 │                                                                            │ 
 │  https://github.com/facebook/react-native                                  │ 
 │                                                                            │ 
 └────────────────────────────────────────────────────────────────────────────┘ 
Looking for JS files in
   /Users/doc/ReactNative/RNDemo1 

[-- ::] <START> Building Dependency Graph
[-- ::] <START> Crawling File System
[Hot Module Replacement] Server listening on /hot

React packager ready.

[-- ::] <END>   Crawling File System (ms)
[-- ::] <START> Building in-memory fs for JavaScript
[-- ::] <END>   Building in-memory fs for JavaScript (ms)
[-- ::] <START> Building in-memory fs for Assets
[-- ::] <END>   Building in-memory fs for Assets (ms)
[-- ::] <START> Building Haste Map
[-- ::] <START> Building (deprecated) Asset Map
[-- ::] <END>   Building (deprecated) Asset Map (ms)
[-- ::] <END>   Building Haste Map (ms)
[-- ::] <END>   Building Dependency Graph (ms)
           

       輸出如下内容,表示啟動成功了。我們是不是就可以運作了,首先我們用react-native run android來運作,這裡需要重新打一個指令視窗。運作指令後直接輸出了:

Android project not found. Maybe run react-native android first?
           

       這裡是因為預設建立是有三年級目錄的,外層目錄,之後Android目錄,之後才是代碼,那我們采用Andriod Studio來運作。不過我們還需要改一個地方,就是之前我們的點選事件還是彈出一個Toast,我們需要改成打開新的Activity,打開代碼如下:

Intent intent = new Intent();
intent.setClass(MainActivity.this, MyReactActivity.class);
startActivity(intent);
           

       我們再一次運作,成功了? no,成的失敗了!~

填坑

       上面我們已經運作了該工程,不過不出所料,成功的失敗了,之後還連續出現了一系列的失敗,這裡就一個一個的解決:

java.lang.UnsatisfiedLinkError

       運作後出現了如下的異常:

FATAL EXCEPTION: AsyncTask #1
Process: im.yixin.rndemo2, PID: 
java.lang.RuntimeException: An error occured while executing doInBackground()
  at android.os.AsyncTask$3.done(AsyncTask.java:)
  at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:)
  at java.util.concurrent.FutureTask.setException(FutureTask.java:)
  at java.util.concurrent.FutureTask.run(FutureTask.java:)
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:)
  at java.lang.Thread.run(Thread.java:)
Caused by: java.lang.UnsatisfiedLinkError: could find DSO to load: libreactnativejni.so
  at com.facebook.soloader.SoLoader.loadLibraryBySoName(SoLoader.java:)
  at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:)
  at com.facebook.react.bridge.JSCJavaScriptExecutor.<clinit>(JSCJavaScriptExecutor.java:)
  at com.facebook.react.bridge.JSCJavaScriptExecutor$Factory.create(JSCJavaScriptExecutor.java:)
  at com.facebook.react.ReactInstanceManagerImpl$ReactContextInitAsyncTask.doInBackground(ReactInstanceManagerImpl.java:)
  at com.facebook.react.ReactInstanceManagerImpl$ReactContextInitAsyncTask.doInBackground(ReactInstanceManagerImpl.java:)
  at android.os.AsyncTask$2.call(AsyncTask.java:)
  at java.util.concurrent.FutureTask.run(FutureTask.java:)
  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:) 
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:) 
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:) 
  at java.lang.Thread.run(Thread.java:) 
           

       從日志可以看出是一個so連結錯誤,這種錯誤網絡是對應的abi不正确。

       解決方案:修改app裡的build.gradle,defaultConfig節點下添加ndk節點:

ndk {
    abiFilters "armeabi-v7a", "x86"
}
           

       來我們再一次運作:

NDK integration is deprecated in the current plugin.

       運作後發現NDK重複內建了,錯誤日志如下:

Error:(, ) NDK integration is deprecated in the current plugin.
<a href="http://tools.android.com/tech-docs/new-build-system/gradle-experimental">Consider trying the new experimental plugin</a><br><a href="useDeprecatedNdk">Set "android.useDeprecatedNdk=true" in gradle.properties to continue using the current NDK integration</a>
           

       解決方案:方案1,可以在gradle.properties下添加android.useDeprecatedNdk=true,方案2,降低gradle的版本,我們任意選擇一種方式都可以,之後在一次運作:

java.lang.IllegalAccessError,Method ‘void android.support.v4.net.ConnectivityManagerCompat

       我們繼續掙紮在沒有成功的道路上,錯誤如下:

FATAL EXCEPTION: AsyncTask #1
Process: im.yixin.rndemo2, PID: 
java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$done(AsyncTask.java:)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:)
at java.util.concurrent.FutureTask.setException(FutureTask.java:)
at java.util.concurrent.FutureTask.run(FutureTask.java:)
at android.os.AsyncTask$SerialExecutor$run(AsyncTask.java:)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:)
at java.lang.Thread.run(Thread.java:)
Caused by: java.lang.IllegalAccessError: Method 'void android.support.v4.net.ConnectivityManagerCompat.<init>()' is inaccessible to class 'com.facebook.react.modules.netinfo.NetInfoModule' (declaration of 'com.facebook.react.modules.netinfo.NetInfoModule' appears in /data/data/im.yixin.rndemo2/files/instant-run/dex/slice-com.facebook.react-react-native-_76f14c344d869afc092625e7670a68a34348b199-classes.dex)
at com.facebook.react.modules.netinfo.NetInfoModule.<init>(NetInfoModule.java:)
at com.facebook.react.shell.MainReactPackage.createNativeModules(MainReactPackage.java:)
at com.facebook.react.ReactInstanceManagerImpl.processPackage(ReactInstanceManagerImpl.java:)
at com.facebook.react.ReactInstanceManagerImpl.createReactContext(ReactInstanceManagerImpl.java:)
at com.facebook.react.ReactInstanceManagerImpl.access$(ReactInstanceManagerImpl.java:)
at com.facebook.react.ReactInstanceManagerImpl$ReactContextInitAsyncTask.doInBackground(ReactInstanceManagerImpl.java:)
at com.facebook.react.ReactInstanceManagerImpl$ReactContextInitAsyncTask.doInBackground(ReactInstanceManagerImpl.java:)
at android.os.AsyncTask$call(AsyncTask.java:)
at java.util.concurrent.FutureTask.run(FutureTask.java:)
at android.os.AsyncTask$SerialExecutor$run(AsyncTask.java:) 
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:) 
at java.lang.Thread.run(Thread.java:)            
           

       從日志可以看出是ConnectivityManagerCompat的實作擷取錯誤。

       解決方案:這裡我們主要修改如下的代碼,隻代碼查找的實作錯誤,之前我們添加了maven,這裡我将:

"$rootDir/.../node_modules/react-native/android" 改成如下
 "$rootDir/node_modules/react-native/android"
           

       改了之後,需要重新import某些路徑,因為加載的路徑變化了。在一起運作:

Application HelloWorld has not been registered

       運作後,沒有錯誤log,不過展示頁面出現了錯誤,頁面如下:

React Native 內建到已有項目

       其實這個不算錯誤,是因為剛才我們寫Activity是沒有是拷貝的,沒有改動,這裡需要改動如下:

注意:package.json, index.android.js, activity這三處名稱需要一緻

       我們修改後再一次運作,到此我們就成功了運作了界面。

React Native 內建到已有項目

       這樣我們就成功的運作了。這裡我們将Hello World改變一下,改成其他的内容,比如Native Hello World,之後搖動手機點reload,可以發現界面已經變化了。

React Native 內建到已有項目

附錄

       這裡還有一些其他情況,我們分别來說一下:

彈窗不顯示

       有的人搖動手機後,不能彈窗,可能是系統禁止了彈窗,需要手動開啟

網絡連接配接失敗

       可能是情況是手機與電腦用的網絡不是同一個,需要設定為統一個網絡

Dev Setting

       點選dev Setting崩潰,這裡需要在AndroidManifest中配置如下節點:

activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
           

總結

       到這裡,我們已經成功的将React Native內建進已有的項目,中途會碰到這樣那樣的坑,這裡遇到的坑,已經很全了,希望大家工具探讨,學習。