美文网首页
Android-多套环境的维护

Android-多套环境的维护

作者: BlueSocks | 来源:发表于2023-06-05 15:59 被阅读0次

记录一下项目中多套环境维护的一种思路。

一、多套环境要注意的问题

1、方便使用灵活配置
2、配置安全不会被覆写
3、扩展灵活
4、安装包可动态切换环境,方便测试人员使用

二、解决思路

1、Android中的Properties文件是只读的,打包后不可修改,所以用Properties文件维护所有的配置。
2、在一个安装包内动态切换环境,方便测试人员切换使用,这一点用MMKV来动态存储。为了防止打包时可能出现的错误,这一点也需要Properties文件来控制。

三、Properties文件的封装

package com.abc.kotlinstudio

import android.content.Context
import java.io.IOException
import java.util.*

object PropertiesUtil {

    private var pros: Properties? = null

    fun init(c: Context) {
        pros = Properties()
        try {
            val input = c.assets.open("appConfig.properties")
            pros?.load(input)
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    private fun getProperty(key: String, default: String): String {
        return pros?.getProperty(key, default) ?: default
    }

    /**
     * 判断是否是国内版本
     */
    fun isCN(): Boolean {
        return getProperty("isCN", "true").toBoolean()

    }

    /**
     * 判断是否是正式环境
     */
    fun isRelease(): Boolean {
        return getProperty("isRelease", "false").toBoolean()
    }

    /**
     * 获取版本的环境 dev test release
     * 如果isRelease为true就读Properties文件,为false就读MMKV存储的值
     */
    fun getEnvironment(): Int = if (isRelease()) {  
        when (getProperty("environment", "test")) {
            "dev" -> {
                GlobalUrlConfig.EnvironmentConfig.DEV.value
            }
            "test" -> {
                GlobalUrlConfig.EnvironmentConfig.TEST.value
            }
            "release" -> {
                GlobalUrlConfig.EnvironmentConfig.RELEASE.value
            }
            else -> {
                GlobalUrlConfig.EnvironmentConfig.TEST.value
            }
        }

    } else {
        when (CacheUtil.getEnvironment(getProperty("environment", "test"))) {
            "dev" -> {
                GlobalUrlConfig.EnvironmentConfig.DEV.value
            }
            "test" -> {
                GlobalUrlConfig.EnvironmentConfig.TEST.value
            }
            "release" -> {
                GlobalUrlConfig.EnvironmentConfig.RELEASE.value
            }

            else -> {
                GlobalUrlConfig.EnvironmentConfig.TEST.value
            }
        }
    }


    /**
     * 获取国内外环境
     */
    fun getCN(): Int = if (isRelease()) {
        when (getProperty("isCN", "true")) {
            "true" -> {
                GlobalUrlConfig.CNConfig.CN.value
            }
            "false" -> {
                GlobalUrlConfig.CNConfig.I18N.value
            }

            else -> {
                GlobalUrlConfig.CNConfig.CN.value
            }
        }

    } else {
        when (CacheUtil.getCN(getProperty("isCN", "true"))) {
            "true" -> {
                GlobalUrlConfig.CNConfig.CN.value
            }
            "false" -> {
                GlobalUrlConfig.CNConfig.I18N.value
            }

            else -> {
                GlobalUrlConfig.CNConfig.CN.value
            }
        }
    }


}

注意二点,打包时如果Properties文件isRelease为true则所有配置都读Properties文件,如果为false就读MMKV存储的值;如果MMKV没有存储值,默认值也是读Properties文件。

1.png

内容比较简单:

isCN = true   //是否国内环境 
isRelease = false   //是否release,比如日志的打印也可以用这个变量控制
#dev test release   //三种环境
environment = dev  //环境切换

四、MMKV封装

package com.abc.kotlinstudio

import android.os.Parcelable
import com.tencent.mmkv.MMKV
import java.util.*

object CacheUtil {

    private var userId: Long = 0

    //公共存储区的ID
    private const val STORAGE_PUBLIC_ID = "STORAGE_PUBLIC_ID"

    //------------------------公共区的键------------------
    //用户登录的Token
    const val KEY_PUBLIC_TOKEN = "KEY_PUBLIC_TOKEN"

    //------------------------私有区的键------------------
    //用户是否第一次登录
    const val KEY_USER_IS_FIRST = "KEY_USER_IS_FIRST"

    /**
     * 设置用户的ID,根据用户ID做私有化分区存储
     */
    fun setUserId(userId: Long) {
        this.userId = userId
    }

    /**
     * 获取MMKV对象
     * @param isStoragePublic true 公共存储空间  false 用户私有空间
     */
    fun getMMKV(isStoragePublic: Boolean): MMKV = if (isStoragePublic) {
        MMKV.mmkvWithID(STORAGE_PUBLIC_ID)
    } else {
        MMKV.mmkvWithID("$userId")
    }


    /**
     * 设置登录后token
     */
    fun setToken(token: String) {
        put(KEY_PUBLIC_TOKEN, token, true)
    }


    /**
     * 获取登录后token
     */
    fun getToken(): String = getString(KEY_PUBLIC_TOKEN)


    /**
     * 设置MMKV存储的环境
     */
    fun putEnvironment(value: String) {
        put("environment", value, true)
    }

    /**
     * 获取MMKV存储的环境
     */
    fun getEnvironment(defaultValue: String): String {
        return getString("environment", true, defaultValue)
    }

    /**
     * 设置MMKV存储的国内外环境
     */
    fun putCN(value: String) {
        put("isCN", value, true)
    }

    /**
     * 获取MMKV存储的国内外环境
     */
    fun getCN(defaultValue: String): String {
        return getString("isCN", true, defaultValue)
    }

//------------------------------------------基础方法区-----------------------------------------------

    /**
     * 基础数据类型的存储
     * @param key 存储的key
     * @param value 存储的值
     * @param isStoragePublic 是否存储在公共区域 true 公共区域 false 私有区域
     */
    fun put(key: String, value: Any?, isStoragePublic: Boolean): Boolean {
        val mmkv = getMMKV(isStoragePublic)
        return when (value) {
            is String -> mmkv.encode(key, value)
            is Float -> mmkv.encode(key, value)
            is Boolean -> mmkv.encode(key, value)
            is Int -> mmkv.encode(key, value)
            is Long -> mmkv.encode(key, value)
            is Double -> mmkv.encode(key, value)
            is ByteArray -> mmkv.encode(key, value)
            else -> false
        }
    }


    /**
     * 这里使用安卓自带的Parcelable序列化,它比java支持的Serializer序列化性能好些
     * @param isStoragePublic 是否存储在公共区域 true 公共区域 false 私有区域
     */
    fun <T : Parcelable> put(key: String, t: T?, isStoragePublic: Boolean): Boolean {
        if (t == null) {
            return false
        }
        return getMMKV(isStoragePublic).encode(key, t)
    }

    /**
     * 存Set集合的数据
     * @param isStoragePublic 是否存储在公共区域 true 公共区域 false 私有区域
     */
    fun put(key: String, sets: Set<String>?, isStoragePublic: Boolean): Boolean {
        if (sets == null) {
            return false
        }
        return getMMKV(isStoragePublic).encode(key, sets)
    }

    /**
     * 取数据,因为私有存储区用的多,所以这里给了默认参数为私有区域,如果公共区域取要记得改成true.下同
     */
    fun getInt(key: String, isStoragePublic: Boolean = false, defaultValue: Int = 0): Int {
        return getMMKV(isStoragePublic).decodeInt(key, defaultValue)
    }

    fun getDouble(
        key: String,
        isStoragePublic: Boolean = false,
        defaultValue: Double = 0.00
    ): Double {
        return getMMKV(isStoragePublic).decodeDouble(key, defaultValue)
    }

    fun getLong(key: String, isStoragePublic: Boolean = false, defaultValue: Long = 0L): Long {
        return getMMKV(isStoragePublic).decodeLong(key, defaultValue)
    }

    fun getBoolean(
        key: String,
        isStoragePublic: Boolean = false,
        defaultValue: Boolean = false
    ): Boolean {
        return getMMKV(isStoragePublic).decodeBool(key, defaultValue)
    }

    fun getFloat(key: String, isStoragePublic: Boolean = false, defaultValue: Float = 0F): Float {
        return getMMKV(isStoragePublic).decodeFloat(key, defaultValue)
    }

    fun getByteArray(key: String, isStoragePublic: Boolean = false): ByteArray? {
        return getMMKV(isStoragePublic).decodeBytes(key)
    }

    fun getString(
        key: String,
        isStoragePublic: Boolean = false,
        defaultValue: String = ""
    ): String {
        return getMMKV(isStoragePublic).decodeString(key, defaultValue) ?: defaultValue
    }

    /**
     * getParcelable<Class>("")
     */
    inline fun <reified T : Parcelable> getParcelable(
        key: String,
        isStoragePublic: Boolean = false
    ): T? {
        return getMMKV(isStoragePublic).decodeParcelable(key, T::class.java)
    }

    fun getStringSet(key: String, isStoragePublic: Boolean = false): Set<String>? {
        return getMMKV(isStoragePublic).decodeStringSet(key, Collections.emptySet())
    }

    fun removeKey(key: String, isStoragePublic: Boolean = false) {
        getMMKV(isStoragePublic).removeValueForKey(key)
    }

    fun clearAll(isStoragePublic: Boolean = false) {
        getMMKV(isStoragePublic).clearAll()
    }
    
}

五、URL的配置

假设有国内外以及host、h5_host环境 :

object GlobalUrlConfig {

    private val BASE_HOST_CN_DEV = "https://cn.dev.abc.com"
    private val BASE_HOST_CN_TEST = "https://cn.test.abc.com"
    private val BASE_HOST_CN_RELEASE = "https://cn.release.abc.com"

    private val BASE_HOST_I18N_DEV = "https://i18n.dev.abc.com"
    private val BASE_HOST_I18N_TEST = "https://i18n.test.abc.com"
    private val BASE_HOST_I18N_RELEASE = "https://i18n.release.abc.com"

    private val BASE_HOST_H5_CN_DEV = "https://cn.dev.h5.abc.com"
    private val BASE_HOST_H5_CN_TEST = "https://cn.test.h5.abc.com"
    private val BASE_HOST_H5_CN_RELEASE = "https://cn.release.h5.abc.com"

    private val BASE_HOST_H5_I18N_DEV = "https://i18n.dev.h5.abc.com"
    private val BASE_HOST_H5_I18N_TEST = "https://i18n.test.h5.abc.com"
    private val BASE_HOST_H5_I18N_RELEASE = "https://i18n.release.h5.abc.com"

    private val baseHostList: List<List<String>> = listOf(
        listOf(
            BASE_HOST_CN_DEV,
            BASE_HOST_CN_TEST,
            BASE_HOST_CN_RELEASE
        ), listOf(
            BASE_HOST_I18N_DEV,
            BASE_HOST_I18N_TEST,
            BASE_HOST_I18N_RELEASE
        )
    )

    private val baseHostH5List: List<List<String>> = listOf(
        listOf(
            BASE_HOST_H5_CN_DEV,
            BASE_HOST_H5_CN_TEST,
            BASE_HOST_H5_CN_RELEASE
        ), listOf(
            BASE_HOST_H5_I18N_DEV,
            BASE_HOST_H5_I18N_TEST,
            BASE_HOST_H5_I18N_RELEASE
        )
    )

    //base
    var BASE_HOST: String =
        baseHostList[PropertiesUtil.getCN()][PropertiesUtil.getEnvironment()]
    //base_h5    
    var BASE_H5_HOST: String =
        baseHostH5List[PropertiesUtil.getCN()][PropertiesUtil.getEnvironment()]


    enum class CNConfig(var value: Int) {
        CN(0), I18N(1)
    }

    enum class EnvironmentConfig(var value: Int) {
        DEV(0), TEST(1), RELEASE(2)
    }

六、测试人员可在打好的App动态切换

可以弹Dialog动态切换环境,下面为测试代码:

//初始化
PropertiesUtil.init(this)
MMKV.initialize(this)
CacheUtil.setUserId(1000L)

val btSetCn = findViewById<AppCompatButton>(R.id.bt_set_cn)
val btSeti18n = findViewById<AppCompatButton>(R.id.bt_set_i8n)
val btSetDev = findViewById<AppCompatButton>(R.id.bt_set_dev)
val btSetTest = findViewById<AppCompatButton>(R.id.bt_set_test)
val btSetRelease = findViewById<AppCompatButton>(R.id.bt_set_release)

//App内找个地方弹一个Dialog动态修改下面的参数即可。

btSetCn.setOnClickListener {
    CacheUtil.putCN("true")
    //重启App(AndroidUtilCode工具类里面的方法)
    AppUtils.relaunchApp(true)
}

btSeti18n.setOnClickListener {
    CacheUtil.putCN("false")
    AppUtils.relaunchApp(true)
}

btSetDev.setOnClickListener {
    CacheUtil.putEnvironment("dev")
    AppUtils.relaunchApp(true)
}

btSetTest.setOnClickListener {
    CacheUtil.putEnvironment("test")
    AppUtils.relaunchApp(true)
}

btSetRelease.setOnClickListener {
    CacheUtil.putEnvironment("release")
    AppUtils.relaunchApp(true)
}

总结

一般会有4套环境: 开发环境,测试环境,预发布环境,正式环境。如果再区分国内外则乘以2。除了base的主机一般还会引入其他主机,比如h5的主机,这样会导致整个环境复杂多变。

刚开始是给测试打多渠道包,测试抱怨切环境,频繁卸载安装App很麻烦,于是做了这个优化。上线时记得把Properties文件isRelease设置为true,则发布的包就不会有问题,这个一般都不会忘记,风险很小。相比存文件或者其他形式安全很多。

写的比较匆忙,代码略粗糙,主要体现思路。以上!

相关文章

  • postman的环境

    如何在postman中创建环境: 创建多套环境 环境可以有多套:开发,测试,生产 使用指定环境 在左上角选择对应的...

  • Postman-多环境使用

    当一个接口有多套环境需要切换的时候,可以设置多套环境变量,使用的时候,直接选择环境进行操作 使用示例 新增环境这里...

  • SpringBoot配置多套环境

    生产中会经历,开发,测试,到上线。三个阶段 使用spring.profiles.active=test 定义一个a...

  • 微服务下 Spring Boot 应用多环境发布

    说明 解决微服务在多套环境发布的问题,解决开发环境和预发环境切换的问题,支持 N 套开发环境水平扩展。 Sprin...

  • SpringBoot 之jasypt 数据库密码加密

    在开发过程中,需要多个开发环境,如开发环境,测试环境,集成环境,线上环境等等,有一些公司会有运维专门维护一套上线的...

  • BMS-git代码规范

    为了更好管理代码分支及避免因分支混乱导致后期项目难以维护,现制定以下分支规范 环境 总共需要3套环境 线上环境--...

  • Android-构建不同环境的Apk

    Android-构建不同环境的Apk 在开发Android过程中,常常需要构建不同环境的Apk,比如我们有可能会需...

  • Mac brew安装指定版本软件 old version

    brew维护着自己的一套软件仓库, 根据官方的说法, 对于多版本软件, 基本只维护最新稳定版本, 所以现在搜索的答...

  • XXL-CONF v1.4.2 发布,分布式配置管理平台(新增

    版本 v1.4.2 新特性 1、多环境支持:单个配置中心集群,支持自定义多套环境,管理多个环境的的配置数据;环境之...

  • Build Configuration 配置多套环境

    Build Configuration? Xcode 默认会有两个编译模式。一个是Debug,一个是release...

网友评论

      本文标题:Android-多套环境的维护

      本文链接:https://www.haomeiwen.com/subject/nafwedtx.html