配置React Native多运行环境

项目之初,往往需要为项目配置诸如开发环境、测试环境、生产环境等,每个环境可能都有自己的打包变量和环境变量。本文尝试为 iOS 端和 Android 端都提供一种多环境的配置方案。

React Native 版本

本文使用的 React Native 版本为 0.61。

构建 iOS 端多环境

在 iOS 中,我们使用 iOS Build Configuration 来构建多环境。使用 Xcode 打开 [projectName].xcworkplace。

新建 Configurations

参照下图创建 Beta configuration。

新建 Scheme

点击该 scheme,在下拉菜单中选择 Edit Scheme,然后在弹窗中选择 Duplicate Scheme 来复制一个 scheme,将其命名为 Beta。将 Run、Test、Analyze 中的 Build Configuration 分别选择为 Beta.Debug,将 Profile 和 Archive 选择为 Beta.Release。勾选弹窗底下的 Shared 来进行分享,以便让其他开发同事也能使用到该 scheme。

修改 App 名称

上面操作我们新建了一个 Beta configuration。对于不同环境,我们通常需要修改图标或者应用名称来表示不同。我们这里主要通过修改应用名称。对于刚刚建立的 Beta configuration,我们将应用名称配置为[projectName]_β。配置参照下图:

添加名为 BUNDLE_DISPLAY_NAME_SUFFIX 的自定义项。

打开项目的 info.plist,删除 Bundle display name,修改 Bundle name 为 RN1$(BUNDLE_DISPLAY_NAME_SUFFIX)注意请将其中的 RN1 替换为实际的应用名称。

同时修改 RN1Test 下 info.plist 的 Bundle name。如下图所示:

修改 Bundle ID

Bundle ID 用来标识应用的唯一性,如果我们希望在同一台手机中能安装同一个应用不同环境的版本,我们需要给应用在每个环境下定义不同的 Bundle ID。同样的我们增加自定义项:

增加以下红框的内容:

打开项目的 info.plist,修改 Bundle Identifier 为 $(PRODUCT_BUNDLE_IDENTIFIER)$(BUNDLE_ID_SUFFIX)

同时修改 RN1Tests 中 info.plist 的 Bundle identifier。

修改 Podfile

这一步主要是告诉 cocoapods 以下事情:

  • Debug 和 Release 是默认的 configurations
  • Beta.Debug 从 Debug 中复制
  • Beta.Release 从 Release 中复制

内容如下:

1
2
3
4
5
project 'RN1',
'Debug' => :debug,
'Release' => :release,
'Beta.Debug' => :debug,
'Beta.Release' => :release
完成之后执行命令:
1
2
cd [project/ios]
pod install
到这里我们可以使用 Xcode 根据不同的 scheme 来运行项目了。

配置 package.json 命令

如果我们希望能从命令行中直接启动项目,可以在项目的 package.json 中增加以下命令:

1
2
3
4
5
6
{
"scripts": {
...
"ios.beta": "react-native run-ios —-simulator='iPhone X' —-scheme Beta --configuration Beta.Debug"
}
}

构建 Android 端多环境

Android 的配置相对来说要简单的多。Android 与 iOS 的 Build Configuration 相对应的是 Build Variant。一个 Build Variant 是 Build Type 和 Product Flavor 的组合。

添加 Product Flavors

打开项目的 android/app/build.gradle 文件,添加一个名为 productFlavors 的块,使用不同的 applicationId 来使得安装包不一样。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
android {
compileSdkVersion rootProject.ext.compileSdkVersion
...

flavorDimensions "default" // <-- !!! 要添加这行 !!!

productFlavors {
dev {
minSdkVersion rootProject.ext.minSdkVersion
applicationId 'com.rn1.dev'
targetSdkVersion rootProject.ext.targetSdkVersion
resValue "string", "build_config_package", "com.myapp"
}
beta {
minSdkVersion rootProject.ext.minSdkVersion
applicationId 'com.rn1.beta'
targetSdkVersion rootProject.ext.targetSdkVersion
resValue "string", "build_config_package", "com.myapp"
}
}
...
}

修改 App 名称

在 android/app/src/beta/res/values 目录下新建文件 strings.xml,填写以下内容:

1
2
3
<resources>
<string name="app_name">[projectName] Beta</string>
</resources>
注意:android/app/src/beta/res/values 该目录原本不存在,需要新建。在构建时会与 main 目录下的该文件进行合并。

运行命令

主要是添加一个参数:

1
—-variant=<productFlavour><BuildType>
所以如果以 debug mode 来运行 dev 版本,命令为:
1
react-native run-android --variant=devDebug --appIdSuffix=dev
如果我们要发布,命令格式为:
1
assemble<ProductFlavour><BuildType>
比如要发布一个beta版本:
1
cd android && ./gradlew assembleBetaRelease

配置环境变量

新建不同环境配置之后,我们希望能将环境变量暴露到 js 端。

配置 iOS 端环境变量

选择 [projectName] 目录,右键打开菜单,按照下列步骤创建原生文件:

编辑 AppInfo.h 文件内容如下:

1
2
3
4
5
6
7
8
9
#import <React/RCTBridge.h>

NS_ASSUME_NONNULL_BEGIN

@interface AppInfo : NSObject <RCTBridgeModule>

@end

NS_ASSUME_NONNULL_END
编辑 AppInfo.m 文件内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#import "AppInfo.h"

@implementation AppInfo : NSObject

RCT_EXPORT_MODULE(AppInfo)

+ (BOOL)requiresMainQueueSetup {
return YES;
}

- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}

- (NSDictionary *)constantsToExport {
NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
NSMutableDictionary *settings = [[info objectForKey:@"AppSettings"] mutableCopy];
NSString *versionName = [info objectForKey:@"CFBundleShortVersionString"];
NSNumber *versionCode = [info objectForKey:@"CFBundleVersion"];
NSString *bundleId = [info objectForKey:@"CFBundleIdentifier"];
[settings setObject:versionName forKey:@"VERSION_NAME"];
[settings setObject:versionCode forKey:@"VERSION_CODE"];
[settings setObject:bundleId forKey:@"APPLICATION_ID"];
return settings;
}

@end
我们导出了变量 VERSION_NAME VERSION_CODE APPLICATION_ID,稍后我们在 js 端来读取。

配置 Android 端环境变量

在 android/app/src/main/java/[packageName] 下新建 AppInfo.java 文件,文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package packageName;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class AppInfo extends ReactContextBaseJavaModule {
public AppInfo(@Nonnull ReactApplicationContext reactContext) {
super(reactContext);
}

@Nonnull
@Override
public String getName() {
return "AppInfo";
}

@Nullable
@Override
public Map<String, Object> getConstants() {
HashMap<String, Object> constants = new HashMap<>();
String flavor = BuildConfig.FLAVOR;
String env = null;

if ("dev".equals(flavor)) {
env = "development";
} else if ("beta".equals(flavor)) {
env = "beta";
} else if ("prod".equals(flavor)) {
env = "production";
}

constants.put("ENVIRONMENT", env);
constants.put("VERSION_NAME", BuildConfig.VERSION_NAME);
constants.put("VERSION_CODE", BuildConfig.VERSION_CODE);
constants.put("APPLICATION_ID", BuildConfig.APPLICATION_ID);
return constants;
}
}
再新建 AppPackage.java 文件,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package packageName;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class AppPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new AppInfo(reactContext));
return modules;
}

@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
修改 MainApplication.java 文件,添加我们自定义的模块:
1
2
3
4
5
6
7
8
9
10
...
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
// 添加自定义的package
new AppPackage()
);
}
...

在 js 端读取环境变量

在 RN 项目下新建文件 config.js,内容如下:

1
2
3
4
5
import { NativeModules } from 'react-native';

const { AppInfo } = NativeModules;

export default AppInfo;

参考资源