Android多套環(huán)境的維護(hù)思路詳解
一、多套環(huán)境要注意的問題
記錄一下項(xiàng)目中多套環(huán)境維護(hù)的一種思路。
1、方便使用靈活配置
2、配置安全不會被覆寫
3、擴(kuò)展靈活
4、安裝包可動態(tài)切換環(huán)境,方便測試人員使用
二、解決思路
1、Android中的Properties
文件是只讀的,打包后不可修改,所以用Properties
文件維護(hù)所有的配置。
2、在一個安裝包內(nèi)動態(tài)切換環(huán)境,方便測試人員切換使用,這一點(diǎn)用MMKV
來動態(tài)存儲。為了防止打包時可能出現(xiàn)的錯誤,這一點(diǎn)也需要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 } /** * 判斷是否是國內(nèi)版本 */ fun isCN(): Boolean { return getProperty("isCN", "true").toBoolean() } /** * 判斷是否是正式環(huán)境 */ fun isRelease(): Boolean { return getProperty("isRelease", "false").toBoolean() } /** * 獲取版本的環(huán)境 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 } } } /** * 獲取國內(nèi)外環(huán)境 */ 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 } } } }
注意二點(diǎn),打包時如果Properties文件isRelease
為true則所有配置都讀Properties文件,如果為false就讀MMKV存儲的值;如果MMKV沒有存儲值,默認(rèn)值也是讀Properties文件。
內(nèi)容比較簡單:
isCN = true //是否國內(nèi)環(huán)境 isRelease = false //是否release,比如日志的打印也可以用這個變量控制 #dev test release //三種環(huán)境 environment = dev //環(huán)境切換
四、MMKV封裝
package com.abc.kotlinstudio import android.os.Parcelable import com.tencent.mmkv.MMKV import java.util.* object CacheUtil { private var userId: Long = 0 //公共存儲區(qū)的ID private const val STORAGE_PUBLIC_ID = "STORAGE_PUBLIC_ID" //------------------------公共區(qū)的鍵------------------ //用戶登錄的Token const val KEY_PUBLIC_TOKEN = "KEY_PUBLIC_TOKEN" //------------------------私有區(qū)的鍵------------------ //用戶是否第一次登錄 const val KEY_USER_IS_FIRST = "KEY_USER_IS_FIRST" /** * 設(shè)置用戶的ID,根據(jù)用戶ID做私有化分區(qū)存儲 */ 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") } /** * 設(shè)置登錄后token */ fun setToken(token: String) { put(KEY_PUBLIC_TOKEN, token, true) } /** * 獲取登錄后token */ fun getToken(): String = getString(KEY_PUBLIC_TOKEN) /** * 設(shè)置MMKV存儲的環(huán)境 */ fun putEnvironment(value: String) { put("environment", value, true) } /** * 獲取MMKV存儲的環(huán)境 */ fun getEnvironment(defaultValue: String): String { return getString("environment", true, defaultValue) } /** * 設(shè)置MMKV存儲的國內(nèi)外環(huán)境 */ fun putCN(value: String) { put("isCN", value, true) } /** * 獲取MMKV存儲的國內(nèi)外環(huán)境 */ fun getCN(defaultValue: String): String { return getString("isCN", true, defaultValue) } //------------------------------------------基礎(chǔ)方法區(qū)----------------------------------------------- /** * 基礎(chǔ)數(shù)據(jù)類型的存儲 * @param key 存儲的key * @param value 存儲的值 * @param isStoragePublic 是否存儲在公共區(qū)域 true 公共區(qū)域 false 私有區(qū)域 */ 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 是否存儲在公共區(qū)域 true 公共區(qū)域 false 私有區(qū)域 */ fun <T : Parcelable> put(key: String, t: T?, isStoragePublic: Boolean): Boolean { if (t == null) { return false } return getMMKV(isStoragePublic).encode(key, t) } /** * 存Set集合的數(shù)據(jù) * @param isStoragePublic 是否存儲在公共區(qū)域 true 公共區(qū)域 false 私有區(qū)域 */ fun put(key: String, sets: Set<String>?, isStoragePublic: Boolean): Boolean { if (sets == null) { return false } return getMMKV(isStoragePublic).encode(key, sets) } /** * 取數(shù)據(jù),因?yàn)樗接写鎯^(qū)用的多,所以這里給了默認(rèn)參數(shù)為私有區(qū)域,如果公共區(qū)域取要記得改成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的配置
假設(shè)有國內(nèi)外以及host、h5_host環(huán)境 :
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動態(tài)切換
可以彈Dialog動態(tài)切換環(huán)境,下面為測試代碼:
//初始化 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內(nèi)找個地方彈一個Dialog動態(tài)修改下面的參數(shù)即可。 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) }
總結(jié)
一般會有4套環(huán)境: 開發(fā)環(huán)境,測試環(huán)境,預(yù)發(fā)布環(huán)境,正式環(huán)境。如果再區(qū)分國內(nèi)外則乘以2。除了base的主機(jī)一般還會引入其他主機(jī),比如h5的主機(jī),這樣會導(dǎo)致整個環(huán)境復(fù)雜多變。
剛開始是給測試打多渠道包,測試抱怨切環(huán)境,頻繁卸載安裝App很麻煩,于是做了這個優(yōu)化。上線時記得把Properties文件isRelease
設(shè)置為true,則發(fā)布的包就不會有問題,這個一般都不會忘記,風(fēng)險很小。相比存文件或者其他形式安全很多。
寫的比較匆忙,代碼略粗糙,主要體現(xiàn)思路。
以上就是Android多套環(huán)境的維護(hù)思路詳解的詳細(xì)內(nèi)容,更多關(guān)于Android多套環(huán)境維護(hù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 全局Dialog的簡單實(shí)現(xiàn)方法
本篇文章主要介紹了Android 全局Dialog的簡單實(shí)現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02Android Scroll實(shí)現(xiàn)彈性滑動_列表下拉彈性滑動的示例代碼
下面小編就為大家分享一篇Android Scroll實(shí)現(xiàn)彈性滑動_列表下拉彈性滑動的示例代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01Android ListView與ScrollView沖突的解決方法總結(jié)
這篇文章主要介紹了Android ListView與ScrollView沖突的解決方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-04-04Android自定義View實(shí)現(xiàn)圓環(huán)交替效果
這篇文章給大家介紹如何基于Android自定義View實(shí)現(xiàn)圓環(huán)交替的效果,實(shí)現(xiàn)后效果很贊,有需要的小伙伴們可以參考借鑒。2016-08-08Android?studio實(shí)現(xiàn)簡單計算器的編寫
這篇文章主要為大家詳細(xì)介紹了Android?studio實(shí)現(xiàn)簡單計算器的編寫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05Android應(yīng)用借助LinearLayout實(shí)現(xiàn)垂直水平居中布局
這篇文章主要介紹了Android應(yīng)用借助LinearLayout實(shí)現(xiàn)垂直水平居中布局的方法,文中列舉了LinearLayout線性布局下居中相關(guān)的幾個重要參數(shù),需要的朋友可以參考下2016-04-04Android基礎(chǔ)之startActivityForResult()的用法詳解
這篇文章主要給大家介紹了Android中startActivityForResult()的用法,文中給出了詳細(xì)的介紹與示例代碼,相信對大家的理解和學(xué)習(xí)具有一定參考借鑒價值,有需要的朋友們下面來一起看看吧。2017-01-01Android布局耗時監(jiān)測的三種實(shí)現(xiàn)方式
在Android應(yīng)用開發(fā)中,性能優(yōu)化是一個至關(guān)重要的方面,為了更好地監(jiān)測布局渲染的耗時,我們需要一種可靠的實(shí)現(xiàn)方案,本文將介紹三種針對Android布局耗時監(jiān)測的實(shí)現(xiàn)方案,幫助開發(fā)者及時發(fā)現(xiàn)并解決布局性能問題,需要的朋友可以參考下2024-03-03