天天看点

EnvironmentPlugin 一款用来配置可动态切换App环境的Gradle插件简介体验下载效果图使用

简介

gradle

中配置开发时的所有环境,你只需要很少的代码就能实现环境动态切换的功能。而在打生产包时你只需要在

gradle

中修改

release

的值为

true

就能将非生产环境剔除(不会将非生产环境打包到Apk中),从而保证非生产环境不会泄漏。在

gradle

中配置完成后只需要clean一下就会在

BuildConfig

的目录中生成一个

EnvConfig

的类,你只需要通过

EnvConfig.getEnv()

方法就能获取到所有你在gradle中配置的值,而此时你不需要关心自己当前处于哪个环境。切换环境时你只需要调用

EnvConfig.setEnv(Type type)

方法切换当前环境即可。

#GitHub

如果你想get源码,请点击https://github.com/kelinZhou/EnvironmentPlugin。

体验

点击下载或扫码下载DemoApk

EnvironmentPlugin 一款用来配置可动态切换App环境的Gradle插件简介体验下载效果图使用

下载

第一步:添加 gradlew plugins 仓库到你项目根目录的 gradle 文件中。

buildscript {
  repositories {
    maven { url "https://plugins.gradle.org/m2/" }
  }
  dependencies {
    classpath "gradle.plugin.com.kelin.environment:environment:1.1.2"
  }
}
           

第二步:在module中引入插件。

apply plugin: "com.kelin.environment"
           

效果图

EnvironmentPlugin 一款用来配置可动态切换App环境的Gradle插件简介体验下载效果图使用

使用

在App gradle中添加如下配置。

environment {
    release false

    initEnvironment "test"

    devConfig {
        appIcon "@mipmap/ic_android"
        appRoundIcon "@mipmap/ic_android"
        appName "EnvPlugin"
//        versionCode 100
        versionName "1.0.0"
        applicationId "${packageName}.test"
    }

    releaseConfig {
        appIcon "@mipmap/ic_launcher"
        appRoundIcon "@mipmap/ic_launcher"
        appName "@string/app_name"
//        versionCode 200
        versionName "2.0.0"
        applicationId packageName
    }

    releaseEnv {
        alias "生产"

        variable "API_HOST", "192.168.31.24"
        variable "API_PORT", "8443"
        variable "WX_APP_ID", "wxc23iadfaioaiuu0a"
        variable "WX_APP_SECRET", "ioa9ad9887ad98ay979axxx"
        variable "UM_APP_KEY", '7c2ed9f7f1d5ecccc', true
    }

    devEnv {
        alias "开发"

        variable "API_HOST", "192.168.30.11"
        variable "API_PORT", "8016"
        variable "UM_APP_KEY", '7c2ed9f7f1d5eefff'
    }

    testEnv {
        alias "测试"

        variable "API_HOST", "192.168.36.18"
        variable "UM_APP_KEY", '7c2ed9f7f1d5eebbb'
    }

    demoEnv {
        alias "预发"

        variable "API_HOST", "192.168.36.10"
        variable "UM_APP_KEY", '7c2ed9f7f1d5eeaaa'
    }
}

android{
    //...省略N多行代码
}
           

不要看代码这么多,其实很简单,你甚至可以直接拷贝我这里的代码进行使用。只是要把

devConfig

releaseConfig

这两个Extension中的值替换成你自己的就可以了。

#####下面主要来讲一下这些参数的含义。

######一、release&initEnvironment。

1).

release

:用来配置当前如果要打包的话是打生产包还是要打开发包,如果要打生产包着要把改值设置为true(

release true

),否者需要把值设置为false(

release false

)。一旦设置为true之后无论你是打debug(调试)包还是release(签名)包,initEnvironment的值将会无效,而且除了

releaseConfig和releaseEnv

以外的其他配置

包括:devConfig、devEnv、testEnv和demoEnv

都会失效,他们是不会被打包到.apk文件中的,否则就会。

2).

initEnvironment

:用来配置当前应用安装到一台设备上之后默认是什么环境,这里有以下这些值可以选择。

参数 说明
“release” 生产环境。
“dev” 开发环境。
“test” 测试环境。
“demo” 预发布环境。

**注意:**只是在新安装到一个设备上才会有效,如果是覆盖安装的话则不一定有效,因为你上一个版本可能做过环境切换,当切换后即使覆盖安装也会继续使用之前的环境,这是为了保证环境不会被莫名其妙的切换。还有就是改参数也可以不配置,如果不配置,这默认会是"release",并且该配置项只有在

release

true

的时候才是有意义的,但你也没有必要把

release

改为

true

后就删除该配置,一直留着就行,留着不会有任何影响。

######二、

devConfig

releaseConfig

这两个Extension。

来说下这两个Extension中都是有哪些配置。

1).

appIcon

:devConfig中用来配置开发时的应用图标,releaseConfig用来配置打成生产包后的图标。做要作用是用来在没有打开应用的时候区分当前是生产包还是开发包。

2).

appRoundIcon

:devConfig中用来配置开发时Android7.0的圆形应用图标,releaseConfig用来配置生产包的Android7.0的圆形应用图标。做要作用是用来在没有打开应用的时候区分当前是生产包还是开发包。

(如果不做Android7.0圆形图标适配的话可以不配置改参数)

3).

appName

:devConfig中用来配开发时的应用名称,releaseConfig用来配置打成生产包后的应用名称。做要作用是用来在没有打开应用的时候区分当前是生产包还是开发包。

4).

versionCode

:devConfig中用来配开发时的版本号,releaseConfig用来配置打成生产包后的版本号。做要作用是用来分离开发包和生产包的不同版本,因为在开发过程中我们可能要出很多个版本这时你只需要修改devConfig中的

viersionCode

的值就可以了。改配置可以省略,如果省略者会用

versionName

的值来代替,例如

versionName="1.0.1"

那么

versionCode

的值就为101,这也就意味着如果你没有配置

versionCode

那么你一定要配置

versionName

。你可以在

clean

过后通过

BuildConfig

类文件查看到这两个值。

5).

versionName

:devConfig中用来配开发时的版本名,releaseConfig用来配置打成生产包后的版本名。做要作用与versionCode的主要作用一致。同样

versionName

的配置也是可以省略的,如果省略着会使用

versionCode

转字符串的方法为

versionName

赋值,例如

versionCode=121

那么

versionName

的值就为

1.2.1

,如果

versionName

的值不足3位数,那么将会在前面补0,例如

versionCode=2

那么

versionName

的值就为

0.0.2

。如果你的versionCode与versionName的规则与我的不一致那么你就需要两个都进行配置,例如:

versionCode 201908
versionName "1.4.05"
           

6).

applicationId

:通常情况下你不需要使用该参数进行applicationId的配置,因为这个就是你的应用包名(packageName),而包名不应该有多个,如果你有多个且有分享功能or三方登录功能的话你就要针对不同的包名申请不同的key,这是比较麻烦的,但是有的时候又确实需要不同的包名进行测试,例如

推送Push服务(例如极光推送)

,如果你没有分离包名这有可能造成生产环境下的用户都能收到推送消息,如果项目还没有上线还好,但是如果上线了,这就会给用户带来不必要的困扰。所以,这个参数配置根据你的义务场景酌情选择。

devConfig

releaseConfig

这两个Extension中就只有这么多的配置,But你只在这里配置过了并不会有什么作用,你需要在正常配置这些参数的地方使用它。就像下面这样。

versionCode、versionName以及applicationId的使用

app gradle 中

android {
    defaultConfig {
        //使用environment中配置的versionCode
        versionCode environment.versionCode
        //使用environment中配置的versionName
        versionName environment.versionName
        //使用environment中配置的applicationId
        applicationId environment.applicationId
    }
}
           

appIcon、appRoundIcon以及appName的使用

AndroidManifest 清单文件中

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.kelin.environmenttoolsdemo">

    <application
            android:allowBackup="true"
            android:name=".App"
            android:icon="${APP_ICON}" //使用environment中配置的appIcon
            android:label="${APP_NAME}"//使用environment中配置的appName
            android:roundIcon="${APP_ROUND_ICON}"//使用environment中配置的appRoundIcon
            android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>
           

**注意:**通过这种方式配置后,当你的

release

的值为

true

时使用的就是

releaseConfig

中的配置,否则使用的就是

devConfig

中的配置。还有一点需要注意就是

appIcon

appRoundIcon

appName

这三项配置在

devConfig

releaseConfig

这两个Extension中必须保证对等出现,例如你在

devConfig

中使用了

appIcon

这个配置,那么就必须要在

releaseConfig

中也配置

appIcon

否者在manifest清单文件中使用他们时是会报错的。

你也可能会觉得这么配置比较麻烦,首先声明我觉得不麻烦,就算是麻烦也就这一次,以后每次出包的时候省掉了很多修改。其次如果你实在不需要这样配,那么

devConfig

releaseConfig

这两个Extension也是可以省略不配的。就像下面这样:

environment {
    release false

    initEnvironment "test"

    releaseEnv {
        alias "生产"

        variable "API_HOST", "192.168.31.24"
        variable "API_PORT", "8443"
        variable "WX_APP_ID", "wxc23iadfaioaiuu0a"
        variable "WX_APP_SECRET", "ioa9ad9887ad98ay979axxx"
        variable "UM_APP_KEY", '7c2ed9f7f1d5ecccc', true
    }

    devEnv {
        alias "开发"

        variable "API_HOST", "192.168.30.11"
        variable "API_PORT", "8016"
        variable "UM_APP_KEY", '7c2ed9f7f1d5eefff'
    }

    testEnv {
        alias "测试"

        variable "API_HOST", "192.168.36.18"
        variable "UM_APP_KEY", '7c2ed9f7f1d5eebbb'
    }

    demoEnv {
        alias "预发"

        variable "API_HOST", "192.168.36.10"
        variable "UM_APP_KEY", '7c2ed9f7f1d5eeaaa'
    }
}
           

像这样的话你就通过正常的配置来配置你的项目就可以了。

######三、releaseEnv 、devEnv 、testEnv 和demoEnv 。

releaseEnv

devEnv

testEnv

demoEnv

中除了

releaseEnv

以外,其他的都是非必须的,例如你只有开发环境和生产环境,你只需要配置

releaseEnv

devEnv

这两个就行了。因为

releaseEnv

是必须要配置的,所以你的

releaseEnv

中的环境变量(姑且先这么称呼吧,环境变量)必须是最全的,而其他的只需要配置与

releaseEnv

中不同的环境变量即可。如果其他的环境配置中包含了

releaseEnv

中没有的环境变量则会被舍弃。

这些环境配置中一共有两个方法,他们的使用方式及作用如下:

1).alias:别名,用来配置当前环境变量的可读性更好的(你自己更加容易读懂的)名称,这个别名的值将出现在EnvConfig.Type枚举中,可以通过EnvConfig.Type.alias获取,也可以用作切换环境时的展示名。可以省略,如果省略则默认为他们对应的英文名,例如

releaseEnv

默认的别为为Release。配置方式:

alias "生产" //配置当前环境配置的别名为生产。
           

2).variable:声明环境变量,用于生成一个环境变量。它一共有三个参数:

参数名 样例 可省略 说明
name “API_HOST” 变量名称,将会生成代码到Environment类文件中,必须遵循Java的命名规范,使用时通过

EnvConfig.getEnv().变量名

调用,例如

EnvConfig.getEnv().API_HOST

value “192.168.31.24” 变量值,将会生成代码到Environment类文件中,使用时通过

EnvConfig.getEnv().变量名

调用,例如

EnvConfig.getEnv().API_HOST

placeholder true 是否同时生成到manifestPlaceholder,如果为true则可以在Manifest清单文件中使用,否则不行。可以省略,如果省略默认为false。该参数在不通的环境配置中只设置一次也可以,就像栗子中的UM_APP_KEY,在release中设置为了true,其他的环境配置中就可以省略。

clean项目。

以上都配置好以后像下面这样

clean

一下项目,或者点击一下gradle右上角的Sync Now。

EnvironmentPlugin 一款用来配置可动态切换App环境的Gradle插件简介体验下载效果图使用

然后你就能得到下面这两个类,EnvConfig 和 Environment。

EnvironmentPlugin 一款用来配置可动态切换App环境的Gradle插件简介体验下载效果图使用

EnvConfig

release为false时EnvConfig的代码如下:

public final class EnvConfig {
  public static final boolean IS_RELEASE = Boolean.parseBoolean("false");

  public static final Type INIT_ENV = Type.nameOf("test");

  private static final Environment RELEASE_ENV = new EnvironmentImpl("192.168.31.24", "8443", "wxc23iadfaioaiuu0a", "ioa9ad9887ad98ay979ad86", "7c2ed9f7f1d5ecccc");

  private static final Environment DEV_ENV = new EnvironmentImpl("192.168.30.11", "8016", "wxc23iadfaioaiuu0a", "ioa9ad9887ad98ay979ad86", "7c2ed9f7f1d5eefff");

  private static final Environment TEST_ENV = new EnvironmentImpl("192.168.36.18", "8001", "wxc23iadfaioaiuu0a", "ioa9ad9887ad98ay979ad86", "7c2ed9f7f1d5eebbb");

  private static final Environment DEMO_ENV = new EnvironmentImpl("192.168.36.10", "8109", "wxc23iadfaioaiuu0a", "ioa9ad9887ad98ay979ad86", "7c2ed9f7f1d5eeaaa");

  private static Context context;

  private static Type curEnvType;

  EnvConfig() {
    throw new RuntimeException("EnvConfig can't be constructed");
  }

  public static void init(Application app) {
    context = app.getApplicationContext();
        curEnvType = Type.nameOf(PreferenceManager.getDefaultSharedPreferences(context).getString("current_environment_type_string_name", INIT_ENV.name()));
  }

  public static boolean setEnvType(Type type) {
    if (type != curEnvType) {
        curEnvType = type;
       if (context != null) {
           PreferenceManager.getDefaultSharedPreferences(context).edit().putString("current_environment_type_string_name", type.name()).apply();
       }
       return true;
    } else {
       return false;
    }
  }

  public static Type getEnvType() {
    return curEnvType;
  }

  public static Environment getEnv() {
    switch (curEnvType) {
        case RELEASE:
            return RELEASE_ENV;
        case DEV:
            return DEV_ENV;
        case TEST:
            return TEST_ENV;
        case DEMO:
            return DEMO_ENV;
        default:
            throw new RuntimeException("the type:" + curEnvType.toString() + " is unkonwn !");
    }}

  public enum Type {
    RELEASE("生产"),

    DEV("开发"),

    TEST("测试"),

    DEMO("预发");

    public final String alias;

    Type(String alias) {
      this.alias = alias;
    }

    private static Type nameOf(String typeName) {
      if (typeName != null) {
              for (Type value : values()) {
                  if (value.name().toLowerCase().equals(typeName.toLowerCase())) {
                      return value;
                  }
              }
          }
          return RELEASE;
    }
  }

  private static final class EnvironmentImpl extends Environment {
    EnvironmentImpl(String var0, String var1, String var2, String var3, String var4) {
      super(var0, var1, var2, var3, var4);
    }
  }
}
           

release为true时EnvConfig的代码如下:

public final class EnvConfig {
  public static final boolean IS_RELEASE = Boolean.parseBoolean("true");

  public static final Type INIT_ENV = Type.nameOf("release");

  private static final Environment RELEASE_ENV = new EnvironmentImpl("192.168.31.24", "8443", "wxc23iadfaioaiuu0a", "ioa9ad9887ad98ay979ad86", "7c2ed9f7f1d5ecccc");

  private static Type curEnvType = Type.RELEASE;

  EnvConfig() {
    throw new RuntimeException("EnvConfig can't be constructed");
  }

  public static void init(Application app) {
  }

  public static boolean setEnvType(Type type) {
    return true;
  }

  public static Type getEnvType() {
    return curEnvType;
  }

  public static Environment getEnv() {
    return RELEASE_ENV;
  }

  public enum Type {
    RELEASE("生产");

    public final String alias;

    Type(String alias) {
      this.alias = alias;
    }

    private static Type nameOf(String typeName) {
      return RELEASE;
    }
  }

  private static final class EnvironmentImpl extends Environment {
    EnvironmentImpl(String var0, String var1, String var2, String var3, String var4) {
      super(var0, var1, var2, var3, var4);
    }
  }
}
           

Environment

release为false时和为true时,Environment的代码不变,代码如下:

public abstract class Environment {
  public final String API_HOST;

  public final String API_PORT;

  public final String WX_APP_ID;

  public final String WX_APP_SECRET;

  public final String UM_APP_KEY;

  protected Environment(String var0, String var1, String var2, String var3, String var4) {
    API_HOST = var0;
    API_PORT = var1;
    WX_APP_ID = var2;
    WX_APP_SECRET = var3;
    UM_APP_KEY = var4;
  }
}
           

初始化。

在你的Application的onCreate方法中进行初始化。其实初始化只有在

release=false

时才有意义,但是你不需要在调用之前判断当前是不是生产包(尽管你可以通过EnvConfig.IS_RELEASE来判断gradle中release的值),因为在

release=true

时EnvConfig中依然保留了

init

方法,只是变成了空实现而已,所以你直接调用即可。

public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        EnvConfig.init(this);  //初始化环境配置。
    }
}
           

获取当前环境。

你可以在任何时候通过

EnvConfig.getEnv()

来获取当前的环境变量,样例代码如下:

Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(EnvConfig.getEnv().API_HOST) //使用当前环境的主机地址构建Retrofit.
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
           

####切换当前环境。

你可以在任何时候通过

EnvConfig.setEnvType(Type type)

来切换环境,这里的参数是一个

EnvConfig.Type

枚举,**虽然这里的参数是一个

EnvConfig.Type

枚举,但是你绝对不可以使用

EnvConfig.Type.RELEASE

以外的其他任何枚举。**因为一旦你将gradle中release的值改为true,除

EnvConfig.Type.RELEASE

以外的其他枚举都将不存在。如果你使用了,将会报错。甚至

EnvConfig.Type.RELEASE

都不建议你直接使用。你可以像下面这样使用:

@SuppressLint("SetTextI18n", "InflateParams")
fun showEnvSwitcherDialog(context: Activity, onSwitch: (envType: EnvConfig.Type) -> Unit) {
    val custom = LayoutInflater.from(context).inflate(R.layout.dialog_env_switcher, null)
    val dialog = AlertDialog.Builder(context, R.style.CommonWidgetDialog)
        .setView(custom)
        .setCancelable(true)
        .create()

    val buttons = listOf<Button>(
        custom.findViewById(R.id.dialog_dev),
        custom.findViewById(R.id.dialog_test),
        custom.findViewById(R.id.dialog_release),
        custom.findViewById(R.id.dialog_prepare)
    )

    EnvConfig.Type.values().forEachIndexed { index, type ->
        val button = buttons[index]
        button.visibility = View.VISIBLE
        button.text = "${type.alias}环境"
        button.setOnClickListener {
            ToastUtil.showShortToast("已切换至${type.alias}环境")
            dialog.dismiss()
            onSwitch(type)
        }
    }
    dialog.show()
}
           

然后在调用的地方像下面这样写:

if (!EnvConfig.IS_RELEASE) {
    showEnvSwitcherDialog(requireActivity()) {
        EnvConfig.setEnvType(it)
        //TODO 执行切换环境的逻辑,比如重新创建网络请求(如Retrofit、OkHttp、GRpc等)。
    }
}
           

好了先写到这里,不知不觉也啰嗦了挺多了。如果你觉得我写的还可以的话,fork

和star或者点一个👍、一个赏,都是对我继续创作下去的鼓励。再次感谢。