我们先来回答一个问题:什么是棕地应用?
所谓 "棕地",是指利用现有元素建造的东西。棕地 "一词最初用于城市规划,描述可以重新利用的区域,后来被移动应用开发所采用。棕地应用程序基本上是指并非从零开始构建的应用程序。
它的实际含义是什么?这意味着我们希望将 React Native 应用程序作为已使用 Java 或 Kotlin 等技术开发的本地应用程序的一部分来运行。换句话说,我们希望将 React Native 集成到现有的应用程序中。
它是如何工作的?
假设我们有一个原生 Android 应用程序,我们想使用已经实现的 React Native 应用程序创建一个新的屏幕。该屏幕将导航到 React Native 应用程序中的另一个屏幕。
为了让它正常工作,我们需要做几件事。
在项目中添加 React Native:
- 首先,我们需要在项目的 build.gradle 中为本地 React Native maven 目录添加一个条目。 让我们把它添加到 allprojects 块,位于其他 maven 仓库之上
allprojects {
repositories {
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
}
}
- 将 node_modules 中的 react-native 添加为应用程序 build.gradle 的依赖项(感谢上一点的修改)
dependencies {
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation "com.facebook.react:react-native:+"
}
- 在应用程序的 build.gradle 末尾添加自动链接命令
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
applyNativeModulesAppBuildGradle(project, "../")
运行 React Native 应用程序:
为了运行 React Native 应用程序,我们需要
- 创建一个实现 DefaultHardwareBackBtnHandler 接口的 Activity,并使用 Theme.AppCompat.Light.NoActionBar 主题,因为某些 React Native UI 组件依赖于它。
public class ReactNativeActivity extends Activity implements DefaultHardwareBackBtnHandler {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
```
```xml
<activity
android:name=".ReactNativeActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
/>
- 在 onCreate 方法中创建 ReactRootView 实例
mReactRootView = new ReactRootView(this);
- 使用所需参数创建 ReactInstanceManager 实例
mReactInstanceManager = ReactInstanceManager.builder()
.setCurrentActivity(this)
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.build();
- 使用 startReactApplication 方法在创建的根视图中启动 react-native 应用程序,其中第二个参数与 index.js 文件中的 AppRegistry.registerComponent() 中的字符串相同。
mReactRootView.startReactApplication(mReactInstanceManager, "MainComponent", null);
- 将 react-native 根视图设置为活动内容视图
setContentView(mReactRootView);
现在,我们可以通过启动 ReactNativeActivity 来运行我们的 react-native 应用程序,但我们还需要添加一些代码才能让所有功能正常运行:)
- 将活动生命周期回调传递给 ReactInstanceManager 和 ReactRootView
@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();
}
}
- 传递返回按钮事件,以避免在按下硬件返回按钮时关闭 ReactNativeActivity,并在 JS 端支持硬件返回按钮
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
注:即使做了这样的改动,我们也没有一种无需使用硬件返回按钮即可返回原生屏幕的机制。这个问题可以通过 react-native 原生模块来解决,该模块会将方法暴露给 JS,从而完成 ReactNativeActivity。
其他要点:
- 在调试模式下,我们需要打包程序来提供 JS 捆绑包。要做到这一点,我们需要互联网权限。在 AndroidManifest.xml 文件中添加以下一行。
<uses-permission android:name="android.permission.INTERNET" />
注意:从 Android 9(API 级别 28)开始,默认情况下禁用明文流量。这将阻止您的应用程序连接到 React Native 打包器。要解决这个问题,我们需要在调试 AndroidManifest.xml 中添加 usesCleartextTraffic 选项。
<application
android:usesCleartextTraffic="true"
tools:targetApi="28"
/>
- 要在 react-native 应用程序中启用开发菜单,请在 AndroidManifest.xml 中添加 DevSettingsActivity
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
只有在调试构建时,我们才会从开发服务器重新加载 JS 代码,因此在发布构建时可以删除所有这些附加点。
我认为,几乎所有的 react-native Android 应用都会重复上述步骤。但就不能做得更简单一些吗?我们真的需要关心 onPause、onDestroy 等函数的传递吗?
当然,这可能更简单:) 我们可以使用 react-native-brownfield 软件包,它可以帮助我们解决导航问题,并提供运行 react-native 应用程序的良好 API。
如何使用 React Native brownfield?
我们想要实现与之前完全相同的效果,但我们将使用 react-native-brownfield 库
在项目中添加 React Native:
理论与我在本文前半部分所述的相同,但增加了一些功能。为了使用 react-native-brownfield 在原生环境中运行 react-native 应用程序,我们需要将该软件包作为依赖项添加到 package.json 中:
npm install @callstack/react-native-brownfield
或
yarn add @callstack/react-native-brownfield
接下来,我们需要添加 react-native 应用程序,就像在添加 react-native 到项目点时所做的那样。
其他步骤:
这里也需要添加所有附加步骤,但仅用于调试构建。
运行 React Native 应用程序:
现在好戏开始了) 我们可以使用 ReactNativeBrownfield 软件包,而无需自己创建根视图和实例管理器。由于 build.gradle 和 react-native-brownfield 已添加了自动链接脚本,因此我们可以使用该软件包,而无需付出额外的努力。
- 在 AndroidManifest.xml 文件中添加 ReactNativeActivity
<activity android:name="com.callstack.reactnativebrownfield.ReactNativeActivity"/>
- 以上下文作为第一个参数初始化 react-native 应用程序(如下代码段所示)
List<ReactPackage> packages = new PackageList(getApplication()).getPackages();
HashMap<String, Object> options = new HashMap<>();
options.put("packages", packages);
options.put("mainModuleName", "example/index");
ReactNativeBrownfield.initialize(this, options);
- 启动 react-native 应用程序
ReactNativeBrownfield.getShared().startReactNative(init -> {
Log.d("RN", "react-native app has started :)");
});
- 以 context 和 moduleName 为参数创建 ReactNativeActivity Intent,并启动活动。
Intent intent = ReactNativeActivity.createReactActivityIntent(
this,
"MainComponent"
);
startActivity(intent);
瞧! 我们已经准备就绪) 我们不必担心传递活动生命周期、返回按钮事件或从 react-native 返回到原生屏幕的原生模块。所有这些都由 react-native-brownfield 软件包处理。
结论:
最后,在现有的原生应用中使用 react-native 是一件很流行的事情,尤其是那些有多个团队在多个平台上开发同一应用的大型企业。 有了 react-native-brownfield,集成变得更容易、更愉快,而且每个 react-native-brownfield 应用程序中都存在的一些问题也迎刃而解:)
使用 React Native brownfield 的工作示例位于示例目录中。
作为 React Native Show 的一部分,我们发布了一个播客,专门介绍使用 React Native 进行棕地开发的未来。