React-Native开发如何通过原生(android)区分开发/测试/生产环境

2,398 阅读4分钟

ReactNative在前端开发中的使用日渐增多,但是随之而来也产生了很多问题:

  • 开发环境、测试环境、正式环境难以区分,发测试和正式包时需要手动修改很多关于环境的配置(如请求的接口地址等),繁琐且容易出错
  • 为了新包可以覆盖安装老包,每次打包前还需要手动维护版本号使其递增
  • 打包生成文件名为app-debug.apk、app-release.apk,不便于区分和存档

一. 开发需求

环境区分

正常需求来讲,我们需要区分三种环境——开发环境、测试环境、正式环境(也可以成为生产环境)。执行react-native run-android时,默认是使用了Debug环境(此处对应为开发环境),而打包时我们通常会执行cd android && ./gradlew assembleRelease,很明显可以看出这里使用的是Release环境(此处对应正式环境或生产环境),但是我们要为测试工程师提供测试包时也是使用这个命令,所以就会造成正式包和测试包使用同一套环境,只能靠手动修改代码来完成环境区分。 那么,为了解决上述问题,我们需要引入一个新的环境——Beta,也即为测试环境

包名及包其他信息修改

上面虽说已经可以区分环境打包,但由于包名相同,三个版本的安装包并不能在一台设备上共存,且即使可以共存,三个包安装后显示的名称已经图标均完全相同无法区分,所以我们需要做一些操作使得它们可以被很容易区分出来。

二. 如何进行配置

android/gradle.properties

MYAPP_RELEASE_STORE_FILE=test.keystore
MYAPP_RELEASE_KEY_ALIAS=my-key-alias
MYAPP_RELEASE_STORE_PASSWORD=***
MYAPP_RELEASE_KEY_PASSWORD=***


MYAPP_RELEASE_STORE_FILE_PRO=pro.keystore
MYAPP_RELEASE_KEY_ALIAS_PRO=my-key-alias
MYAPP_RELEASE_STORE_PASSWORD_PRO=***
MYAPP_RELEASE_KEY_PASSWORD_PRO=***

android/app/build.gradle


....
    project.ext.react = [
        ...
    // 设置beta版本打包jsbundle
    // 如没有设置,则beta版会和debug版一样,读取本地js-server的jsbundle
        bundleInBeta: true,
    ]

...
    signingConfigs {

        debug {
            storeFile file('debug.keystore')
            storePassword 'android'
            keyAlias 'androiddebugkey'
            keyPassword 'android'
        }
			// 从 gradle.properties 读取不同环境的签名
        beta {
            if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
                storeFile file(MYAPP_RELEASE_STORE_FILE)
                storePassword MYAPP_RELEASE_STORE_PASSWORD
                keyAlias MYAPP_RELEASE_KEY_ALIAS
                keyPassword MYAPP_RELEASE_KEY_PASSWORD
            }
        }
        release {
            if (project.hasProperty('MYAPP_RELEASE_STORE_FILE_PRO')) {
                storeFile file(MYAPP_RELEASE_STORE_FILE_PRO)
                storePassword MYAPP_RELEASE_STORE_PASSWORD_PRO
                keyAlias MYAPP_RELEASE_KEY_ALIAS_PRO
                keyPassword MYAPP_RELEASE_KEY_PASSWORD_PRO
            }
        }

    }
    buildTypes {
        debug {
            signingConfig signingConfigs.debug
            // 是开发环境,是测试环境,所以是开发环境
            buildConfigField "boolean", "IS_DEBUG", "true"
            buildConfigField "boolean", "IS_DEV", "true"  
            //为debug版本的包名添加.debug后缀
            applicationIdSuffix ".debug"
            // 设置manifest占位符,为了显示不同的软件名
            manifestPlaceholders = [
                APP_NAME      : "@string/app_name_debug"
            ]
            resValue "string", "CodePushDeploymentKey", "w-rRMe60d0kaFD8mPM56gb58XJQXvAzxbTavs"
            matchingFallbacks = ['debug']

        }
        beta {
            signingConfig signingConfigs.beta
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        
            // 不是开发环境,是测试环境,所以是测试环境
            buildConfigField "boolean", "IS_DEBUG", "true"
            buildConfigField "boolean", "IS_DEV", "false"   
            //为测试版本的包名添加.beta
            applicationIdSuffix ".beta"
            // 设置manifest占位符,为了显示不同的软件名
            manifestPlaceholders = [
                APP_NAME      : "@string/app_name_beta"
            ]
            resValue "string", "CodePushDeploymentKey", "******"
            matchingFallbacks = ['beta','debug','release',]

        }  

        release {
            signingConfig signingConfigs.release
            minifyEnabled enableProguardInReleaseBuilds
            
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        
            // 不是开发环境,不是测试环境,所以是正式环境
            buildConfigField "boolean", "IS_DEBUG", "false"
            buildConfigField "boolean", "IS_DEV", "false"
            // 设置manifest占位符,为了显示不同的软件名
            manifestPlaceholders = [
                APP_NAME      : "@string/app_name"
            ]
            resValue "string", "CodePushDeploymentKey", "*****"
            matchingFallbacks = ['release']
        }
    }
....


设置好之后,在Android的代码中,我们通过BuildConfig.IS_DEBUGBuildConfig.IS_DEV就可以获取到我们当前的运行环境了,我们根据环境,通过代码就很容易进行配置的切换。但这仅仅是Android的层面,我们还需要将这个信息传递给RN层面,在js中也需要知道运行环境,这时候只需要对android/app/src/main/java/xxx/MainActivity.java做些小改动就可以:

android/app/src/main/java/xxx/MainActivity.java

package com.beesrv.www;

import com.facebook.react.ReactActivity;

import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import android.os.Bundle;

public class MainActivity extends ReactActivity {

  /**
   * Returns the name of the main component registered from JavaScript. This is used to schedule
   * rendering of the component.
   */
  @Override
  protected String getMainComponentName() {
    return "demoapp";
  }

// 注意这下面的代码
  @Override
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {
      @Override
      protected ReactRootView createRootView() {
       return new RNGestureHandlerEnabledRootView(MainActivity.this);
      }
          @Override
          protected Bundle getLaunchOptions() {
            Bundle bundle = new Bundle();
            bundle.putBoolean("isDebugMode", BuildConfig.IS_DEBUG);
            bundle.putBoolean("isDevMode", BuildConfig.IS_DEV);
            return bundle;
          }

    };
  }
}

android/app/src/main/AndroidManifest.xml

<application
    ...
    android:label="${APP_NAME}">
    <activity
        ...
        android:name=".MainActivity"
        android:label="${APP_NAME}">
        ...
    </activity>
    ...
</application>

android/app/src/main/res/values/strings.xml

<resources>
    <string name="app_name">软件XXX</string>
    <string name="app_name_beta">软件XXX测试版</string>
    <string name="app_name_debug">软件XXX开发版</string>
</resources>

RN代码修改

之后我们在RN的根元素中就能获取到这两个信息:

import React from 'react';
import Index from './app/index';

const App: () => React$Node = props => {
  // isDebugMode  isDevMode
  console.log(props);
  return <Index />;
};

export default App;

二. 包名及包其他信息修改

android/app/src/main/AndroidManifest.xml文件:

<application
    ...
    android:label="${APP_NAME}">
    <activity
        ...
        android:name=".MainActivity"
        android:label="${APP_NAME}">
        ...
    </activity>
    ...
</application>

android/app/src/main/res/values/strings.xml文件:

<resources>
    <string name="app_name">软件XXX</string>
    <string name="app_name_beta">软件XXX测试版</string>
    <string name="app_name_debug">软件XXX开发版</string>
</resources>

!!!需要注意的一点是,修改了启动包名之后,RN默认的启动命令是可以正常打包,但是不会使应用自启,也就是执行react-native run-android后,我们需要手动启动APP。但RN开发团队早就想到了这点,cli中是可以通过参数来解决这个问题的。我们将启动命令修改为react-native run-android --appIdSuffix=debug即可。

三. 打包

// window系统下

$ cd android

// 清空编译缓存
$ .\gradlew clean

// 生产环境-打包
$ .\gradlew assembleRelease

// 测试环境- 打包
$ .\gradlew assembleBeta


$ cd ..
// 安装生产环境包到 手机上
$ react-native run-android --variant=release

// 安装测试环境包到手机上
$ react-native run-android --variant=beta --appIdSuffix=beta --no-packager

参考文档

  1. 掘金
  2. codepush官方-配置多环境
  3. 简书-Gradle之resValue自定义资源
  4. react-native官方-打包APK