为原生项目添加 RN 模块

你的大部分业务已经用原生代码实现,你想添加一些 RN 业务模块。

官方文档Integration with Existing Appsopen in new window,有比较详细的介绍,本文讲述的过程和官方文档大同小异。

为了确保流畅的体验,使用如下目录结构:

MyApp
├─ android/
├─ ios/
├─ node_modules/
├─ package.json

创建 RN 项目

运行如下命令,创建一个 RN 项目:

npx react-native init <AppName>

也可以使用 npx react-native-create-app <AppName> 命令来创建

创建成功后,打开该目录,删除里面的 andriod 和 ios 文件夹

cd 到 RN 项目,执行如下命令添加依赖:

yarn add hybrid-navigation

RN 项目配置

打开 index.js 这个文件,通常,它就在 package.json 旁边。

你需要注册你的 React 组件

以前,你是这么注册的

AppRegistry.registerComponent('ReactNativeProject', () => App)

现在,你需要作出改变

import { ReactRegistry, Garden, BarStyleDarkContent } from 'hybrid-navigation'
import Home from './HomeComponent'
import Profile from './ProfileComponent'

// 配置全局样式
Garden.setStyle({
  topBarStyle: BarStyleDarkContent,
})

ReactRegistry.startRegisterComponent()

// 注意,你的每一个页面都需要注册
ReactRegistry.registerComponent('Home', () => Home)
ReactRegistry.registerComponent('Profile', () => Profile)

ReactRegistry.endRegisterComponent()

Android 项目配置

首先,将现有 Android 项目拷贝到 RN 项目的 android 文件夹下。结构如下:

MyApp
├─ android/
│    ├─ app/
│    ├─ build.gradle
│    └─ settings.gradle
├─ ios/
├─ node_modules/
├─ package.json

在 settings.gradle 中添加如下配置

rootProject.name = 'MyApp'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle")
applyNativeModulesSettingsGradle(settings)
include ':app'

在根项目的 build.gradle 文件中,确保以下配置或变更

  ext {
+     minSdkVersion = 21
+     targetSdkVersion = 30
+     compileSdkVersion = 30
+     buildToolsVersion = '30.0.2'
  }

  buildscript {
      repositories {
+         google()
+         mavenCentral()
      }
      dependencies {
-         classpath 'com.android.tools.build:gradle:2.2.3'
+         classpath 'com.android.tools.build:gradle:4.2.2'
      }
  }

  allprojects {
      repositories {
+         maven {
+             // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
+             url("$rootDir/../node_modules/react-native/android")
+         }
+         maven {
+             // Android JSC is installed from npm
+             url("$rootDir/../node_modules/jsc-android/dist")
+         }
+         mavenCentral {
+             // We don't want to fetch react-native from Maven Central as there are
+             // older versions over there.
+             content {
+                 excludeGroup "com.facebook.react"
+             }
+         }
+         google()
+         maven { url 'https://www.jitpack.io' }
      }
  }

在 app/build.gradle 文件中,作如下变更

+ project.ext.react = [
+     entryFile: "index.js",
+     enableHermes: false,
+ ]

+ apply from: "../../node_modules/react-native/react.gradle"

+ def jscFlavor = 'org.webkit:android-jsc:+'
+ def enableHermes = project.ext.react.get("enableHermes", false);

  android {
+     compileSdkVersion rootProject.ext.compileSdkVersion
+     buildToolsVersion rootProject.ext.buildToolsVersion

      defaultConfig {
+         minSdkVersion rootProject.ext.minSdkVersion
+         targetSdkVersion rootProject.ext.targetSdkVersion
      }
  }

  dependencies {
+     implementation fileTree(include: ['*.jar'], dir: 'libs')

+     implementation project(':hybrid-navigation')
+     implementation "com.facebook.react:react-native:+" // From node_modules
+     implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
+     if (enableHermes) {
+		  def hermesPath = "../../node_modules/hermes-engine/android/";
+		  debugImplementation files(hermesPath + "hermes-debug.aar")
+		  releaseImplementation files(hermesPath + "hermes-release.aar")
+	  } else {
+		  implementation jscFlavor
+	  }
  }
+ apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle")
+ applyNativeModulesAppBuildGradle(project)

在 android/gradle/wrapper/gradle-wrapper.properties 文件中,确保你使用了正确的 gradle wrapper 版本。

- distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+ distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip

修改 MainApplication.java 文件。在你的项目中,可能叫其它名字。

public class MainApplication extends Application implements ReactApplication {

	private final ReactNativeHost mReactNativeHost =
		new ReactNativeHost(this) {
			@Override
			public boolean getUseDeveloperSupport() {
				return BuildConfig.DEBUG;
			}

			@Override
			protected List<ReactPackage> getPackages() {
				@SuppressWarnings("UnnecessaryLocalVariable")
				List<ReactPackage> packages = new PackageList(this).getPackages();
				return packages;
			}

			@Override
			protected String getJSMainModuleName() {
				return "index";
			}
		};

	@Override
	public ReactNativeHost getReactNativeHost() {
		return mReactNativeHost;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		SoLoader.init(this, /* native exopackage */ false);
		ReactBridgeManager bridgeManager = ReactBridgeManager.get();
		bridgeManager.install(getReactNativeHost());
		FLog.setMinimumLoggingLevel(FLog.INFO);
	}
}

创建 ReactEntryActivity,继承 ReactAppCompatActivity

可以叫其它名字

import com.reactnative.hybridnavigation.ReactAppCompatActivity;

public class ReactEntryActivity extends ReactAppCompatActivity {
    @Override
    protected String getMainComponentName() {
        return "Home";
    }
}

如果希望 UI 层级由原生这边决定,则需要实现 onCreateMainComponent 方法:

@Override
protected void onCreateMainComponent() {
    // 注意不要调用下面这行代码
    // super.onCreateMainComponent();
    ReactBridgeManager bridgeManager = getReactBridgeManager();

    ReactStackFragment navigation = new ReactStackFragment();
    navigation.setRootFragment(bridgeManager.createFragment("Navigation"));
    ReactStackFragment options = new ReactStackFragment();
    options.setRootFragment(bridgeManager.createFragment("Options"));

    ReactTabBarFragment tabBarFragment = new ReactTabBarFragment();
    tabBarFragment.setChildFragments(navigation, options);

    setActivityRootFragment(tabBarFragment);
}

ReactEntryActivity 添加 NoActionBar 主题

<activity
  android:name=".ReactEntryActivity"
  android:theme="@style/Theme.AppCompat.NoActionBar"
/>

在 AndroidManifest.xml 中添加如下权限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

<application
    android:usesCleartextTraffic="true"
    tools:targetApi="28"
    tools:ignore="GoogleAppIndexingWarning">
    <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>

iOS 项目配置

首先,将现有 iOS 项目拷贝到 RN 项目的 ios 文件夹下。结构如下:

MyApp
├─ android/
├─ ios/
│   ├─ Podfile
│   ├─ *.xcodeproj/
│   └─ *.xcworkspace/
├─ node_modules/
├─ package.json

假设你使用 cocopods 来管理依赖,在 Podfile 文件中添加如下设置

platform :ios, '11.0'
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

target 'MyApp' do
    config = use_native_modules!
    use_react_native!(
      :path => config[:reactNativePath],
      # to enable hermes on iOS, change `false` to `true` and then install pods
      :hermes_enabled => false
    )
end

post_install do |installer|
    react_native_post_install(installer)
    __apply_Xcode_12_5_M1_post_install_workaround(installer)
end

记得 pod install 一次。

找到 Info.plist 文件,右键 -> Open As -> Source Code,添加如下内容

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>localhost</key>
    <dict>
      <key>NSExceptionAllowsInsecureHTTPLoads</key>
      <true/>
    </dict>
  </dict>
</dict>

在 Build Phases 中新建一个 Run Script

integration-native-2021-10-19-15-38-04

双击标题,将其更名为 Bundle React Native code and images

点击三角图标展开,在其中填入

export NODE_BINARY=node ../node_modules/react-native/scripts/react-native-xcode.sh

像下面那样更改 AppDelegate.h 文件

#import <UIKit/UIKit.h>
#import <React/RCTBridgeDelegate.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

像下面那样更改 AppDelegate.m 文件

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTBridgeModule.h>
#import <HybridNavigation/HybridNavigation.h>

@interface AppDelegate () <HBDReactBridgeManagerDelegate>

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
    [[HBDReactBridgeManager get] installWithBridge:bridge];

    UIStoryboard *storyboard =  [UIStoryboard storyboardWithName:@"LaunchScreen" bundle:nil];
    UIViewController *rootViewController = [storyboard instantiateInitialViewController];
    self.window.windowLevel = UIWindowLevelStatusBar + 1;
    self.window.rootViewController = rootViewController;
    [self.window makeKeyAndVisible];
    return YES;
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#if DEBUG
    return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
    return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

- (void)reactModuleRegisterDidCompleted:(HBDReactBridgeManager *)manager {
    HBDTabBarController *tabs = [[HBDTabBarController alloc] init];
    HBDNavigationController *navigation = [[HBDNavigationController alloc] initWithRootViewController:[manager controllerWithModuleName:@"Navigation" props:nil options:nil]];
    HBDNavigationController *options = [[HBDNavigationController alloc] initWithRootViewController:[manager controllerWithModuleName:@"Options" props:nil options:nil]];

    [tabs setViewControllers:@[ navigation, options ]];
    [manager setRootViewController:tabs];
}

@end
上次更新: