简介

gradle中配置开发时的所有环境,你只需要很少的代码就能实现环境动态切换的功能。而在打生产包时你只需要在gradle中修改release的值为true就能将非生产环境剔除(不会将非生产环境打包到Apk中),从而保证非生产环境不会泄漏。在gradle中配置完成后只需要clean一下就会在BuildConfig的目录中生成一个EnvConfig的类,你只需要通过EnvConfig.getEnv()方法就能获取到所有你在gradle中配置的值,而此时你不需要关心自己当前处于哪个环境。切换环境时你只需要调用EnvConfig.setEnv(Type type)方法切换当前环境即可。
#GitHub
如果你想get源码,请点击https://github.com/kelinZhou/EnvironmentPlugin。

体验

点击下载或扫码下载DemoApk

下载

第一步:添加 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"

效果图

使用

在App gradle中添加如下配置。

environment {release falseinitEnvironment "test"devConfig {appIcon "@mipmap/ic_android"appRoundIcon "@mipmap/ic_android"appName "EnvPlugin"
//        versionCode 100versionName "1.0.0"applicationId "${packageName}.test"}releaseConfig {appIcon "@mipmap/ic_launcher"appRoundIcon "@mipmap/ic_launcher"appName "@string/app_name"
//        versionCode 200versionName "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多行代码
}

不要看代码这么多,其实很简单,你甚至可以直接拷贝我这里的代码进行使用。只是要把devConfigreleaseConfig这两个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",并且该配置项只有在releasetrue的时候才是有意义的,但你也没有必要把release改为true后就删除该配置,一直留着就行,留着不会有任何影响。

######二、devConfigreleaseConfig这两个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服务(例如极光推送),如果你没有分离包名这有可能造成生产环境下的用户都能收到推送消息,如果项目还没有上线还好,但是如果上线了,这就会给用户带来不必要的困扰。所以,这个参数配置根据你的义务场景酌情选择。

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

android {defaultConfig {//使用environment中配置的versionCodeversionCode environment.versionCode//使用environment中配置的versionNameversionName environment.versionName//使用environment中配置的applicationIdapplicationId 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"><applicationandroid:allowBackup="true"android:name=".App"android:icon="${APP_ICON}" //使用environment中配置的appIconandroid:label="${APP_NAME}"//使用environment中配置的appNameandroid:roundIcon="${APP_ROUND_ICON}"//使用environment中配置的appRoundIconandroid: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中的配置。还有一点需要注意就是appIconappRoundIconappName这三项配置在devConfigreleaseConfig这两个Extension中必须保证对等出现,例如你在devConfig中使用了appIcon这个配置,那么就必须要在releaseConfig中也配置appIcon否者在manifest清单文件中使用他们时是会报错的。

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

environment {release falseinitEnvironment "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 。
releaseEnvdevEnvtestEnvdemoEnv中除了releaseEnv以外,其他的都是非必须的,例如你只有开发环境和生产环境,你只需要配置releaseEnvdevEnv这两个就行了。因为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。

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

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 {@Overridepublic 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.VISIBLEbutton.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或者点一个

EnvironmentPlugin 一款用来配置可动态切换App环境的Gradle插件相关推荐

  1. Spring多数据源配置以及动态切换实现

    Spring多数据源配置以及动态切换实现 问题描述 一:首先是配置数据源 1.使用xml的bean节点来配置 2.使用yml配置+java代码实现配置 二:动态数据源 三:使用AOP切面实现动态数据源 ...

  2. uniapp 动态切换应用图标、名称插件(如新年、国庆等) Ba-ChangeIcon

    动态切换应用图标.名称(如新年.国庆等) Ba-ChangeIcon 简介(下载地址) Ba-ChangeIcon 是一款uniapp动态切换应用图标.名称的插件.可实现过年.过节动态切换应用图标的效 ...

  3. springboot多数据源动态切换和自定义mybatis分页插件

    1.配置多数据源 增加druid依赖 完整pom文件 数据源配置文件 route.datasource.driver-class-name= com.mysql.jdbc.Driver route.d ...

  4. [贝聊科技]谈谈 iOS 如何动态切换 APP 的主题

    在移动互联网的下半场,越来越多的 APP 更加注重用户体验,以期来打动用户.主题的切换就是可以增强用户体验.结合运营活动的一个点:譬如 QQ 的夜间模式,节日里电商 APP 的皮肤切换等等的这些小细节 ...

  5. iOS开发笔记 -- 动态切换APP的logo

    1.618大促,看到天猫与京东的logo 也相应改变,所以查找资料 探究其实现方式. 2.实现的过程并不复杂,在此做个笔记,在今后的项目中可能会用到. 3.本篇笔记 demo 1.先看一下实现的效果. ...

  6. echart动态切换图表类型

    echart动态切换图表类型: 花开堪折直需折,莫待无花空折枝 最近的状态很差,已经一个月了,还没有适应这样的状态! 事情多起来了,没有很好的处理各个事情之间的关系,只是在走,不过姿势好像错了, 我居 ...

  7. SpringBoot2/SpringBoot/Java动态数据源配置、动态连接池配置、多数据源负载均衡

    Java动态数据源配置.动态连接池配置.多数据源负载均衡 大家好,今天给大家推荐一个自产的连接池插件.废话不多说,本文接口分为以下主题: 1. 插件开发背景: 2. 插件提供的能力: 3. 插件的使用 ...

  8. Proxool配置多数据源动态切换

    2019独角兽企业重金招聘Python工程师标准>>> 前段时间遇到多数据源动态切换问题,总结一下,做个记录,以备后续之需! 首先附上proxool连接池的配置方法:http://3 ...

  9. 【Android FFMPEG 开发】Android Studio 工程配置 FFMPEG ( 动态库打包 | 头文件与函数库拷贝 | CMake 脚本配置 )

    文章目录 I . FFMPEG 交叉编译后的函数库及头文件 II . FFMPEG 静态库打包动态库 ( 仅做参考 ) III . 创建 Android Studio 工程 IV . FFMPEG 头 ...

  10. 【Android NDK 开发】Android Studio 使用 CMake 导入动态库 ( 构建脚本路径配置 | 指定动态库查找路径 | 链接动态库 )

    文章目录 I . CMake 引入动态库与静态库区别 II . Android Studio 中 CMake 引入动态库流程 III . 指定动态库查找路径 IV . 链接函数库 V . 完整代码示例 ...

最新文章

  1. 深入理解 Cache 工作原理
  2. 收藏 | 10本免费的机器学习和数据科学书籍(附链接)
  3. contos 安装vim自动补全插件 YCM YouCompleteMe
  4. docker -v 覆盖了容器中的文件_springboot配合maven打成可执行jar,构建镜像部署到docker容器中...
  5. @Autowired和@Resource注解的区别?
  6. JS 给某个对象添加专属方法
  7. mysql5.6.19安装图解_mysql5.6.19安装说明
  8. 问题四十六:怎么用ray tracing画superellipsoid
  9. SLAM会议笔记(四)Lego-LOAM
  10. Mybaitis框架与Spring整合详解(三)
  11. 成功解决不能完成“视频帧到图层”的命令,因为需要QuickTime7.1或者更高版本
  12. OpenCV 头像训练与识别
  13. java微信退款接口demo_微信公众号接口开发----退款详解
  14. pcan的dbc和project等的配置
  15. 数学建模预测模型总结
  16. 喜羊羊与灰太狼java_java swing实现喜羊羊与灰太狼推箱子游戏附带视频开发教程...
  17. 如何通过Chrome查看网站登录 Cookie 信息
  18. MUI框架TAB切换
  19. Linux 网络编程学习笔记——二、IP 协议详解
  20. android 弹窗确认,弹出一个带确认和取消的dialog实例

热门文章

  1. Linux磁盘分区及文件系统格式化和挂载
  2. 3个关于HR的OKR的优秀案例
  3. pic单片机c语言程序设计实例精粹 pdf,PIC单片机C语言程序设计.pdf
  4. 黑马程序员Java零基础视频教程(2022最新Java)B站视频学习笔记-Day1-Java入门
  5. mt4 显示服务器时间,MQL4编程学习之MT4显示任意时间周期指标的使用方法
  6. 手机型号大全_《华为手机型号大全》值得收藏
  7. Java并发编程实战读书笔记合集
  8. python2.7使用教程_使用模块 - 廖雪峰 Python 2.7 中文教程
  9. 54扑克牌轮流拿问题,Python实现(详解)
  10. 双二极管(BAT54S)在电路中起什么作用? 钳位和保护