配置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
5project 'RN1',
'Debug' => :debug,
'Release' => :release,
'Beta.Debug' => :debug,
'Beta.Release' => :release1
2cd [project/ios]
pod install
配置 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
22android {
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>
运行命令
主要是添加一个参数:
1
—-variant=<productFlavour><BuildType>
1
react-native run-android --variant=devDebug --appIdSuffix=dev
1
assemble<ProductFlavour><BuildType>
1
cd android && ./gradlew assembleBetaRelease
配置环境变量
新建不同环境配置之后,我们希望能将环境变量暴露到 js 端。
配置 iOS 端环境变量
选择 [projectName] 目录,右键打开菜单,按照下列步骤创建原生文件:
编辑 AppInfo.h 文件内容如下:
1
2
3
4
5
6
7
8
9
NS_ASSUME_NONNULL_BEGIN
@interface AppInfo : NSObject <RCTBridgeModule>
@end
NS_ASSUME_NONNULL_END1
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
@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;
}
@endVERSION_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
44package 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( ReactApplicationContext reactContext){
super(reactContext);
}
public String getName() {
return "AppInfo";
}
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
28package 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 {
public List<NativeModule> createNativeModules( ReactApplicationContext reactContext){
List<NativeModule> modules = new ArrayList<>();
modules.add(new AppInfo(reactContext));
return modules;
}
public List<ViewManager> createViewManagers( ReactApplicationContext reactContext){
return Collections.emptyList();
}
}MainApplication.java
文件,添加我们自定义的模块:
1
2
3
4
5
6
7
8
9
10...
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
// 添加自定义的package
new AppPackage()
);
}
...
在 js 端读取环境变量
在 RN 项目下新建文件 config.js
,内容如下:
1
2
3
4
5import { NativeModules } from 'react-native';
const { AppInfo } = NativeModules;
export default AppInfo;