App一鍵切換url環(huán)境、一鍵打包__Android (Java)

文 | Promise Sun


一宝冕、背景:

1. 2022上班第一天张遭,整理一下過去的工作,發(fā)現(xiàn)這方面的小知識點地梨,去年忘記記錄博客了菊卷,于是就有了這篇文章。分享給大家宝剖,希望對有需要的朋友有幫助洁闰。

2. 項目在開發(fā)調(diào)試過程中,后臺的接口域名一般會分生產(chǎn)環(huán)境万细、測試環(huán)境扑眉、自定義本地環(huán)境等等多個url地址環(huán)境,供開發(fā)人員使用,而且經(jīng)常會遇到頻繁切換url地址的情況腰素,就需要更改接口域名地址聘裁,然后AndroidStudio再重新編譯運行App,這樣就會非常麻煩弓千!
如果我們可以通過在app中直接切換環(huán)境衡便,不需要再重新運行打包,是不是就會方便很多呢洋访?

3. 在開發(fā)镣陕、打包app上線前,有時會需要手動改動很多配置變量姻政,比較麻煩呆抑,也很容易遺漏。
如果可以通過配置文件直接設(shè)置好汁展,就會避免很多問題理肺。

二、功能和方案:

1.實現(xiàn)主要功能:App一鍵切換url環(huán)境善镰、一鍵打包妹萨。
app應(yīng)用內(nèi)一鍵切換正式、測試環(huán)境炫欺,無需重新打包乎完。
包括一鍵打包,無需手動改動過多配置上線變量品洛。

2.實現(xiàn)方案:
主要通過配置本地文件的方式树姨,將所有涉及的相關(guān)變量寫在配置文件中,然后通過代碼實現(xiàn)相關(guān)功能桥状。

3.實現(xiàn)功能項目下載地址:
下載本文Demo請點擊此處

三帽揪、解決方案步驟一:基礎(chǔ)配置

1. 新建configs目錄

首先在app目錄下,新建configs文件夾辅斟,在configs下新建auto和release兩個文件目錄转晰。
auto:此文件夾中存放 開發(fā)版本使用的配置文件。
release:此文件夾中存放 線上版本使用的配置文件士飒。
注:這里的文件夾名稱是可以自定義的查邢,只要開發(fā)的代碼中也做相應(yīng)更改就沒問題。)
下圖僅供參考:

新建文件夾.jpg

2. 配置文件設(shè)置

1)auto目錄:

每個文件代表一種url環(huán)境的變量配置酵幕,環(huán)境可以相互切換扰藕。**
設(shè)置了5個配置文件(包括config.properties、configDev.properties芳撒、configPre.properties邓深、configCustom.properties未桥、configProduct.properties),大家可以根據(jù)自己的需要進行設(shè)置芥备,若不需要這么多開發(fā)環(huán)境冬耿,可以自行刪除或者增加相關(guān)配置文件。
注:以下文件中的屬性配置僅供參考门躯,大家也可以根據(jù)實際需要進行設(shè)置。)
config.properties文件:

#app 運行環(huán)境設(shè)置
#環(huán)境名
name=develop
#項目環(huán)境 url酷师,此處只是示例讶凉,需要替換成你自己的域名地址
api.base.url=http://www.reibang.com/u/d346ccc6f7a4
#是否為線上
isProduct=false
#是否顯示Log日志
isShowLog=true
#是否顯示JSON格式
isJSON=true

configDev.properties文件:

# dev環(huán)境
#環(huán)境名
name=develop
#項目環(huán)境 url,此處只是示例山孔,需要替換成你自己的
api.base.url=https://blog.csdn.net/sun_promise/dev/
#是否為線上
isProduct=false
#是否顯示Log日志
isShowLog=true
#是否顯示JSON格式
isJSON=true

configPre.properties文件:

# pre環(huán)境
#環(huán)境名
name=pre
#項目環(huán)境 url懂讯,此處只是示例,需要替換成你自己的
api.base.url=https://blog.csdn.net/sun_promise/pre/
#是否為線上
isProduct=false
#是否顯示Log日志
isShowLog=true
#是否顯示JSON格式
isJSON=true

configCustom.properties文件:

# 自定義測試環(huán)境
#環(huán)境名
name=custom
#項目環(huán)境 url台颠,此處只是示例褐望,需要替換成你自己的域名地址
api.base.url=http://192.168.xx.xx:11008/
#是否為線上
isProduct=false
#是否顯示Log日志
isShowLog=true
#是否顯示JSON格式
isJSON=true

configProduct.properties文件:

# 正式環(huán)境
#環(huán)境名
name=product
#項目環(huán)境 url,此處只是示例串前,需要替換成你自己的
api.base.url=https://blog.csdn.net/sun_promise
#是否為線上
isProduct=true
#是否顯示Log日志
isShowLog=true
#是否顯示JSON格式
isJSON=true

2)release目錄:

只有一個文件config.properties瘫里,代表線上產(chǎn)品版本,不能切換環(huán)境荡碾,此文件的配置是給線上真正的用戶使用的谨读。
config.properties文件:

# 線上環(huán)境
#環(huán)境名
name=product
#項目環(huán)境 url,此處只是示例坛吁,需要替換成你自己的
api.base.url=https://blog.csdn.net/sun_promise

#若以下配置不使用劳殖,可以刪除不設(shè)置
#是否為線上
isProduct=true
#是否顯示Log日志
isShowLog=false
#是否顯示JSON格式
isJSON=false

3. 配置build.gradle(app下的Module)

apply plugin: 'com.android.application'

android {
    compileSdk 31

    defaultConfig {
        applicationId "com.sun.urlenvconfig"
        minSdk 21
        targetSdk 31
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        // 多渠道打包,AS3.0之后:原因就是使用了productFlavors分包拨脉,
        // 解決方法就是在build.gradle中的defaultConfig中添加 一個flavorDimensions "1"就可以了哆姻,后面的1一般是跟你的versionCode相同
        // defaultConfig.versionCode
        flavorDimensions "\"${defaultConfig.versionCode}\""

        //記錄下利用buildConfigField為項目進行動態(tài)配置(對應(yīng)BuildConfig.class)
        // eg: ----- debug:打印日志,在內(nèi)網(wǎng)測試.----- release:關(guān)閉日志玫膀,外網(wǎng)矛缨,簽名等
        // 已經(jīng)通過配置文件設(shè)置了,此處可以不設(shè)置了
//        buildConfigField("boolean", "IS_PRODUCT", "\"${IS_PRODUCT}\"")
//        buildConfigField("boolean", "IS_JSON", "true")
    }

    buildTypes {
        debug {
            minifyEnabled false
            zipAlignEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//            signingConfig signingConfigs.release
        }
        release {
            minifyEnabled true
            zipAlignEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//            signingConfig signingConfigs.release
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    sourceSets {
        //開發(fā)版本使用的配置文件
        auto {
            assets.srcDirs = ['assets', 'configs/auto']
        }
        // 線上版本使用的配置文件
        product {
            assets.srcDirs = ['assets', 'configs/release']
        }
    }

    //多渠道打包
    productFlavors {
        auto {
            //可以設(shè)置app不同環(huán)境的名字 貨主測試版
            manifestPlaceholders = [app_name: "環(huán)境配置測試版"]
        }
        // 線上產(chǎn)品版本
        product {
            manifestPlaceholders = [app_name: "環(huán)境配置正式版"]
        }
    }
    // 打包時選擇帖旨,這些都是可以自己在config.properties文件中自己配置劳景,然后組合打包的。
    //    autoDebug       指定默認(rèn)測試環(huán)境碉就,有 log,可切換
    //    autoRelease     指定默認(rèn)測試環(huán)境盟广,無 log,可切換
    //    productDebug    環(huán)境,無 log,不可切換
    //    productRelease  環(huán)境瓮钥,無 log,不可切換

    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            def buildTypeName = variant.buildType.name
            def versionName = defaultConfig.versionName
            def versionCode = defaultConfig.versionCode
            // 多渠道打包的時候筋量,后臺不支持中文
            outputFileName = "envconfig-v${versionName}-${versionCode}-${buildTypeName}-${buildTime()}.apk"
        }
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    /*butterknife*/
    api 'com.jakewharton:butterknife:10.2.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0'
    //MMKV 組件
    implementation 'com.tencent:mmkv-static:1.2.7'
}

//設(shè)置默認(rèn)環(huán)境:不寫參數(shù)或者環(huán)境名錯誤烹吵,則默認(rèn)develop環(huán)境
setDefaultEnv()

def setDefaultEnv() {
    def envName = envConfig()
    def envConfigDir = "${rootDir}/app/configs/auto/"
    //def envConfigDir = "${rootDir}/app/configs/release/"
    def renameFile = "config.properties"
    println("打包接口環(huán)境:${envName}")
    task configCopy(type: Copy) {
        copy {
            delete "${envConfigDir}/${renameFile}"
            from(envConfigDir)
            into(envConfigDir)
            include(envName)
            rename(envName, renameFile)
        }
    }
}
//這里可以更改AndroidStudio的默認(rèn)運行環(huán)境: 更改envName這里對應(yīng)的值即可。
String envConfig() {
    def envName = "develop"  //默認(rèn)運行環(huán)境設(shè)置:pre桨武、custom肋拔、product、develop

    if (hasProperty("env")) {
        envName = getPropmerty("env")
    }
    println("配置環(huán)境為:${envName}")

    def envFileName = 'configDev'

    if (envName == "develop") {
        envFileName = 'configDev'
    } else if (envName == "pre") {
        envFileName = 'configPre'
    } else if (envName == "custom") {
        envFileName = 'configCustom'
    } else if (envName == "product") {
        envFileName = 'configProduct'
    }
    return envFileName + ".properties"
}

static def buildTime() {
    def date = new Date()
    def formattedDate = date.format('yyyyMMdd_HHmmss')
    return formattedDate
}

4. 配置渠道包的app名

更改清單文件的設(shè)置android:label="${app_name}"呀酸,

<application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="${app_name}"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.UrlEnvironmentConfig">

四凉蜂、解決方案步驟二:開發(fā)代碼完善功能

1. 涉及功能代碼目錄,僅供參考性誉。

功能代碼目錄.jpg

2. 初始化環(huán)境配置

MyApplication類

/**
 * @Author Promise Sun
 */
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        //SP框架要在PropertyUtils工具類之前進行初始化窿吩,如果你的項目中已有其他的SP工具類,可以直接使用
        MMKV.initialize(this);
        SpUtil.getInstance();

        //Url相關(guān)
        PropertyUtils.init(this);

        //設(shè)置打印開關(guān)
//        LogUtil.setIsLog(PropertyUtils.isShowLog());
    }
}

3. 加載配置文件工具類 PropertyUtils

/**
 * @Author Promise Sun
 */
public class PropertyUtils {

    private static Properties mProps = new Properties();
    private static boolean mHasLoadProps = false;
    private static final Object mLock = new Object();
    private static final String TAG = "PropertyUtils";

    public PropertyUtils() { }

    /**
     * 在AppApplication中初始化
     */
    public static void init(Context context) {
        if (!mHasLoadProps) {
            synchronized (mLock) {
                if (!mHasLoadProps) {
                    try {
                        //獲取環(huán)境類型
                        ConfigManager.EnvironmentType environmentType = ConfigManager.getDefault().getAppEnv();
                        //Log.e("xyh", "init: " + environmentType.configType + ".properties");
                        InputStream is = context.getAssets().open(environmentType.configType + ".properties");
                        mProps.load(is);
                        mHasLoadProps = true;
                        Log.e(TAG, "load config.properties successfully!");
                    } catch (IOException var4) {
                        Log.e(TAG, "load config.properties error!", var4);
                    }

                }
            }
        }
    }

    public static String getApiBaseUrl() {
        if (mProps == null) {
            throw new IllegalArgumentException("must call #UtilsManager.init(context) in application");
        } else {
            return mProps.getProperty(PropertyKey.BASE_URL, "");
        }
    }

    public static boolean isProduct() {
        return mProps.getProperty(PropertyKey.IS_PRODUCT, "false").equals("true");
    }

    public static boolean isJSON() {
        return mProps.getProperty(PropertyKey.IS_JSON, "false").equals("true");
    }

    public static boolean isShowLog() {
        return mProps.getProperty(PropertyKey.IS_SHOW_LOG, "false").equals("true");
    }

    public static String getEnvironmentName() {
        return mProps.getProperty(PropertyKey.NAME_ENV, "");
    }

    public static ConfigManager.EnvironmentType environmentMap() {
        String envName = getEnvironmentName();
        switch (envName) {
            case "config":
                return ConfigManager.EnvironmentType.DEV;
            case "pre":
                return ConfigManager.EnvironmentType.PRE;
            case "custom":
                return ConfigManager.EnvironmentType.CUSTOM;
            case "product":
                return ConfigManager.EnvironmentType.PRODUCT;
            default:
                return ConfigManager.EnvironmentType.DEFAULT;
        }
    }
}

4. 設(shè)置環(huán)境配置管理類ConfigManager

/**
 * @Author Promise Sun
 * 環(huán)境配置管理類
 */
public class ConfigManager {

    //當(dāng)前環(huán)境
    private EnvironmentType mCurrentEnvType;

    private static final String APP_ENV = "appEnv";

    private ConfigManager() {
    }

    public static ConfigManager getDefault() {
        return HOLDER.INSTANCE;
    }

    private static class HOLDER {
        static ConfigManager INSTANCE = new ConfigManager();
    }

    /***
     * 保存環(huán)境:指在切換環(huán)境時調(diào)用一次
     */
    public void saveAppEnv(EnvironmentType type) {
        SpUtil.setString(APP_ENV, type.configType);
    }


    /***
     * 獲取環(huán)境類型
     */
    public EnvironmentType getAppEnv() {

        if (mCurrentEnvType == null) {
           // Log.e("sun:", "FLAVOR: " + BuildConfig.FLAVOR);
            String env;
            if (GlobalConstant.AUTO.equals(BuildConfig.FLAVOR)) {
                env = SpUtil.getString(APP_ENV, EnvironmentType.DEFAULT.configType);
                if (TextUtils.isEmpty(env)) {
                    env = EnvironmentType.DEFAULT.configType;
                }
            } else {
                env = EnvironmentType.DEFAULT.configType;
            }
            mCurrentEnvType = EnvironmentType.map(env);
        }
        return mCurrentEnvType;
    }


    //環(huán)境類型
    public enum EnvironmentType {

        // 默認(rèn)環(huán)境dev   config:環(huán)境配置文件名
        DEFAULT("config"),

        // develop環(huán)境
        DEV("configDev"),

        // 自定義測試環(huán)境
        CUSTOM("configCustom"),

        // 預(yù)發(fā)布環(huán)境
        PRE("configPre"),

        // 線上環(huán)境
        PRODUCT("configProduct");

        String configType;

        EnvironmentType(String configType) {
            this.configType = configType;
        }

        public static EnvironmentType map(String configType) {
            if (TextUtils.equals(EnvironmentType.DEV.configType, configType)) {
                return EnvironmentType.DEV;
            } else if (TextUtils.equals(EnvironmentType.PRE.configType, configType)) {
                return EnvironmentType.PRE;
            } else if (TextUtils.equals(EnvironmentType.CUSTOM.configType, configType)) {
                return EnvironmentType.CUSTOM;
            } else if (TextUtils.equals(EnvironmentType.PRODUCT.configType, configType)) {
                return EnvironmentType.PRODUCT;
            } else {
                return EnvironmentType.DEFAULT;
            }
        }
    }

}

5. PropertyKey :配置文件相關(guān)屬性設(shè)置

@StringDef({PropertyKey.BASE_URL,PropertyKey.IS_PRODUCT
        ,PropertyKey.IS_SHOW_LOG,PropertyKey.IS_JSON
        , PropertyKey.NAME_ENV})
@Retention(RetentionPolicy.SOURCE)
public @interface PropertyKey {
    String NAME_ENV = "name";
    String BASE_URL = "api.base.url";
    String IS_PRODUCT = "isProduct";
    String IS_JSON = "isJSON";
    String IS_SHOW_LOG = "isShowLog";
}

6. 常量類GlobalConstant

/**
 * 常量池
 * @Author Promise Sun
 */
public class GlobalConstant {
    public static final String AUTO="auto";
}

7. SharedPreferences工具類

注:SP工具類可以使用你自己項目中已有的错览,下面這段可以忽略)

public class SpUtil {


    private static SpUtil mInstance;
    private static MMKV mv;

    private SpUtil() {
        mv = MMKV.defaultMMKV();
    }

    /**
     * 初始化MMKV,只需要初始化一次纫雁,建議在Application中初始化
     */
    public static SpUtil getInstance() {
        if (mInstance == null) {
            synchronized (SpUtil.class) {
                if (mInstance == null) {
                    mInstance = new SpUtil();
                }
            }
        }
        return mInstance;
    }

    /**
     * 保存數(shù)據(jù)的方法,我們需要拿到保存數(shù)據(jù)的具體類型倾哺,然后根據(jù)類型調(diào)用不同的保存方法
     *
     * @param key
     * @param object
     */
    public static void setFloat(String key, Object object) {
        mv.encode(key, (Float) object);
    }

    public static void setString(String key, Object object) {
        mv.encode(key, (String) object);
    }

    public static void setInt(String key, Object object) {
        mv.encode(key, (Integer) object);
    }

    public static void setDouble(String key, Object object) {
        mv.encode(key, (Double) object);
    }

    public static void setLong(String key, Object object) {
        mv.encode(key, (Long) object);
    }

    public static void setBoolean(String key, Object object) {
        mv.encode(key, (Boolean) object);
    }


    public static void setStringSet(String key, Set<String> sets) {
        mv.encode(key, sets);
    }

    public static void setParcelable(String key, Parcelable obj) {
        mv.encode(key, obj);
    }

    /**
     * 得到保存數(shù)據(jù)的方法轧邪,我們根據(jù)默認(rèn)值得到保存的數(shù)據(jù)的具體類型,然后調(diào)用相對于的方法獲取值
     */
    public static Integer getInt(String key) {
        return mv.decodeInt(key, 0);
    }

    public static Double getDouble(String key) {
        return mv.decodeDouble(key, 0.00);
    }

    public static Long getLong(String key) {
        return mv.decodeLong(key, 0L);
    }

    public static Boolean getBoolean(String key) {
        return mv.decodeBool(key, false);
    }

    public static Float getFloat(String key) {
        return mv.decodeFloat(key, 0F);
    }

    public static String getString(String key) {
        return mv.decodeString(key, "");
    }

    public static String getString(String key, String defaultValue) {
        return mv.decodeString(key, defaultValue);
    }

    public static Set<String> getStringSet(String key) {
        return mv.decodeStringSet(key, Collections.<String>emptySet());
    }

    public static Parcelable getParcelable(String key) {
        return mv.decodeParcelable(key, null);
    }

    /**
     * 移除某個key對
     *
     * @param key
     */
    public static void removeByKey(String key) {
        mv.removeValueForKey(key);
    }

    /**
     * 清除所有key
     */
    public static void removeAll() {
        mv.clearAll();
    }

    /**
     * 是否包含某個key
     */

    public static boolean containsKey(String key) {
        return mv.containsKey(key);
    }

}

五羞海、解決方案步驟三:頁面切換環(huán)境功能實現(xiàn)

1. 功能實現(xiàn)類ChangeUrlEnvActivity

(注:功能已實現(xiàn)忌愚,只是UI有點丑,大家可以自行開發(fā)設(shè)置)

/**
 * @Author Promise Sun
 */
public class ChangeUrlEnvActivity extends AppCompatActivity {

    @BindView(R.id.toolbar_left)
    RelativeLayout ShowBack;
    @BindView(R.id.tvTitle)
    TextView tvTitle;

    @BindView(R.id.tv_env)
    TextView tv_env;
    @BindView(R.id.tv_Env_Show)
    TextView tv_Env_Show;

    @BindView(R.id.group)
    RadioGroup mRadioGroup;

    @BindView(R.id.rb_test)
    RadioButton rb_test;

    @BindView(R.id.et_base_url)
    EditText et_base_url;
    @BindView(R.id.ll_set_env)
    LinearLayout ll_set_env;

    @BindView(R.id.btn_ok)
    Button btn_ok;

    private Unbinder unbinder;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_url_env_change);
        unbinder =ButterKnife.bind(this);
        initView();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbinder.unbind();
    }

    @OnClick({R.id.toolbar_left,R.id.btn_ok})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.toolbar_left:
                finish();
                break;
            default:
                break;
        }
    }

    protected void initView() {
        ShowBack.setVisibility(View.VISIBLE);
        tvTitle.setTextColor(getResources().getColor(R.color.black));
        tvTitle.setText("URL 環(huán)境");

        tv_env.setText("當(dāng)前測試環(huán)境:"+ PropertyUtils.getEnvironmentName());
        tv_Env_Show.setText( "url :" + PropertyUtils.getApiBaseUrl());

        ConfigManager.EnvironmentType environmentType = PropertyUtils.environmentMap();

        switch (environmentType) {
            case DEV:
                mRadioGroup.check(R.id.rb_dev);
                break;
            case CUSTOM:
                mRadioGroup.check(R.id.rb_test);
                break;
            case PRE:
                mRadioGroup.check(R.id.rb_pre);
                break;
            case PRODUCT:
                mRadioGroup.check(R.id.rb_product);
                break;
            default:
                mRadioGroup.check(R.id.rb_dev);
                break;
        }

        //點擊切換環(huán)境
        mRadioGroup.setOnCheckedChangeListener((group, checkedId) -> {
            switch (checkedId) {
                case R.id.rb_dev:
                    if (ConfigManager.getDefault().getAppEnv() != ConfigManager.EnvironmentType.DEV) {
                        ConfigManager.getDefault().saveAppEnv(ConfigManager .EnvironmentType.DEV);
                    }
                    setRestart();
                    break;
                case R.id.rb_pre:
                    if (ConfigManager.getDefault().getAppEnv() != ConfigManager.EnvironmentType.PRE) {
                        ConfigManager.getDefault().saveAppEnv(ConfigManager.EnvironmentType.PRE);
                    }
                    setRestart();
                    break;
                case R.id.rb_product:
                    if (ConfigManager.getDefault().getAppEnv() != ConfigManager.EnvironmentType.PRODUCT) {
                        ConfigManager.getDefault().saveAppEnv(ConfigManager.EnvironmentType.PRODUCT);
                    }
                    setRestart();
                    break;
                case R.id.rb_test:
                    if (ConfigManager.getDefault().getAppEnv() != ConfigManager.EnvironmentType.CUSTOM) {
                    ConfigManager.getDefault().saveAppEnv(ConfigManager.EnvironmentType.CUSTOM);
                    }
                    setRestart();
                    break;
            }
        });
    }

    private void setRestart() {
        Toast.makeText(this, "1s后關(guān)閉App却邓,重啟生效", Toast.LENGTH_SHORT).show();

        //退出app要進行退出登錄和去除數(shù)據(jù)相關(guān)

        // system.exit(0)菜循、finish、android.os.Process.killProcess(android.os.Process.myPid())區(qū)別:
        //可以殺死當(dāng)前應(yīng)用活動的進程申尤,這一操作將會把所有該進程內(nèi)的資源(包括線程全部清理掉)癌幕。
        //當(dāng)然,由于ActivityManager時刻監(jiān)聽著進程昧穿,一旦發(fā)現(xiàn)進程被非正常Kill勺远,它將會試圖去重啟這個進程。這就是為什么时鸵,有時候當(dāng)我們試圖這樣去結(jié)束掉應(yīng)用時胶逢,發(fā)現(xiàn)它又自動重新啟動的原因。
        //1. System.exit(0) 表示是正常退出饰潜。
        //2. System.exit(1) 表示是非正常退出初坠,通常這種退出方式應(yīng)該放在catch塊中。
        //3. Process.killProcess 或 System.exit(0)當(dāng)前進程確實也被 kill 掉了彭雾,但 app 會重新啟動碟刺。

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Process.killProcess(Process.myPid());
            }
        }, 1000);
    }
}

2. 布局文件 activity_url_env_change.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <include layout="@layout/include_toolbar" />


    <TextView
        android:id="@+id/tv_env"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:text="當(dāng)前測試環(huán)境:"
        android:textSize="18sp"
        android:textColor="#FFFFFF"
        android:textStyle="bold" />
    <TextView
        android:id="@+id/tv_Env_Show"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:textSize="18sp" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:text="切換環(huán)境,請選擇:"
        android:textSize="18sp"
        android:textStyle="bold" />

    <RadioGroup
        android:id="@+id/group"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="25dp"
        android:gravity="center">

        <RadioButton
            android:id="@+id/rb_dev"
            android:layout_width="170dp"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:padding="16dp"
            android:text="dev 環(huán)境"
            android:textStyle="bold"/>

        <RadioButton
            android:id="@+id/rb_pre"
            android:layout_width="170dp"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:padding="16dp"
            android:text="pre 環(huán)境"
            android:textStyle="bold"/>

        <RadioButton
            android:id="@+id/rb_product"
            android:layout_width="170dp"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:padding="16dp"
            android:text="prod 線上環(huán)境"
            android:textStyle="bold"/>

        <RadioButton
            android:id="@+id/rb_test"
            android:layout_width="170dp"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:padding="16dp"
            android:text="自定義測試環(huán)境"
            android:textStyle="bold"/>
    </RadioGroup>

    <LinearLayout
        android:id="@+id/ll_set_env"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:visibility="gone"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="16dp"
            android:text="手動設(shè)置測試環(huán)境:"
            android:textSize="18sp"
            android:textStyle="bold" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:background="@color/white"
            android:orientation="horizontal">

            <TextView
                android:layout_width="100dp"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="10dp"
                android:gravity="center_vertical"
                android:text="URL :"
                android:textSize="16sp" />

            <EditText
                android:id="@+id/et_base_url"
                android:layout_width="match_parent"
                android:layout_height="55dp"
                android:layout_gravity="center_vertical"
                android:layout_marginRight="10dp"
                android:layout_toLeftOf="@+id/iv_user_del"
                android:layout_toRightOf="@+id/iv_icon_username"
                android:background="#ffffffff"
                android:hint="請輸入測試環(huán)境url"
                android:maxLines="2"
                android:padding="10dp"
                android:singleLine="true"
                android:textColor="#0f30b9"
                android:textColorHint="#999999"
                android:textSize="16sp" />

        </LinearLayout>


        <Button
            android:id="@+id/btn_ok"
            android:layout_width="150dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="100dp"
            android:background="#0f30b9"
            android:text="o k"
            android:textColor="@color/white"
            android:textSize="18sp" />
    </LinearLayout>


</LinearLayout>


下載本文Demo請點擊此處


版權(quán)聲明:本文為博主原創(chuàng)文章薯酝,轉(zhuǎn)載請點贊此文并注明出處半沽,謝謝爽柒!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市者填,隨后出現(xiàn)的幾起案子浩村,更是在濱河造成了極大的恐慌,老刑警劉巖占哟,帶你破解...
    沈念sama閱讀 212,686評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件心墅,死亡現(xiàn)場離奇詭異,居然都是意外死亡榨乎,警方通過查閱死者的電腦和手機怎燥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,668評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谬哀,“玉大人刺覆,你說我怎么就攤上這事严肪∈芳澹” “怎么了?”我有些...
    開封第一講書人閱讀 158,160評論 0 348
  • 文/不壞的土叔 我叫張陵驳糯,是天一觀的道長篇梭。 經(jīng)常有香客問我,道長酝枢,這世上最難降的妖魔是什么恬偷? 我笑而不...
    開封第一講書人閱讀 56,736評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮帘睦,結(jié)果婚禮上袍患,老公的妹妹穿的比我還像新娘。我一直安慰自己竣付,他們只是感情好诡延,可當(dāng)我...
    茶點故事閱讀 65,847評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著古胆,像睡著了一般肆良。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逸绎,一...
    開封第一講書人閱讀 50,043評論 1 291
  • 那天惹恃,我揣著相機與錄音,去河邊找鬼棺牧。 笑死巫糙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颊乘。 我是一名探鬼主播曲秉,決...
    沈念sama閱讀 39,129評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼采蚀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了承二?” 一聲冷哼從身側(cè)響起榆鼠,我...
    開封第一講書人閱讀 37,872評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亥鸠,沒想到半個月后妆够,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,318評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡负蚊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,645評論 2 327
  • 正文 我和宋清朗相戀三年神妹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片家妆。...
    茶點故事閱讀 38,777評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸵荠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伤极,到底是詐尸還是另有隱情蛹找,我是刑警寧澤,帶...
    沈念sama閱讀 34,470評論 4 333
  • 正文 年R本政府宣布哨坪,位于F島的核電站庸疾,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏当编。R本人自食惡果不足惜届慈,卻給世界環(huán)境...
    茶點故事閱讀 40,126評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望忿偷。 院中可真熱鬧金顿,春花似錦、人聲如沸鲤桥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,861評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芜壁。三九已至礁凡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慧妄,已是汗流浹背顷牌。 一陣腳步聲響...
    開封第一講書人閱讀 32,095評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留塞淹,地道東北人窟蓝。 一個月前我還...
    沈念sama閱讀 46,589評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像饱普,于是被迫代替她去往敵國和親运挫。 傳聞我的和親對象是個殘疾皇子状共,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,687評論 2 351

推薦閱讀更多精彩內(nèi)容