天天看點

Android原生內建react-native

在原有的 Android 應用中內建 react-native。假設我們現在已經有了一個 Android 原生應用 MyApplication。 主要參考: https://reactnative.cn/docs/0.51/integration-with-existing-apps.html#content https://facebook.github.io/react-native/docs/integration-with-existing-apps.html https://stackoverflow.com/questions/40694285/react-native-expection-java-lang-unsatisfiedlinkerror- dlopen-failed-data-dat?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa https://blog.csdn.net/chichengjunma/article/details/53815299

#第一步:進入到項目更目錄下,建立一個空檔案夾,命名為 android,然後将原本目錄下所有的檔案都移動到這個 android 檔案夾下。 是以現在項目的根目錄就是 MyApplication,更目錄下有一個 android 檔案夾,裡面是原來所有 android 原生應用的檔案。

#第二步:在項目更目錄下建立一個 package.json 檔案用來安裝所需要的 npm 依賴。package.json 檔案内容大概為 { "name": "MyApplication", "version": "0.0.1", "private": true, "scripts": { "start": "node node_modules/react-native/local-cli/cli.js start" }, "dependencies": { "@babel/core": "^7.0.0-beta.42", "babel-core": "^7.0.0-bridge.0", "react": "16.3.0-alpha.1", "react-native": "^0.54.2" } } 需要注意的是這裡的 name 一定要與項目的名稱保持一樣。 react 和 react-native 以及其它庫的版本根據目前最新版本适當修改即可。 然後在更目錄下執行 npm install 即可。

#第三部:用 android studio 打開 android 下面的原生應用。在 app 的 build.gradle 檔案中的 dependencies 閉包下加上一句 compile "com.facebook.react:react-native:+" 官網上是這麼說的,同時官網上還要求了 compile 'com.android.support:appcompat-v7:23.0.1' 但是實際應用中發現一方面并不需要一定是 v7:23.0.1,而且更重要的是 compile 方式即将不支援了。換用 implementation。 而且建立 android 原生應用的時候會預設使用最新的編譯庫(目前是27)是以要是按照官網上的話,不僅需要修改便宜版本為23, 同時使用 compile。這樣會導緻很多問題。是以直接按照下面的配置就可以了: //app: build.gradle dependencies { implementation 'com.android.support:appcompat-v7:27.1.1' …… …… implementation "com.facebook.react:react-native:+" } 這樣就不會有問題了。接着修改 project 的 build.gradle 檔案,在 allprojects 閉包中的 repositories 閉包下添加 maven 依賴, //project: build.gradle allprojects { repositories { maven { // All of React Native (JS, Android binaries) is installed from npm url "$rootDir/../node_modules/react-native/android" } ... } ... } 這兩部都修改完之後點選提示的 Sync Now. 建構完成之後應該不會出現錯誤。

#第四步:在 AndroidManifest.xml 中添加網絡權限: <uses-permission android:name="android.permission.INTERNET" /> 當然如果是在 Debug 模式下,友善調試還可以在<Application> </ Application >内加上: < activity android:name= "com.facebook.react.devsupport.DevSettingsActivity" /> 添加完之後大概為: < manifest ………… > < uses-permission android:name= "android.permission.INTERNET" /> < application ………… > …… …… < activity android:name= "com.facebook.react.devsupport.DevSettingsActivity" /> </ application > </ manifest >

#第五步:建立 App.js 和 index.js 檔案,就像原本的 react-native 項目中那樣,隻是要記住在 index.js 檔案中注冊的元件名稱比如 AppRegistry.registerComponent('MyApplication', () => App); 這裡的 MyApplication

#第六步:因為在 Debug 模式下的錯誤資訊都是顯示在懸浮窗中的,是以如果是在 android 6.0 或者以上的系統中, 我們需要動态的申請這個權限(當然,如果是在 Release 模式下并不需要這個權限)。權限在希望啟動 react-native 界面的活動下申請, 比如這裡我們希望在 Main2Activity 這個活動中啟動 private final int OVERLAY_PERMISSION_REQ_CODE = 1; @Override protected void onCreate(Bundle savedInstanceState) { …… …… 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); } } } 接着重寫活動的 onActivityResult() 方法以處理使用者對于上述權限的處理 @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 } } } }

#第七部:将 react-native 元件加進來,在我們希望啟動 react-native 的活動(這裡的 Main2Activity)類實作 DefaultHardwareBackBtnHandler 接口,然後通過 ReactRootView 這個類将 react-native 元件加進來 public class Main2Activity extends AppCompatActivity 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") .setJSMainModulePath("index") .addPackage(new MainReactPackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); // 注意這裡的名稱(MyApplication)必須與 index.js 檔案中注冊的名稱一樣 mReactRootView.startReactApplication(mReactInstanceManager, "MyApplication", null);

setContentView(mReactRootView); }

@Override public void invokeDefaultOnBackPressed() { super.onBackPressed(); } } 這裡可以看到,通過 setContentView(mReactRootView) 方法将 react-native 的視圖作為了這個活動的視圖了。 做完這一步之後還需要注意,因為 react-native 界面頂部是沒有 android 自帶的 actionBar 的,是以我們需要把這個活動下視圖的 actionBar 去掉,隻要在 AndroidManifest.xml 檔案下對應的活動下修改視圖的主題就可以了 < activity android:name= ".Main2Activity" android:theme= "@style/Theme.AppCompat.Light.NoActionBar" > </ activity >

#第八步:将活動的生命周期傳遞給 ReactInstanceManager: @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(); } } 同時在這個活動下,把後退按鈕傳遞給 react-native: @Override public void onBackPressed() { if (mReactInstanceManager != null) { mReactInstanceManager.onBackPressed(); } else { super.onBackPressed(); } } 另外,如果是在模拟器環境下,Ctrl + M 按鍵調出調試面闆是非常有用的,是以我們也可以重寫這個按鈕的事件 @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { mReactInstanceManager.showDevOptionsDialog(); return true; } return super.onKeyUp(keyCode, event); }

#第九步:到此,內建已經完畢了,可以運作程式了。但是直接 react-native run-android 一般會有問題,不能正确的打開本地伺服器。 一般用 android studio 将程式安裝到手機或者模拟器上之後再通過 npm start 手動打開本地伺服器.

#最後還有一個問題,如果是運作在 64 位的手機或者模拟器上,程式會失敗,提示錯誤資訊為 dlopen failed: "/data/data/package/lib-main/libgnustl_shared.so" is 32-bit instead of 64-bit 這個好像是因為 react-native 要求的 .so 檔案不支援 64 位的手機,解決辦法就是 在 app:build.gradle 檔案中在加上兩個閉包 defaultConfig { …… …… ndk { abiFilters "armeabi-v7a", "x86" } packagingOptions { exclude "lib/arm64-v8a/libgnustl_shared.so" } } 好了,到此就成功将 react-native 內建到 Android 原生應用中啦!!! 下面是完整的 Main2Activity 活動的檔案内容: =========================================================================================================

import android.content.Intent; import android.net.Uri; import android.os.Build; import android.provider.Settings; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.KeyEvent;

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 Main2Activity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {

private final int OVERLAY_PERMISSION_REQ_CODE = 1; private ReactRootView mReactRootView; private ReactInstanceManager mReactInstanceManager;

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

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); } }

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(); mReactRootView.startReactApplication(mReactInstanceManager, "MyApplication", null); setContentView(mReactRootView);

}

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

@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)) { // 如果懸浮窗權限申請失敗的話…… } } } }

@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 void onBackPressed() { if (mReactInstanceManager != null) { mReactInstanceManager.onBackPressed(); } else { super.onBackPressed(); } }

@Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { mReactInstanceManager.showDevOptionsDialog(); return true; } return super.onKeyUp(keyCode, event); } }

=================================================================================================== react-native android 打包指令: 事先在 ./android/app/src/main/ 下要有一個 assets 檔案夾,沒有的話就手動建立一個即可 (附帶,引用這個檔案夾下的資源可通過 file:///android_asset/xxx) react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/com/your-company-name/app-package-name/src/main/assets/index.android.bundle --assets-dest android/com/your-company-name/app-package-name/src/main/res/ 例如這裡就用 react-native bundle --platform android --dev false --entry-file index.js --bundle-output ./android/app/src/main/assets/index.android.bundle --assets-dest ./android/app/src/main/res/