前言
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.
建立工程很容易就完成了,現在我們加一個跳轉按鈕不過目前還不能跳轉。最終展示界面如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICO5MTMwETN0EDMyATM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
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檔案,輸入指令後界面輸出如下内容:
這裡需要輸入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,不過展示頁面出現了錯誤,頁面如下:
其實這個不算錯誤,是因為剛才我們寫Activity是沒有是拷貝的,沒有改動,這裡需要改動如下:
注意:package.json, index.android.js, activity這三處名稱需要一緻
我們修改後再一次運作,到此我們就成功了運作了界面。
這樣我們就成功的運作了。這裡我們将Hello World改變一下,改成其他的内容,比如Native Hello World,之後搖動手機點reload,可以發現界面已經變化了。
附錄
這裡還有一些其他情況,我們分别來說一下:
彈窗不顯示
有的人搖動手機後,不能彈窗,可能是系統禁止了彈窗,需要手動開啟
網絡連接配接失敗
可能是情況是手機與電腦用的網絡不是同一個,需要設定為統一個網絡
Dev Setting
點選dev Setting崩潰,這裡需要在AndroidManifest中配置如下節點:
activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
總結
到這裡,我們已經成功的将React Native內建進已有的項目,中途會碰到這樣那樣的坑,這裡遇到的坑,已經很全了,希望大家工具探讨,學習。