Android在APP開發階段,實現動態的環境配置切換

語言: CN / TW / HK

​本文已參與「新人創作禮」活動,一起開啟掘金創作之路​

@[toc] 直接檢視 《Android實現動態切換環境配置4.0版本

少囉嗦,先上圖

在這裡插入圖片描述在這裡插入圖片描述 apk 安裝包地址:http://download.csdn.net/download/Silence1515/20973685

專案工程地址:http://download.csdn.net/download/Silence1515/20973713

說明

參照此專案的2.0版本 《Android實現動態切換環境配置2.0版本》

APP環境配置動態切換

  一般情況下APP在專案開發中,會配置不同的環境,比如,測試環境、準生產環境、生產環境等。不同的環境對應不同的伺服器URL地址,或者其它的一些功能開關的配置。在通常的配置條件下,我們打出一個安裝包同時也只對應的一種環境下的配置,如果在需要切換其它環境時,需要重新打包安裝測試,這樣很不方便,也浪費時間,影響開發進度。所以我們一般都會選擇在應用內新增一個,能夠實現動態的環境配置切換的功能,在應用執行期間可以隨意切換到其它環境進行測試。

  本專案中實現的動態環境配置,是相對比較通用的處理邏輯,可以通過配置的 json 檔案的調整來滿足專案需求,調整專案中需要的環境條目,同時可以檢視配置的所有引數內容。當然如果實際專案中使用肯定是有另外的需求,基本上在現有的邏輯基礎上進行少量的修改應該是可以滿足需要的,總體的邏輯框架應該是不必做大的調整。

工程結構

在這裡插入圖片描述   通過自定義的UI元件,作為一個觸發條件,如圖中的“點我”,我們簡單稱它為 配置開關View,也就是工程中的 EnvConfigShadowLayout 類, 它本身有自己定義的屬性,可以控制使用者是否可見,可見時 View是非透明的,不可見裡時,View是透明的,也就是假隱藏了;另外它的觸發條件也分為單次點選彈出配置對話方塊,和快速的連續多次點選觸發彈框兩種,暫時設定的是14次以上,點選時間間隔不小於300毫秒,否則重新計算。

  EnvConfigShadowLayout 類內部自己設定了事件,滿足觸發條件後,自行彈出彈框,這個彈框是使用 DialogFragment 實現的,所以頁面的宿主 Activity 必須是 FragmentActivity 的子類;

  我們把配置環境相關的類放在了一個獨立的 module 中 ModuleEnvConfig 裡,其它的處理業務,包括快取資料都可在這個module裡完成,也可以根據需求去調整實現;另外,把這個業務模組放在主工程裡和單獨的module裡主要是會影響是否能獲取到主專案的 BuildConfig 內的資料問題,放的位置不一樣,影響的也只是 BuildConfig 相關的資料獲取方式問題,以及與它相關的處理邏輯,其它功能業務不受影響。

應用示例

必要的初始化階段:   初始化一些必要的引數及環境配置引數; ```java package com.windfallsheng;

import android.app.Application; import com.windfallsheng.envconfig.EnvConfigHelper; import com.windfallsheng.envconfig.EnvServerUrlConsts; import com.windfallsheng.envconfig.bean.AppInfoBean; import com.windfallsheng.envconfig.envconfigexample.BuildConfig; import com.windfallsheng.lib.util.LogUtils;

/* * @Author: lzsheng * @date: 2021/5/28 16:26 * @description: / public class MyApplication extends Application {

private static Application mApplication = null;

public static Application getInstance() {
    return mApplication;
}

@Override
public void onCreate() {
    super.onCreate();
    mApplication = this;
    LogUtils.initDebug(BuildConfig.BUILD_TYPE, BuildConfig.MODE);
    // 如果配置模組是單獨放在一個 Module 中,則不能得到主工程的 BuildConfig,
    // 這裡可以使用 AppInfoBean,作為傳遞到配置模組內供使用,所以這裡必須要先賦值;
    // 這種情況下,這個賦值邏輯可以放在應用註冊的 Application 的初始化階段;
    // 如果配置模組是放在主工程內,則可以在內部直接呼叫 BuildConfig 中的值;
    AppInfoBean appInfoBean = new AppInfoBean(BuildConfig.APPLICATION_ID, BuildConfig.BUILD_TYPE, BuildConfig.FLAVOR, BuildConfig.VERSION_CODE,
            BuildConfig.VERSION_NAME, BuildConfig.LOG_DEBUG, BuildConfig.DEBUG, BuildConfig.MODE);
    LogUtils.dl("MainActivity", "method:onCreate#appInfoBean=" + appInfoBean);
    EnvConfigHelper.getInstance()
            .initContext(mApplication)
            .setAppInfoBean(appInfoBean);
    // 初始化環境配置引數;
    EnvConfigHelper.getInstance()
            .initDefaultEnv();
    // 列印下預設環境地址;
    LogUtils.dl("onCreate★★\n★★modeValue=" + EnvConfigHelper.getInstance().getModeSelected() +
            "\n★★EnvServerUrlConsts.API_SERVER=" + EnvServerUrlConsts.API_SERVER +
            ", \n★★EnvServerUrlConsts.IMAGE_SERVER=" + EnvServerUrlConsts.IMAGE_SERVER +
            ", \n★★EnvServerUrlConsts.H5_SERVER=" + EnvServerUrlConsts.H5_SERVER);
}

} ```

首先看在具體頁面上的使用:

  頁面上的應用邏輯非常簡單,只有一個配置選擇完成時的介面回撥而已,對現有頁面可能的其它的功能邏輯並沒有大的影響。   並且,MainActivity 的 onEnvConfigCheckedComplete 的介面回撥中呼叫的修改環境配置的方法: EnvConfigHelper.getInstance() .updateEnvServerUrl(modeValue, envConfigWrapper); 也可以放在 ModuleEnvConfig 模組的內部實現,在沒有其它業務需求的情況下,acitivity 就可以不必實現這個介面。   由此可以看出這個環境配置的實現對程式碼的侵入還是比較小的; ```java package com.windfallsheng;

import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import com.windfallsheng.envconfig.EnvConfigHelper; import com.windfallsheng.envconfig.EnvServerUrlConsts; import com.windfallsheng.envconfig.bean.AppInfoBean; import com.windfallsheng.envconfig.bean.EnvConfigWrapper; import com.windfallsheng.envconfig.dialog.EnvConfigCheckedCompleteListener; import com.windfallsheng.envconfig.envconfigexample.BuildConfig; import com.windfallsheng.envconfig.envconfigexample.R; import com.windfallsheng.lib.util.LogUtils;

public class MainActivity extends AppCompatActivity implements EnvConfigCheckedCompleteListener {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

@Override
public void onEnvConfigCheckedComplete(EnvConfigWrapper envConfigWrapper) {
    // 根據返回結果,處理實際專案中的業務;
    LogUtils.dl("MainActivity", "method:onConfigCompleteClicked#envConfigWrapper=" + envConfigWrapper);
    // 修改環境配置的方法可以根據需求放在 EnvConfigDialogFragment 中呼叫也可以;
    EnvConfigHelper.getInstance()
            .updateEnvServerUrl(envConfigWrapper);
}

} ```

頁面的 xml 佈局:

  UI 佈局非常簡單,只需要在頁面元素中加入 com.windfallsheng.envconfig.widget.EnvConfigShadowLayout 這個環境配置的觸發 UI 元素即可,不需要頁面去 findViewById(),不需要再設定另外的點選事件,也不需要關心其內部的實現細節,只要需要這樣一個 UI 元件就行; ```xml

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="150dp"
    android:orientation="vertical">

    <!-- 第一行內容 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:orientation="horizontal">

        <Space
            android:layout_width="16dp"
            android:layout_height="100dp" />

        <RelativeLayout
            android:id="@+id/layout_verify_coupon"
            android:layout_width="0dp"
            android:layout_height="156dp"
            android:layout_weight="1"
            android:background="@drawable/selector_common_bg_8dp_white_gray"
            android:orientation="vertical">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:gravity="center_horizontal"
                android:orientation="vertical">

                <com.windfallsheng.envconfig.widget.EnvConfigShadowLayout
                    android:id="@+id/layout_01"
                    android:layout_width="78dp"
                    android:layout_height="78dp"
                    app:is_single_click="true"
                    app:is_visible="true" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="23dp"
                    android:text="使用者可見--單次點選觸發"
                    android:textColor="@color/app_text_color"
                    android:textSize="15sp" />
            </LinearLayout>

        </RelativeLayout>

        <Space
            android:layout_width="16dp"
            android:layout_height="100dp" />

        <RelativeLayout
            android:id="@+id/layout_one_move_accomplish"
            android:layout_width="0dp"
            android:layout_height="156dp"
            android:layout_weight="1"
            android:background="@drawable/selector_common_bg_8dp_white_gray"
            android:orientation="vertical">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:gravity="center_horizontal"
                android:orientation="vertical">

                <com.windfallsheng.envconfig.widget.EnvConfigShadowLayout
                    android:id="@+id/layout_02"
                    android:layout_width="78dp"
                    android:layout_height="78dp" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="23dp"
                    android:text="使用者不可見--連續多次點選觸發"
                    android:textColor="@color/app_text_color"
                    android:textSize="15sp" />
            </LinearLayout>

        </RelativeLayout>

        <Space
            android:layout_width="16dp"
            android:layout_height="100dp" />
    </LinearLayout>

    <!-- 第二行內容 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:orientation="horizontal">

        <Space
            android:layout_width="16dp"
            android:layout_height="100dp" />

        <RelativeLayout
            android:id="@+id/layout_daily_summary"
            android:layout_width="0dp"
            android:layout_height="156dp"
            android:layout_weight="1"
            android:background="@drawable/selector_common_bg_8dp_white_gray"
            android:orientation="vertical">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:gravity="center_horizontal"
                android:orientation="vertical">

                <com.windfallsheng.envconfig.widget.EnvConfigShadowLayout
                    android:id="@+id/layout_03"
                    android:layout_width="78dp"
                    android:layout_height="78dp"
                    app:is_visible="true" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="23dp"
                    android:text="使用者可見--連續多次點選觸發"
                    android:textColor="@color/app_text_color"
                    android:textSize="15sp" />
            </LinearLayout>

        </RelativeLayout>

        <Space
            android:layout_width="16dp"
            android:layout_height="100dp" />

        <RelativeLayout
            android:id="@+id/layout_financial_management"
            android:layout_width="0dp"
            android:layout_height="156dp"
            android:layout_weight="1"
            android:background="@drawable/selector_common_bg_8dp_white_gray"
            android:orientation="vertical">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:gravity="center_horizontal"
                android:orientation="vertical">

                <com.windfallsheng.envconfig.widget.EnvConfigShadowLayout
                    android:id="@+id/layout_04"
                    android:layout_width="78dp"
                    android:layout_height="78dp"
                    app:is_single_click="true" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="23dp"
                    android:text="使用者不可見--單次點選觸發"
                    android:textColor="@color/app_text_color"
                    android:textSize="15sp" />
            </LinearLayout>

        </RelativeLayout>

        <Space
            android:layout_width="16dp"
            android:layout_height="100dp" />
    </LinearLayout>

</LinearLayout>

```

主專案中配置的 build.gradle 檔案:   配置了相關的打包環境; ```groovy plugins { id 'com.android.application' }

android { compileSdkVersion 30 buildToolsVersion "30.0.2"

defaultConfig {
    applicationId "com.windfallsheng.envconfig.envconfigexample"
    minSdkVersion 21
    targetSdkVersion 30
    versionCode 1
    versionName "1.0.0.0"

    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

signingConfigs {
    debugConfig {
        storeFile file('keystore/common_example_keystore.jks')
        storePassword 'qwert12345'
        keyAlias 'common_example'
        keyPassword 'qwert12345'
    }
    releaseConfig {
        storeFile file('keystore/common_example_keystore.jks')
        storePassword 'qwert12345'
        keyAlias 'common_example'
        keyPassword 'qwert12345'
    }
}

buildTypes {
    release {
        minifyEnabled false
        // 注意要添加簽名
        signingConfig signingConfigs.releaseConfig
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
    debug {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        applicationIdSuffix ".debug"
    }
}
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

// 指定維度 flavorDimensions "general"

productFlavors {

// def suffixStr = ".development" + rootProject.ext.android["versionCode"] // 開發環境 development { applicationIdSuffix ".development" // versionNameSuffix "-development" manifestPlaceholders = [app_name: "app開發版", icon: "@mipmap/ic_launcher"] resValue('string', 'isDebug', 'false') buildConfigField("String", "MODE", "\"development\"") buildConfigField("boolean", "LOG_DEBUG", "true") dimension "general" } // 試執行環境 pilotRun { applicationIdSuffix ".pilotRun" // versionNameSuffix "-pilotRun" manifestPlaceholders = [app_name: "app試執行版", icon: "@mipmap/ic_launcher"] resValue('string', 'isDebug', 'false') buildConfigField("String", "MODE", "\"pilotRun\"") buildConfigField("boolean", "LOG_DEBUG", "true") dimension "general" } //生產環境 product { manifestPlaceholders = [app_name: "app生產版", icon: "@mipmap/ic_launcher"] resValue('string', 'isDebug', 'false') buildConfigField("String", "MODE", "\"product\"") buildConfigField("boolean", "LOG_DEBUG", "false") dimension "general" } } // 批量打包,studio3.0以上這麼寫: android.applicationVariants.all { variant -> // if (variant.buildType.name == 'release') variant.outputs.all { output -> // def fileName = "${applicationId}${variant.productFlavors[0].name}_v${variant.versionName}${variant.buildType.name}${buildTime()}.apk" def fileName = "${applicationId}${variant.productFlavors[0].name}${variant.buildType.name}_v${variant.versionName}${buildTime()}.apk" println "print apk name=" + fileName //修改apk檔名稱 outputFileName = fileName } }

// studio3.0以下這麼寫 // applicationVariants.all { variant -> // variant.outputs.all { output -> // def fileName = "schoolpayment_${variant.productFlavors[0].name}v${variant.versionName}${variant.buildType.name}_${buildTime()}.apk" // def outputFile = output.outputFile // println "print apk name:" + fileName // if (outputFile != null && outputFile.name.endsWith('.apk')) { // //修改apk檔名稱 // outputFileName = fileName //// output.outputFile = new File(outputFile.parent, fileName) // } // } // } }

//獲取編譯時間 static def buildTime() { // return new Date().format("yyyy年MM月dd日HH時mm分ss秒SSS", TimeZone.getTimeZone("GMT+08:00")) return new Date().format("yyyyMMdd-HHmmss-SSS", TimeZone.getTimeZone("GMT+08:00")) }

dependencies {

implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation project(path: ':ModuleEnvConfig')
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

} ```

核心業務實現

  自定義的 EnvConfigShadowLayout 的實現過程: 實現了彈框的觸發條件,及自定義屬性,實現可以靈活控制相關業務; ```java package com.windfallsheng.envconfig.widget;

import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.text.TextUtils; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import androidx.fragment.app.FragmentActivity; import com.windfallsheng.envconfig.EnvConfigHelper; import com.windfallsheng.envconfig.R; import com.windfallsheng.envconfig.bean.AppInfoBean; import com.windfallsheng.envconfig.dialog.EnvConfigDialogFragment; import com.windfallsheng.lib.util.LogUtils;

/* * @Author: lzsheng * @date: 2021/8/6 17:15 * @description: / public class EnvConfigShadowLayout extends RelativeLayout {

/**
 * 連續點選次數累計到觸發彈框的上限值
 */
private final int LIMIT_CLICK_COUNT = 14;
/**
 * 連續點選次數的間隔時間
 */
private final long MIN_TIME_INTERVAL = 300;
private Context mContext;
private View mRootView;
private TextView mTvClickIt;
private boolean isSingleClick;
private long mLastClickTimeMillis;
private int mClickCount;

public EnvConfigShadowLayout(Context context) {
    super(context);
    init(context, null, 0);
}

public EnvConfigShadowLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs, 0);
}

public EnvConfigShadowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context, attrs, defStyleAttr);
}

private void init(Context context, AttributeSet attrs, int defStyleAttr) {
    this.mContext = context;
    mRootView = LayoutInflater.from(context).inflate(R.layout.env_config_layout_env_config_shadow, null);

    mTvClickIt = mRootView.findViewById(R.id.tv_click_it);

// mRootView.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); addView(mRootView);

    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.env_config_shadow_layout);
    // 使用者是否可見
    boolean isVisible = typedArray.getBoolean(R.styleable.env_config_shadow_layout_is_visible, false);
    isSingleClick = typedArray.getBoolean(R.styleable.env_config_shadow_layout_is_single_click, false);
    typedArray.recycle();
    if (isVisible) {
        // 使用者是否可見
        mTvClickIt.setTextColor(Color.parseColor("#FA8166"));
        mRootView.setBackgroundColor(Color.parseColor("#F2F2F2"));
    }

// else { // mTvClickIt.setTextColor(Color.parseColor("#0000")); // mRootView.setBackgroundColor(Color.parseColor("#0000")); // }

    mTvClickIt.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            LogUtils.dl("onClick#mTvClickIt");
            if (isSingleClick) {
                // 顯示彈框
                showEnvConfigDialog();
                return;
            }
            long currentTimeMillis = System.currentTimeMillis();
            if (mLastClickTimeMillis > 0) {
                long timeInterval = currentTimeMillis - mLastClickTimeMillis;
                if (timeInterval < MIN_TIME_INTERVAL) {
                    mClickCount++;
                } else {
                    resetCounter();
                }
            } else {
                // 首次點選;
                mClickCount++;
            }
            LogUtils.dl("onClick#mClickCount=" + mClickCount);

            if (mClickCount >= LIMIT_CLICK_COUNT) {
                // 顯示彈框
                showEnvConfigDialog();
                //
                resetCounter();
            } else {
                mLastClickTimeMillis = currentTimeMillis;
            }
        }
    });
}

private void showEnvConfigDialog() {
    AppInfoBean appInfoBean = EnvConfigHelper.getInstance().getAppInfoBean();
    if (appInfoBean == null) {
        Toast.makeText(mContext, "appInfoBean = null", Toast.LENGTH_LONG).show();
        return;
    }
    String modeOfBuild = appInfoBean.getModeOfBuild();
    if (TextUtils.isEmpty(modeOfBuild)) {
        Toast.makeText(mContext, "modeOfBuild 為空", Toast.LENGTH_LONG).show();
        return;
    }

// String modeBuild = BuildConfig.MODE; // 構建的包是生產和試執行環境時,暫時不支援修改地址;這個業務功能可以根據需求調整是否限制; if (TextUtils.equals(modeOfBuild, EnvConfigHelper.EnvMode.PILOT_RUN.getMode()) || TextUtils.equals(modeOfBuild, EnvConfigHelper.EnvMode.PRODUCT.getMode())) { LogUtils.wl("method:showEnvConfigDialog#return#modeOfBuild=" + modeOfBuild); Toast.makeText(mContext, modeOfBuild + "環境暫不支援修改環境配置", Toast.LENGTH_LONG).show(); return; } EnvConfigDialogFragment envConfigDialogFragment = EnvConfigDialogFragment.newInstance(); if (mContext instanceof FragmentActivity) { FragmentActivity activity = (FragmentActivity) mContext; envConfigDialogFragment.show(activity.getSupportFragmentManager(), "envConfigDialogFragment"); } else { Toast.makeText(mContext, "Context not instance of FragmentActivity.", Toast.LENGTH_LONG).show(); LogUtils.wl("method:showEnvConfigDialog#Context not instance of FragmentActivity."); } }

private void resetCounter() {
    mClickCount = 0;
    mLastClickTimeMillis = 0;
}

} &emsp;&emsp;**自定義的EnvConfigDialogFragment 配置彈框的實現過程:** 實現了頁面的UI效果,同時處理了不同配置項被選中時的業務,重置配置引數的業務,及完成後儲存資料的業務;java package com.windfallsheng.envconfig.dialog;

import android.app.Dialog; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast;

import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView;

import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.windfallsheng.envconfig.EnvConfigHelper; import com.windfallsheng.envconfig.EnvServerUrlConsts; import com.windfallsheng.envconfig.R; import com.windfallsheng.envconfig.bean.AllServerUrlDetailsBean; import com.windfallsheng.envconfig.bean.AppInfoBean; import com.windfallsheng.envconfig.bean.EnvConfigBean; import com.windfallsheng.envconfig.bean.EnvConfigWrapper; import com.windfallsheng.envconfig.bean.ServerUrlDetailsBean; import com.windfallsheng.envconfig.widget.UrlDetailsInputLayout; import com.windfallsheng.lib.Consts; import com.windfallsheng.lib.util.DisplayHelper; import com.windfallsheng.lib.util.LogUtils; import com.windfallsheng.lib.util.ResUtils; import com.windfallsheng.lib.util.SharedPrefsManager;

import java.util.ArrayList; import java.util.List;

/* * @Author: lzsheng * @CreateDate: 2020/12/16 14:54 * @LastModifiedTime: 2020/12/16 14:54 * @Description: / public class EnvConfigDialogFragment extends BaseDialogFragment {

public static final int VIEW_TYPE_SERVER_URL_DETAILS = 1;
public static final int VIEW_TYPE_AD = 2;
public static final int VIEW_TYPE_WEB = 3;
public static final int VIEW_TYPE_FIRST_INSTALL = 4;
public static final int VIEW_TYPE_LOG_DEBUG = 5;
private static final String MODE_VALUE_LAST_SELECTED = "mode_value_last_selected";
private static final String ENV_CONFIG_WRAPPER_DEFAULT = "env_config_wrapper_default";
private final boolean CHECKED = true;
private final boolean UNCHECKED = false;
private final boolean REQUIRED = true;
private final boolean UNREQUIRED = false;
private final boolean GROUP = true;
private final boolean NO_GROUP = false;
private final String GROUP_ID_SERVER_URL_DETAILS = "server_url_details";
private final String GROUP_ID_SKIP_AD_PAGE = "skip_ad_page";
private final String GROUP_ID_FIRST_INSTALL = "first_install";
private final String GROUP_ID_LOG_DEBUG = "log_debug";
private final String TAG = "AppConfigDialogFragment";
/**
 * 根據需要修改配置資料的json串;
 */
private final String configJsonAllServerDetails = "[" +
        "{" +
        "\"configId\":\"dev_test_server_url_details\"," +
        "\"groupId\":\"" + GROUP_ID_SERVER_URL_DETAILS + "\"," +
        "\"modeOfEnv\":\"" + EnvConfigHelper.EnvMode.DEV_TEST_SERVER.getMode() + "\"," +
        "\"labelName\":\"測試環境(測試伺服器)\"," +
        "\"isChecked\":" + CHECKED + "," +
        "\"isRequired\":" + REQUIRED + "," +
        "\"isGroup\":" + GROUP + "," +
        "\"viewType\":" + VIEW_TYPE_SERVER_URL_DETAILS + "," +

        "\"apiServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"api\"," +
        "\"typeName\":\"API伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.TestServer.API_SERVER + "\"" +
        "}," +
        "\"imageServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"image\"," +
        "\"typeName\":\"image伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.TestServer.IMAGE_SERVER + "\"" +
        "}," +
        "\"h5ServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"h5\"," +
        "\"typeName\":\"H5伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.TestServer.H5_SERVER + "\"" +
        "}" +

        "}," +
        "{" +
        "\"configId\":\"dev_test_local_server_url_details\"," +
        "\"groupId\":\"" + GROUP_ID_SERVER_URL_DETAILS + "\"," +
        "\"labelName\":\"本地測試環境(本地地址)\"," +
        "\"modeOfEnv\":\"" + EnvConfigHelper.EnvMode.DEV_TEST_LOCAL.getMode() + "\"," +
        "\"isChecked\":" + UNCHECKED + "," +
        "\"isRequired\":" + REQUIRED + "," +
        "\"isGroup\":" + GROUP + "," +
        "\"viewType\":" + VIEW_TYPE_SERVER_URL_DETAILS + "," +

        "\"apiServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"api\"," +
        "\"typeName\":\"API伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.TestLocal.API_SERVER + "\"" +
        "}," +
        "\"imageServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"image\"," +
        "\"typeName\":\"image伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.TestLocal.IMAGE_SERVER + "\"" +
        "}," +
        "\"h5ServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"h5\"," +
        "\"typeName\":\"H5伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.TestLocal.H5_SERVER + "\"" +
        "}" +

        "}," +
        "{" +
        "\"configId\":\"dev_pilot_server_url_details\"," +
        "\"groupId\":\"" + GROUP_ID_SERVER_URL_DETAILS + "\"," +
        "\"labelName\":\"試執行環境\"," +
        "\"modeOfEnv\":\"" + EnvConfigHelper.EnvMode.DEV_PILOT_RUN.getMode() + "\"," +
        "\"isChecked\":" + UNCHECKED + "," +
        "\"isRequired\":" + REQUIRED + "," +
        "\"isGroup\":" + GROUP + "," +
        "\"viewType\":" + VIEW_TYPE_SERVER_URL_DETAILS + "," +

        "\"apiServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"api\"," +
        "\"typeName\":\"API伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.PilotRun.API_SERVER + "\"" +
        "}," +
        "\"imageServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"image\"," +
        "\"typeName\":\"image伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.PilotRun.IMAGE_SERVER + "\"" +
        "}," +
        "\"h5ServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"h5\"," +
        "\"typeName\":\"H5伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.PilotRun.H5_SERVER + "\"" +
        "}" +

        "}," +
        "{" +
        "\"configId\":\"dev_product_server_url_details\"," +
        "\"groupId\":\"" + GROUP_ID_SERVER_URL_DETAILS + "\"," +
        "\"modeOfEnv\":\"" + EnvConfigHelper.EnvMode.DEV_PRODUCT.getMode() + "\"," +
        "\"labelName\":\"生產環境\"," +
        "\"isChecked\":" + UNCHECKED + "," +
        "\"isRequired\":" + REQUIRED + "," +
        "\"isGroup\":" + GROUP + "," +
        "\"viewType\":" + VIEW_TYPE_SERVER_URL_DETAILS + "," +

        "\"apiServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"api\"," +
        "\"typeName\":\"API伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.Product.API_SERVER + "\"" +
        "}," +
        "\"imageServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"image\"," +
        "\"typeName\":\"image伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.Product.IMAGE_SERVER + "\"" +
        "}," +
        "\"h5ServerUrlDetails\":" +
        "{" +
        "\"typeId\":\"h5\"," +
        "\"typeName\":\"H5伺服器地址\"," +
        "\"urlStr\":\"" + EnvServerUrlConsts.Product.H5_SERVER + "\"" +
        "}" +

        "}" +
        "]";

/**
 * 根據需要修改配置資料的json串;
 */
private final String configJsonSimple = "[" +
        "{" +
        "\"configId\":\"first_install\"," +
        "\"groupId\":\"" + GROUP_ID_FIRST_INSTALL + "\"," +
        "\"labelName\":\"是否為首次安裝啟動(選中代表是)\"," +
        "\"isChecked\":" + UNCHECKED + "," +
        "\"isRequired\":" + UNREQUIRED + "," +
        "\"isGroup\":" + NO_GROUP + "," +
        "\"viewType\":" + VIEW_TYPE_FIRST_INSTALL +
        "}," +
        "{" +
        "\"configId\":\"log_debug\"," +
        "\"groupId\":\"" + GROUP_ID_LOG_DEBUG + "\"," +
        "\"labelName\":\"是否關閉Log日誌(選中代表關閉)\"," +
        "\"isChecked\":" + UNCHECKED + "," +
        "\"isRequired\":" + UNREQUIRED + "," +
        "\"isGroup\":" + NO_GROUP + "," +
        "\"viewType\":" + VIEW_TYPE_LOG_DEBUG +
        "}," +
        "{" +
        "\"configId\":\"skip_ad_page\"," +
        "\"groupId\":\"" + GROUP_ID_SKIP_AD_PAGE + "\"," +
        "\"labelName\":\"是否跳過廣告頁(選中代表是)\"," +
        "\"isChecked\":" + UNCHECKED + "," +
        "\"isRequired\":" + UNREQUIRED + "," +
        "\"isGroup\":" + NO_GROUP + "," +
        "\"viewType\":" + VIEW_TYPE_AD +
        "}" +
        "]";
private View rootView;
private RecyclerView mRecyclerView;
private TextView tvCheckDetails;
private TextView tvEnvDetails;
private TextView tvFinish;
private TextView tvResetAllConfig;
private EnvConfigAdapter mEnvConfigAdapter;
private List<EnvConfigBean> mEnvConfigList;
/**
 * 手動輸入URL的UI部分;
 */
private UrlDetailsInputLayout mUrlInputLayoutApi;
private UrlDetailsInputLayout mUrlInputLayoutImage;
private UrlDetailsInputLayout mUrlInputLayoutH5;

/**
 * 傳入的配置項或者上次快取的配置項;
 */
private EnvConfigWrapper mEnvConfigWrapperDefault;
private EnvConfigCheckedCompleteListener mEnvConfigCheckedCompleteListener;

public static EnvConfigDialogFragment newInstance() {
    return newInstance(null);
}

public static EnvConfigDialogFragment newInstance(EnvConfigWrapper envConfigWrapper) {
    EnvConfigDialogFragment fragment = new EnvConfigDialogFragment();
    Bundle args = new Bundle();
    args.putSerializable(ENV_CONFIG_WRAPPER_DEFAULT, envConfigWrapper);
    fragment.setArguments(args);
    return fragment;
}

@Override
public void onAttach(Context context) {
    mContext = context;
    super.onAttach(context);
    if (getArguments() != null) {
        mEnvConfigWrapperDefault = (EnvConfigWrapper) getArguments().getSerializable(ENV_CONFIG_WRAPPER_DEFAULT);
    }
    // 如果入參沒有配置項,則檢視有沒有本地快取;
    if (mEnvConfigWrapperDefault == null) {
        // 暫時使用 SharedPreferences 作為本地快取,根據實際需要選擇處理方式;
        mEnvConfigWrapperDefault = EnvConfigHelper.getInstance().initEnvConfigWrapper();
    }

    LogUtils.dl(TAG, "method:onAttach#mEnvConfigWrapperDefault=" + mEnvConfigWrapperDefault);
    try {
        mEnvConfigCheckedCompleteListener = (EnvConfigCheckedCompleteListener) context;
    } catch (ClassCastException e) {
        LogUtils.el(TAG, "method:onAttach#exception=" + e.getMessage());
    }
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
    rootView = inflater.inflate(R.layout.env_config_layout_fragment_dialog, container, false);
    mRecyclerView = rootView.findViewById(R.id.recyclerview_config);
    tvCheckDetails = rootView.findViewById(R.id.tv_check_details);
    tvEnvDetails = rootView.findViewById(R.id.tv_env_config_details);
    tvFinish = rootView.findViewById(R.id.tv_finish);
    tvResetAllConfig = rootView.findViewById(R.id.tv_reset_config);
    mUrlInputLayoutApi = rootView.findViewById(R.id.url_details_layout_api);
    mUrlInputLayoutImage = rootView.findViewById(R.id.url_details_layout_image);
    mUrlInputLayoutH5 = rootView.findViewById(R.id.url_details_layout_h5);
    return rootView;
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // 調整佈局的寬高
    ViewGroup.LayoutParams layoutParams = rootView.getLayoutParams();
    layoutParams.height = (int) (DisplayHelper.getScreenHeight(mContext) * 0.8);
    layoutParams.width = (int) (DisplayHelper.getScreenWidth(mContext) * 0.8);
    rootView.setLayoutParams(layoutParams);
    FrameLayout parent = (FrameLayout) rootView.getParent();
    parent.setBackgroundResource(R.color.transparent);

    LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(mContext);
    mRecyclerView.setLayoutManager(mLinearLayoutManager);
    mRecyclerView.setNestedScrollingEnabled(false);

    mEnvConfigList = initListDataFromJson();
    // 如果最近一次的選中資料,則修改預設選中項;
    initEnvConfigListDataAndViews(mEnvConfigList);

    mEnvConfigAdapter = new EnvConfigAdapter();
    mEnvConfigAdapter.addAll(mEnvConfigList);
    mRecyclerView.setAdapter(mEnvConfigAdapter);
    // 行點選事件
    mEnvConfigAdapter.setOnRecyclerViewItemClick((EnvConfigAdapter.OnRecyclerViewItemClick<EnvConfigBean>)
            (childView, position, envConfigBean) -> {
                updateListData(position, envConfigBean);
                mEnvConfigAdapter.notifyDataSetChanged();
            });
    // 重置配置項事件;
    tvResetAllConfig.setOnClickListener(v -> {
        handleResetAllConfig();
    });

    tvCheckDetails.setOnClickListener(v -> {
        showAllConfigDetails();
    });

    tvFinish.setOnClickListener(v -> handlerFinishClicked());
}

private List<EnvConfigBean> initListDataFromJson() {
    Gson gson = new Gson();
    List<EnvConfigBean> list = new ArrayList<>();
    // 可以選擇從 default_config_server_url_details.json 中讀取配置;
    List<AllServerUrlDetailsBean> serverList = gson.fromJson(configJsonAllServerDetails, new TypeToken<List<AllServerUrlDetailsBean>>() {
    }.getType());
    list.addAll(serverList);
    List<EnvConfigBean> cofigList = gson.fromJson(configJsonSimple, new TypeToken<List<EnvConfigBean>>() {
    }.getType());
    list.addAll(cofigList);
    return list;
}

private List<EnvConfigBean> getListDataFromJson() {
    List<EnvConfigBean> list = null;
    try {
        String jsonStr = ResUtils.getJsonStr(mContext, "default_config_server_url_details.json");
        Gson gson = new Gson();
        list = gson.fromJson(jsonStr, new TypeToken<List<EnvConfigBean>>() {
        }.getType());
    } catch (Exception e) {
        LogUtils.el(TAG, e.getMessage());
    }
    return list;
}

private void showAllConfigDetails() {
    EnvConfigWrapper envConfigWrapper = buildEnvConfigWrapper();

// String modeValueBuild = envConfigWrapper.getModeOfBuild(); AllServerUrlDetailsBean allServerUrlDetailsOfCheckedEnv = envConfigWrapper.getAllServerUrlDetailsOfCheckedEnv();

    StringBuilder builder = new StringBuilder();
    //
    AppInfoBean appInfoBean = EnvConfigHelper.getInstance().getAppInfoBean();
    if (appInfoBean != null) {
        builder
                .append("★★BuildConfig部分:").append("\n\n")
                .append("★★BuildConfig#APPLICATION_ID=").append(appInfoBean.getApplicationId()).append("\n\n")
                .append("★★BuildConfig#VERSION_CODE=").append(appInfoBean.getVersionCode()).append("\n\n")
                .append("★★BuildConfig#VERSION_NAME=").append(appInfoBean.getVersionName()).append("\n\n")
                .append("★★BuildConfig#BUILD_TYPE=").append(appInfoBean.getBuildType()).append("\n\n")
                .append("★★BuildConfig#FLAVOR=").append(appInfoBean.getFlavor()).append("\n\n")
                .append("★★BuildConfig#DEBUG=").append(appInfoBean.isDebug()).append("\n\n")
                .append("★★BuildConfig#LOG_DEBUG=").append(appInfoBean.isLogDebug()).append("\n\n")
                .append("★★打包模式modeOfBuild=").append(appInfoBean.getModeOfBuild()).append("\n\n");

    } else {
        LogUtils.el(TAG, "method:showAllConfigDetails#EnvConfigHelper#mAppInfoBean not initialize.");
    }

// 實際的環境情況: builder .append("★★當前使用的地址:").append("\n\n") .append("★★api伺服器地址:").append(EnvServerUrlConsts.API_SERVER).append("\n\n") .append("★★image伺服器地址:").append(EnvServerUrlConsts.IMAGE_SERVER).append("\n\n") .append("★★H5伺服器地址:").append(EnvServerUrlConsts.H5_SERVER).append("\n\n"); // builder // .append("BuildConfig部分:").append("\n\n") // .append("BuildConfig#APPLICATION_ID=").append(BuildConfig.APPLICATION_ID).append("\n\n") // .append("BuildConfig#VERSION_CODE=").append(BuildConfig.VERSION_CODE).append("\n\n") // .append("BuildConfig#VERSION_NAME=").append(BuildConfig.VERSION_NAME).append("\n\n") // .append("BuildConfig#BUILD_TYPE=").append(BuildConfig.BUILD_TYPE).append("\n\n") // .append("BuildConfig#FLAVOR=").append(BuildConfig.FLAVOR).append("\n\n") // .append("BuildConfig#DEBUG=").append(BuildConfig.DEBUG).append("\n\n") // .append("BuildConfig#LOG_DEBUG=").append(BuildConfig.LOG_DEBUG).append("\n\n") .append("★★修改配置後部分:").append("\n\n");

    if (allServerUrlDetailsOfCheckedEnv != null) {
        builder
                .append("★★").append(allServerUrlDetailsOfCheckedEnv.getLabelName()).append("=").append(allServerUrlDetailsOfCheckedEnv.getChecked()).append("\n\n")
                .append("★★").append("modeOfCurrentEnv=").append(allServerUrlDetailsOfCheckedEnv.getModeOfEnv()).append("\n\n");

        ServerUrlDetailsBean apiDetailsSelected = envConfigWrapper.getApiServerUrlDetailsInput();
        // 手動輸入且被選中的URL的優先順序高於環境配置內的URL地址,優先使用手動輸入的URL
        if (apiDetailsSelected == null || !apiDetailsSelected.isChecked()) {
            apiDetailsSelected = allServerUrlDetailsOfCheckedEnv.getApiServerUrlDetails();
        }
        if (apiDetailsSelected != null) {
            builder
                    .append("★★").append(apiDetailsSelected.getTypeName()).append("=").append(apiDetailsSelected.getUrlStr()).append("\n\n");
        }
        ServerUrlDetailsBean imageDetailsSelected = envConfigWrapper.getImageServerUrlDetailsInput();
        if (imageDetailsSelected == null || !imageDetailsSelected.isChecked()) {
            imageDetailsSelected = allServerUrlDetailsOfCheckedEnv.getImageServerUrlDetails();
        }
        if (imageDetailsSelected != null) {
            builder
                    .append("★★").append(imageDetailsSelected.getTypeName()).append("=").append(imageDetailsSelected.getUrlStr()).append("\n\n");
        }
        ServerUrlDetailsBean h5DetailsSelected = envConfigWrapper.getH5ServerUrlDetailsInput();
        if (h5DetailsSelected == null || !h5DetailsSelected.isChecked()) {
            h5DetailsSelected = allServerUrlDetailsOfCheckedEnv.getH5ServerUrlDetails();
        }
        if (h5DetailsSelected != null) {
            builder
                    .append("★★").append(h5DetailsSelected.getTypeName()).append("=").append(h5DetailsSelected.getUrlStr()).append("\n\n");
        }
    }

    EnvConfigBean firstInstall = envConfigWrapper.getFirstInstall();
    if (firstInstall != null) {
        builder
                .append("★★").append(firstInstall.getLabelName()).append("=").append(firstInstall.getChecked()).append("\n\n");
    }
    EnvConfigBean logDebug = envConfigWrapper.getLogDebug();
    if (logDebug != null) {
        builder
                .append("★★").append(logDebug.getLabelName()).append("=").append(logDebug.getChecked()).append("\n\n");
    }
    EnvConfigBean skipAdPage = envConfigWrapper.getSkipAdPage();
    if (skipAdPage != null) {
        builder
                .append("★★").append(skipAdPage.getLabelName()).append("=").append(skipAdPage.getChecked()).append("\n\n");
    }
    String allConfigDetailsStr = builder.toString();
    LogUtils.dl(TAG, "method:showAllConfigDetails#allConfigDetailsStr=" + allConfigDetailsStr);

    tvEnvDetails.setText(allConfigDetailsStr);
}

/**
 * 重置所有配置項及UI效果;
 */
private void handleResetAllConfig() {
    SharedPrefsManager.getInstance()
            .init(mContext, Consts.ENV_CONFIG.ENV_CONFIG_SP)
            .removeKey(Consts.ENV_CONFIG.ENV_CONFIG_WRAPPER_JSON);

    mEnvConfigWrapperDefault = null;
    mEnvConfigList = initListDataFromJson();
    //
    initEnvConfigListDataAndViews(mEnvConfigList);
    mEnvConfigAdapter.addNewAll(mEnvConfigList);

    mEnvConfigAdapter.notifyDataSetChanged();

    if (mUrlInputLayoutApi != null) {
        mUrlInputLayoutApi.resetData();
    }
    if (mUrlInputLayoutImage != null) {
        mUrlInputLayoutImage.resetData();
    }
    if (mUrlInputLayoutH5 != null) {
        mUrlInputLayoutH5.resetData();
    }

    tvCheckDetails.performClick();
}

/**
 * 如果最近一次的選中資料有值,則修改預設選中項,如果沒有則按預設項處理;
 *
 * @param envConfigBeanList
 */
private void initEnvConfigListDataAndViews(List<EnvConfigBean> envConfigBeanList) {
    if (mEnvConfigWrapperDefault == null) {
        // 當前邏輯分支和json中的預設配置相同,可以不必再執行;
        // 沒有設定預設的配置時,根據構建環境進行預設處理;
        handleInitEnvConfigListDataAndViews(envConfigBeanList);
    } else {
        // 有預設配置項時
        handleInitEnvConfigListDataWithDefault(envConfigBeanList);
    }
}

private void handleInitEnvConfigListDataWithDefault(List<EnvConfigBean> envConfigBeanList) {
    AllServerUrlDetailsBean allServerUrlDetailsOfCheckedEnv = mEnvConfigWrapperDefault.getAllServerUrlDetailsOfCheckedEnv();
    EnvConfigBean envConfigFirstInstall = mEnvConfigWrapperDefault.getFirstInstall();
    EnvConfigBean envConfigLogDebug = mEnvConfigWrapperDefault.getLogDebug();
    EnvConfigBean envConfigSkipAdPage = mEnvConfigWrapperDefault.getSkipAdPage();
    String modeValueLast = allServerUrlDetailsOfCheckedEnv.getModeOfEnv();
    for (EnvConfigBean envConfigBean : envConfigBeanList) {
        String groupId = envConfigBean.getGroupId();
        switch (groupId) {
            case GROUP_ID_SERVER_URL_DETAILS:
                if (envConfigBean instanceof AllServerUrlDetailsBean) {
                    AllServerUrlDetailsBean allServerUrlDetailsBean = (AllServerUrlDetailsBean) envConfigBean;
                    String modeValue = allServerUrlDetailsBean.getModeOfEnv();
                    if (TextUtils.equals(modeValueLast, modeValue)) {
                        envConfigBean.setChecked(CHECKED);
                    } else {
                        envConfigBean.setChecked(UNCHECKED);
                    }
                } else {
                    LogUtils.el(TAG, "method:initEnvConfigListDataAndViews#envConfigBean not a instance of AllServerUrlDetailsBean.");
                }
                break;
            case GROUP_ID_FIRST_INSTALL:
                if (envConfigFirstInstall != null) {
                    envConfigBean.setChecked(envConfigBean.getChecked());
                }
                break;
            case GROUP_ID_LOG_DEBUG:
                if (envConfigLogDebug != null) {
                    envConfigBean.setChecked(envConfigLogDebug.getChecked());
                }
                break;
            case GROUP_ID_SKIP_AD_PAGE:
                if (envConfigSkipAdPage != null) {
                    envConfigBean.setChecked(envConfigSkipAdPage.getChecked());
                }
                break;
            default:
                LogUtils.el(TAG, "method:initEnvConfigListData#未處理當前邏輯分支");
                break;
        }
    }

    ServerUrlDetailsBean apiDetailsInput = mEnvConfigWrapperDefault.getApiServerUrlDetailsInput();
    if (apiDetailsInput != null) {
        mUrlInputLayoutApi.setChecked(apiDetailsInput.isChecked());
        mUrlInputLayoutApi.setUrlInput(apiDetailsInput.getUrlStr());
    }
    ServerUrlDetailsBean imageDetailsInput = mEnvConfigWrapperDefault.getImageServerUrlDetailsInput();
    if (imageDetailsInput != null) {
        mUrlInputLayoutImage.setChecked(imageDetailsInput.isChecked());
        mUrlInputLayoutImage.setUrlInput(imageDetailsInput.getUrlStr());
    }
    ServerUrlDetailsBean h5DetailsInput = mEnvConfigWrapperDefault.getH5ServerUrlDetailsInput();
    if (h5DetailsInput != null) {
        mUrlInputLayoutH5.setChecked(h5DetailsInput.isChecked());
        mUrlInputLayoutH5.setUrlInput(h5DetailsInput.getUrlStr());
    }
}

private void handleInitEnvConfigListDataAndViews(List<EnvConfigBean> envConfigBeanList) {
    AppInfoBean appInfoBean = EnvConfigHelper.getInstance().getAppInfoBean();
    if (appInfoBean == null) {
        Toast.makeText(mContext, "appInfoBean = null", Toast.LENGTH_LONG).show();
        return;
    }
    String modeOfBuild = appInfoBean.getModeOfBuild();
    if (TextUtils.isEmpty(modeOfBuild)) {
        Toast.makeText(mContext, "modeOfBuild 為空", Toast.LENGTH_LONG).show();
        return;
    }

// String modeBuild = BuildConfig.MODE; for (EnvConfigBean envConfigBean : envConfigBeanList) { String groupId = envConfigBean.getGroupId(); switch (groupId) { case GROUP_ID_SERVER_URL_DETAILS: if (envConfigBean instanceof AllServerUrlDetailsBean) { handServerUrlDetalsDefault(modeOfBuild, envConfigBean); } else { LogUtils.el(TAG, "method:initEnvConfigListDataAndViews#envConfigBean not a instance of AllServerUrlDetailsBean."); } break; case GROUP_ID_FIRST_INSTALL: case GROUP_ID_LOG_DEBUG: case GROUP_ID_SKIP_AD_PAGE: // 暫時按預設狀態處理; // String modePilotRun = EnvConfigHelper.EnvMode.DEV_PILOT_RUN.getMode(); // if (TextUtils.equals(modeBuild, modePilotRun) || // TextUtils.equals(modeBuild, modeDevPilotRun)) { // envConfigBean.setChecked(CHECKED); // } else { // envConfigBean.setChecked(UNCHECKED); // } break; default: LogUtils.el(TAG, "method:initEnvConfigListData#未處理當前邏輯分支"); break; } } }

private void handServerUrlDetalsDefault(String modeBuild, EnvConfigBean envConfigBean) {
    AllServerUrlDetailsBean allServerUrlDetailsBean = (AllServerUrlDetailsBean) envConfigBean;
    String modeValue = allServerUrlDetailsBean.getModeOfEnv();
    if (TextUtils.equals(modeBuild, EnvConfigHelper.EnvMode.DEVELOPMENT.getMode())) {
        // 開發環境預設使用 DEV_TEST_SERVER;
        String modeDevServer = EnvConfigHelper.EnvMode.DEV_TEST_SERVER.getMode();
        if (TextUtils.equals(modeValue, modeDevServer)) {
            envConfigBean.setChecked(CHECKED);
        } else {
            envConfigBean.setChecked(UNCHECKED);
        }
    } else if (TextUtils.equals(modeBuild, EnvConfigHelper.EnvMode.PILOT_RUN.getMode())) {
        // 試執行環境預設使用 DEV_PILOT_RUN;
        String modeDevServer = EnvConfigHelper.EnvMode.DEV_PILOT_RUN.getMode();
        if (TextUtils.equals(modeValue, modeDevServer)) {
            envConfigBean.setChecked(CHECKED);
        } else {
            envConfigBean.setChecked(UNCHECKED);
        }
    } else if (TextUtils.equals(modeBuild, EnvConfigHelper.EnvMode.PRODUCT.getMode())) {
        // 生產環境預設使用 DEV_PRODUCT;
        String modeDevServer = EnvConfigHelper.EnvMode.DEV_PRODUCT.getMode();
        if (TextUtils.equals(modeValue, modeDevServer)) {
            envConfigBean.setChecked(CHECKED);
        } else {
            envConfigBean.setChecked(UNCHECKED);
        }
    }
}

/**
 * 根據點選事件發生所在position,及資料型別,修改了資料集合中相關資料的狀態;
 *
 * @param position
 */
private void updateListData(int position, EnvConfigBean evnConfigBean) {
    LogUtils.d(TAG, "method:updateListData#evnConfigBean=" + evnConfigBean);
    boolean isGroup = evnConfigBean.isGroup();
    if (isGroup) {
        // 多個選項為一組時的業務處理
        handleGroupSingleChecked(position, evnConfigBean);
    } else {
        boolean isChecked = evnConfigBean.isChecked();
        // 單個選項為一組時的業務處理

// switch (groupId) { // case GROUP_ID_SKIP_AD_PAGE: // case GROUP_ID_FIRST_INSTALL: // case GROUP_ID_LOG_DEBUG: evnConfigBean.setChecked(!isChecked); // break; // default: // LogUtils.el(TAG, "method:updateListData#未處理當前邏輯分支"); // break; // } } }

/**
 * 處理一組內容選中的邏輯,單選邏輯;
 * 如果是必選項,則只有一個可被選中,否則可以沒有選中項;
 *
 * @param position
 * @param evnConfigBean
 */
private void handleGroupSingleChecked(int position, EnvConfigBean evnConfigBean) {
    String groupIdChecked = evnConfigBean.getGroupId();
    boolean isRequired = evnConfigBean.isRequired();
    boolean isChecked = evnConfigBean.isChecked();
    for (int i = 0; i < mEnvConfigList.size(); i++) {
        EnvConfigBean item = mEnvConfigList.get(i);
        String groupIdItem = item.getGroupId();
        if (!TextUtils.equals(groupIdChecked, groupIdItem)) {
            continue;
        }
        // 保證同一組內的選項有且只有一個可被選中;
        if (position == i) {
            if (isRequired) {
                if (isChecked) {
                    LogUtils.wl(TAG, "method:handleGroupSingleChecked#isRequired=" + isRequired
                            + ", isChecked=" + isChecked);
                } else {
                    // 保證同一組內的選項至少有一個選項被選中;
                    evnConfigBean.setChecked(!isChecked);
                }
            } else {
                evnConfigBean.setChecked(!isChecked);
            }
        } else {
            item.setChecked(UNCHECKED);
        }
    }
}

/**
 * 處理點選完成的業務;
 */
private void handlerFinishClicked() {
    EnvConfigWrapper envConfigWrapper = buildEnvConfigWrapper();
    // 暫時使用 SharedPreferences 作為本地快取,根據實際需要選擇處理方式;
    Gson gson = new Gson();
    String envConfigWrapperJson = gson.toJson(envConfigWrapper);
    SharedPrefsManager.getInstance()
            .init(mContext, Consts.ENV_CONFIG.ENV_CONFIG_SP)
            .putString(Consts.ENV_CONFIG.ENV_CONFIG_WRAPPER_JSON, envConfigWrapperJson)
            .commit();
    // 修改環境配置的方法可以根據需求放在 EnvConfigDialogFragment 的此處呼叫;
    // 也可以在頁面的 mEnvConfigCheckedCompleteListener 回撥時呼叫;

// EnvConfigHelper.getInstance() // .updateEnvServerUrl(envConfigWrapper.getAllServerUrlDetailsOfCheckedEnv().getModeOfEnv(), envConfigWrapper); // 介面回撥; if (mEnvConfigCheckedCompleteListener == null) { LogUtils.el(TAG, "method:handlerFinishClicked#mConfigCompleteListener = " + mEnvConfigCheckedCompleteListener); } else { mEnvConfigCheckedCompleteListener.onEnvConfigCheckedComplete(envConfigWrapper); } dismiss(); }

/**
 * 構建返回結果,將選中的資料內容封裝在物件內;
 *
 * @return
 */
private EnvConfigWrapper buildEnvConfigWrapper() {
    EnvConfigWrapper envConfigWrapper = new EnvConfigWrapper();
    for (int i = 0; i < mEnvConfigList.size(); i++) {
        EnvConfigBean item = mEnvConfigList.get(i);
        String groupIdItem = item.getGroupId();
        switch (groupIdItem) {
            // 封裝當前選項的資料
            case GROUP_ID_SKIP_AD_PAGE:
                envConfigWrapper.setSkipAdPage((EnvConfigBean) item.clone());
                break;
            // 封裝當前選項的資料
            case GROUP_ID_FIRST_INSTALL:
                envConfigWrapper.setFirstInstall((EnvConfigBean) item.clone());
                break;
            // 封裝當前選項的資料
            case GROUP_ID_LOG_DEBUG:
                envConfigWrapper.setLogDebug((EnvConfigBean) item.clone());
                break;
            // 封裝當前選項的資料
            case GROUP_ID_SERVER_URL_DETAILS:
                if (!item.getChecked()) {
                    continue;
                }
                if (item instanceof AllServerUrlDetailsBean) {
                    AllServerUrlDetailsBean serverUrlDetailsBean = (AllServerUrlDetailsBean) item;
                    envConfigWrapper.setAllServerUrlDetailsOfCheckedEnv((AllServerUrlDetailsBean) serverUrlDetailsBean.clone());
                } else {
                    LogUtils.el(TAG, "method:handlerFinishClicked#envConfigBean not a instance of AllServerUrlDetailsBean.");
                }
                break;
            default:
                LogUtils.el(TAG, "method:handlerFinishClicked#未處理當前邏輯分支");
                break;
        }
    }

    if (mUrlInputLayoutApi != null) {
        ServerUrlDetailsBean urlDetailApi = mUrlInputLayoutApi.getUrlDetailBean();
        if (urlDetailApi == null && mUrlInputLayoutApi.isChecked()) {
            Toast.makeText(mContext, mUrlInputLayoutApi.getTypeName() + "輸入的URL地址無效", Toast.LENGTH_SHORT).show();
        }
        envConfigWrapper.setApiServerUrlDetailsInput(urlDetailApi);
    }
    if (mUrlInputLayoutImage != null && mUrlInputLayoutImage.isChecked()) {
        ServerUrlDetailsBean urlDetailImage = mUrlInputLayoutImage.getUrlDetailBean();
        if (urlDetailImage == null && mUrlInputLayoutApi.isChecked()) {
            Toast.makeText(mContext, mUrlInputLayoutImage.getTypeName() + "輸入的URL地址無效", Toast.LENGTH_SHORT).show();
        }
        envConfigWrapper.setImageServerUrlDetailsInput(urlDetailImage);
    }
    if (mUrlInputLayoutH5 != null && mUrlInputLayoutH5.isChecked()) {
        ServerUrlDetailsBean urlDetailH5 = mUrlInputLayoutH5.getUrlDetailBean();
        if (urlDetailH5 == null && mUrlInputLayoutApi.isChecked()) {
            Toast.makeText(mContext, mUrlInputLayoutH5.getTypeName() + "輸入的URL地址無效", Toast.LENGTH_SHORT).show();
        }
        envConfigWrapper.setH5ServerUrlDetailsInput(urlDetailH5);
    }
    AppInfoBean appInfoBean = EnvConfigHelper.getInstance().getAppInfoBean();
    if (appInfoBean != null) {
        envConfigWrapper.setModeOfBuild(appInfoBean.getModeOfBuild());
    } else {
        LogUtils.el(TAG, "method:handlerFinishClicked#EnvConfigHelper#mAppInfoBean not initialize.");
    }
    return envConfigWrapper;
}

@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    Dialog dialog = super.onCreateDialog(savedInstanceState);
    dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
    return dialog;
}

} ```    專案中資料內容是定義在 json 串中的,如果僅公是資料項的不同,可以在 json 中的編寫,新增或者移除,基本的業務邏輯變化不大,但如果 json 串的格式想要做調整,那整體的業務處理邏輯就需要根據新的資料內容進行處理了。

   EnvConfigHelper 主要處理預設配置環境的初始化及環境切換後的賦值邏輯;

```java package com.windfallsheng.envconfig;

import android.content.Context; import android.text.TextUtils; import androidx.annotation.StringDef; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.windfallsheng.envconfig.bean.AllServerUrlDetailsBean; import com.windfallsheng.envconfig.bean.AppInfoBean; import com.windfallsheng.envconfig.bean.EnvConfigWrapper; import com.windfallsheng.envconfig.bean.ServerUrlDetailsBean; import com.windfallsheng.lib.Consts; import com.windfallsheng.lib.Consts.ENV_MODE; import com.windfallsheng.lib.util.LogUtils; import com.windfallsheng.lib.util.SharedPrefsManager;

/* * @Author: lzsheng * @CreateDate: 2020/12/17 15:22 * @LastModifiedTime: 2020/12/17 15:22 * @Description: 單例模式,以方便全域性隨時可以獲取設定的引數; / public class EnvConfigHelper {

public static String TAG = "EnvConfigHelper";
private Context mContext;
private String mModeSelected;
private AppInfoBean mAppInfoBean;

private EnvConfigHelper() {
}

public static EnvConfigHelper getInstance() {
    return SingletonHolder.INSTANCE;
}

public EnvConfigHelper initContext(Context context) {
    mContext = context;
    return this;
}

public String getModeSelected() {
    return mModeSelected;
}

public AppInfoBean getAppInfoBean() {
    return mAppInfoBean;
}

public void setAppInfoBean(AppInfoBean appInfoBean) {
    mAppInfoBean = appInfoBean;
}

/**
 * 根據選擇的環境型別,調整環境配置;
 *
 * @param modeValue
 */
public void updateEnvServerUrl(@MODE String modeValue, EnvConfigWrapper envConfigWrapper) {
    // 修改當前模式為已修改的模式;
    this.mModeSelected = modeValue;
    // 判斷是否有手動輸入的地址項;
    String urlApiStr = "";
    String urlImageStr = "";
    String urlH5Str = "";
    if (envConfigWrapper != null) {
        ServerUrlDetailsBean apiDetailsInput = envConfigWrapper.getApiServerUrlDetailsInput();
        if (apiDetailsInput != null) {
            urlApiStr = apiDetailsInput.getUrlStr();
        }
        ServerUrlDetailsBean imageDetailsInput = envConfigWrapper.getImageServerUrlDetailsInput();
        if (imageDetailsInput != null) {
            urlImageStr = imageDetailsInput.getUrlStr();
        }
        ServerUrlDetailsBean h5DetailsInput = envConfigWrapper.getH5ServerUrlDetailsInput();
        if (h5DetailsInput != null) {
            urlH5Str = h5DetailsInput.getUrlStr();
        }
    }
    // 手動輸入的地址項優先賦值;
    if (!TextUtils.isEmpty(urlApiStr)) {
        EnvServerUrlConsts.API_SERVER = urlApiStr;
    }
    if (!TextUtils.isEmpty(urlImageStr)) {
        EnvServerUrlConsts.IMAGE_SERVER = urlImageStr;
    }
    if (!TextUtils.isEmpty(urlH5Str)) {
        EnvServerUrlConsts.H5_SERVER = urlH5Str;
    }
    switch (modeValue) {
        case ENV_MODE.DEV_TEST_LOCAL:

            // 手動輸入的地址項優先賦值,若沒有輸入地址,則使用預設配置地址;
            if (TextUtils.isEmpty(urlApiStr)) {
                EnvServerUrlConsts.API_SERVER = EnvServerUrlConsts.TestLocal.API_SERVER;
            }
            if (TextUtils.isEmpty(urlImageStr)) {
                EnvServerUrlConsts.IMAGE_SERVER = EnvServerUrlConsts.TestLocal.IMAGE_SERVER;
            }
            if (TextUtils.isEmpty(urlH5Str)) {
                EnvServerUrlConsts.H5_SERVER = EnvServerUrlConsts.TestLocal.H5_SERVER;
            }
            break;
        case ENV_MODE.DEVELOPMENT:
        case ENV_MODE.DEV_TEST_SERVER:
            // 手動輸入的地址項優先賦值,若沒有輸入地址,則使用預設配置地址;
            if (TextUtils.isEmpty(urlApiStr)) {
                EnvServerUrlConsts.API_SERVER = EnvServerUrlConsts.TestServer.API_SERVER;
            }
            if (TextUtils.isEmpty(urlImageStr)) {
                EnvServerUrlConsts.IMAGE_SERVER = EnvServerUrlConsts.TestServer.IMAGE_SERVER;
            }
            if (TextUtils.isEmpty(urlH5Str)) {
                EnvServerUrlConsts.H5_SERVER = EnvServerUrlConsts.TestServer.H5_SERVER;
            }
            break;
        case ENV_MODE.PILOT_RUN:
        case ENV_MODE.DEV_PILOT_RUN:
            // 手動輸入的地址項優先賦值,若沒有輸入地址,則使用預設配置地址;
            if (TextUtils.isEmpty(urlApiStr)) {
                EnvServerUrlConsts.API_SERVER = EnvServerUrlConsts.PilotRun.API_SERVER;
            }
            if (TextUtils.isEmpty(urlImageStr)) {
                EnvServerUrlConsts.IMAGE_SERVER = EnvServerUrlConsts.PilotRun.IMAGE_SERVER;
            }
            if (TextUtils.isEmpty(urlH5Str)) {
                EnvServerUrlConsts.H5_SERVER = EnvServerUrlConsts.PilotRun.H5_SERVER;
            }
            break;
        case ENV_MODE.PRODUCT:
        case ENV_MODE.DEV_PRODUCT:
            // 手動輸入的地址項優先賦值,若沒有輸入地址,則使用預設配置地址;
            if (TextUtils.isEmpty(urlApiStr)) {
                EnvServerUrlConsts.API_SERVER = EnvServerUrlConsts.Product.API_SERVER;
            }
            if (TextUtils.isEmpty(urlImageStr)) {
                EnvServerUrlConsts.IMAGE_SERVER = EnvServerUrlConsts.Product.IMAGE_SERVER;
            }
            if (TextUtils.isEmpty(urlH5Str)) {
                EnvServerUrlConsts.H5_SERVER = EnvServerUrlConsts.Product.H5_SERVER;
            }
            break;
        default:
            LogUtils.el(TAG, "method:updateEnvServerUrl#未處理當前邏輯分支");
            break;
    }

    LogUtils.dl("updateEnvServerUrl★★\n★★modeValue=" + modeValue +
            "\n★★EnvServerUrlConsts.API_SERVER=" + EnvServerUrlConsts.API_SERVER +
            ", \n★★EnvServerUrlConsts.IMAGE_SERVER=" + EnvServerUrlConsts.IMAGE_SERVER +
            ", \n★★EnvServerUrlConsts.H5_SERVER=" + EnvServerUrlConsts.H5_SERVER);
}

/**
 * 根據選擇的環境型別,調整環境配置;
 *
 * @param envConfigWrapper
 */
public void updateEnvServerUrl(EnvConfigWrapper envConfigWrapper) {
    if (envConfigWrapper == null) {
        LogUtils.el(TAG, "method:updateEnvServerUrl#return#envConfigWrapper == null");
        return;
    }
    AllServerUrlDetailsBean allServerUrlDetailsOfCheckedEnv = envConfigWrapper.getAllServerUrlDetailsOfCheckedEnv();
    if (allServerUrlDetailsOfCheckedEnv == null) {
        LogUtils.el(TAG, "method:updateEnvServerUrl#return#allServerUrlDetailsOfCheckedEnv == null");
        return;
    }
    String modeValue = allServerUrlDetailsOfCheckedEnv.getModeOfEnv();
    updateEnvServerUrl(modeValue, envConfigWrapper);
}

/**
 * 根據選擇的環境型別,調整環境配置;
 *
 * @param mode
 */
public void updateEnvServerUrl(@MODE String modeValue) {
    updateEnvServerUrl(modeValue, null);
}

/**
 * 在應用啟動時最先初始化配置資訊;
 */
public void initDefaultEnv() {
    String modeValue = null;
    LogUtils.dl(TAG, "method:initDefaultEnv#mAppInfoBean=" + mAppInfoBean);
    if (mAppInfoBean == null) {
        LogUtils.el(TAG, "method:initDefaultEnv#return#mAppInfoBean == null");
        return;
    }

    modeValue = mAppInfoBean.getModeOfBuild();
    LogUtils.dl(TAG, "method:initDefaultEnv#modeValue=" + modeValue);

    if (TextUtils.isEmpty(modeValue)) {
        LogUtils.el(TAG, "method:initDefaultEnv#return#modeValue == " + modeValue);
        return;
    }
    // 如果打包構建的環境是生產和試執行,則以打包環境為準
    if (TextUtils.equals(modeValue, ENV_MODE.PILOT_RUN) ||
            TextUtils.equals(modeValue, ENV_MODE.PRODUCT)) {
        updateEnvServerUrl(modeValue);
    } else if (TextUtils.equals(modeValue, ENV_MODE.DEVELOPMENT)) {
        // 如果是開發環境,先檢查是否有上次快取的配置資料;
        EnvConfigWrapper envConfigWrapper = initEnvConfigWrapper();
        LogUtils.dl(TAG, "method:initDefaultEnv#envConfigWrapper=" + envConfigWrapper);
        if (envConfigWrapper == null) {
            //                如果沒有快取資料,則以構建環境為準;
            modeValue = mAppInfoBean.getModeOfBuild();
            updateEnvServerUrl(modeValue);
        } else {
            // 如果上次快取的有資料,則以快取資料為準;
            updateEnvServerUrl(envConfigWrapper);
        }
    }
}

public EnvConfigWrapper initEnvConfigWrapper() {
    if (mContext == null) {
        LogUtils.el(TAG, "method:initEnvConfigWrapper#return null#mContext == null");
        return null;
    }
    // 暫時使用 SharedPreferences 作為本地快取,根據實際需要選擇處理方式;
    String envConfigWrapperJson = SharedPrefsManager.getInstance()
            .init(mContext, Consts.ENV_CONFIG.ENV_CONFIG_SP)
            .getString(Consts.ENV_CONFIG.ENV_CONFIG_WRAPPER_JSON, "");
    EnvConfigWrapper envConfigWrapper = null;
    if (!TextUtils.isEmpty(envConfigWrapperJson)) {
        Gson gson = new Gson();
        try {
            envConfigWrapper = gson.fromJson(envConfigWrapperJson, EnvConfigWrapper.class);
        } catch (JsonSyntaxException e) {
            LogUtils.el(TAG, "method:initEnvConfigWrapper#e=" + e.getMessage());
            SharedPrefsManager.getInstance()
                    .init(mContext, Consts.ENV_CONFIG.ENV_CONFIG_SP)
                    .removeKey(Consts.ENV_CONFIG.ENV_CONFIG_WRAPPER_JSON);
        }
    }
    return envConfigWrapper;
}

/**
 * 自定義的環境模式,其屬性值如果在productFlavors中存在則要和它保持一致;
 */
public enum EnvMode {
    /**
     * 開發版本下的伺服器測試環境
     */
    DEV_TEST_SERVER(ENV_MODE.DEV_TEST_SERVER),
    /**
     * 開發版本下的本地環境
     */
    DEV_TEST_LOCAL(ENV_MODE.DEV_TEST_LOCAL),
    /**
     * 開發版本下的試執行環境
     */
    DEV_PILOT_RUN(ENV_MODE.DEV_PILOT_RUN),
    /**
     * 開發版本下的生產環境
     */
    DEV_PRODUCT(ENV_MODE.DEV_PRODUCT),
    /**
     * 開發版本
     */
    DEVELOPMENT(ENV_MODE.DEVELOPMENT),
    /**
     * 試執行版本
     */
    PILOT_RUN(ENV_MODE.PILOT_RUN),
    /**
     * 生產版本
     */
    PRODUCT(Consts.ENV_MODE.PRODUCT);

    private String mMode;

    EnvMode(String mode) {
        this.mMode = mode;
    }

    public String getMode() {
        return mMode;
    }
}

@StringDef({ENV_MODE.DEV_TEST_LOCAL, ENV_MODE.DEV_TEST_SERVER, ENV_MODE.DEV_PILOT_RUN, ENV_MODE.DEV_PRODUCT, ENV_MODE.DEVELOPMENT, ENV_MODE.PILOT_RUN, ENV_MODE.PRODUCT})
public @interface MODE {
}

private static class SingletonHolder {
    private static final EnvConfigHelper INSTANCE = new EnvConfigHelper();
}

} ```

一些輔助類

部分實體類 ```java package com.windfallsheng.envconfig.bean;

import com.windfallsheng.envconfig.EnvConfigHelper;

import java.io.Serializable;

/* * @Author: lzsheng * @CreateDate: 2020/12/16 20:21 * @LastModifiedTime: 2020/12/16 20:21 * @Description: 封裝選中的配置資訊,作為最終的配置資料結果; / public class EnvConfigWrapper implements Serializable {

/**
 * 構建包的模式;
 */
@EnvConfigHelper.MODE
private String mModeOfBuild;
/**
 * 如選中的測試環境下的所有伺服器地址的配置資訊;
 */
private AllServerUrlDetailsBean mAllServerUrlDetailsOfCheckedEnv;
/**
 * 一些開關性質的配置資訊;
 */
private EnvConfigBean mSkipAdPage;
private EnvConfigBean mFirstInstall;
private EnvConfigBean mLogDebug;
/**
 * 手動輸入的伺服器的配置資訊;
 */
private ServerUrlDetailsBean mApiServerUrlDetailsInput;
private ServerUrlDetailsBean mImageServerUrlDetailsInput;
private ServerUrlDetailsBean mH5ServerUrlDetailsInput;

public EnvConfigWrapper() {
}

public String getModeOfBuild() {
    return mModeOfBuild;
}

public void setModeOfBuild(@EnvConfigHelper.MODE String modeOfBuild) {
    this.mModeOfBuild = modeOfBuild;
}

public AllServerUrlDetailsBean getAllServerUrlDetailsOfCheckedEnv() {
    return mAllServerUrlDetailsOfCheckedEnv;
}

public void setAllServerUrlDetailsOfCheckedEnv(AllServerUrlDetailsBean allServerUrlDetailsOfCheckedEnv) {
    this.mAllServerUrlDetailsOfCheckedEnv = allServerUrlDetailsOfCheckedEnv;
}

public EnvConfigBean getSkipAdPage() {
    return mSkipAdPage;
}

public void setSkipAdPage(EnvConfigBean skipAdPage) {
    this.mSkipAdPage = skipAdPage;
}

public EnvConfigBean getFirstInstall() {
    return mFirstInstall;
}

public void setFirstInstall(EnvConfigBean firstInstall) {
    this.mFirstInstall = firstInstall;
}

public EnvConfigBean getLogDebug() {
    return mLogDebug;
}

public void setLogDebug(EnvConfigBean logDebug) {
    this.mLogDebug = logDebug;
}

public ServerUrlDetailsBean getApiServerUrlDetailsInput() {
    return mApiServerUrlDetailsInput;
}

public void setApiServerUrlDetailsInput(ServerUrlDetailsBean apiServerUrlDetailsInput) {
    this.mApiServerUrlDetailsInput = apiServerUrlDetailsInput;
}

public ServerUrlDetailsBean getImageServerUrlDetailsInput() {
    return mImageServerUrlDetailsInput;
}

public void setImageServerUrlDetailsInput(ServerUrlDetailsBean imageServerUrlDetailsInput) {
    this.mImageServerUrlDetailsInput = imageServerUrlDetailsInput;
}

public ServerUrlDetailsBean getH5ServerUrlDetailsInput() {
    return mH5ServerUrlDetailsInput;
}

public void setH5ServerUrlDetailsInput(ServerUrlDetailsBean h5ServerUrlDetailsInput) {
    this.mH5ServerUrlDetailsInput = h5ServerUrlDetailsInput;
}

@Override
public String toString() {
    return "EnvConfigWrapper{" +
            "mModeOfBuild='" + mModeOfBuild + '\'' +
            ", mAllServerUrlDetailsOfCheckedEnv=" + mAllServerUrlDetailsOfCheckedEnv +
            ", mSkipAdPage=" + mSkipAdPage +
            ", mFirstInstall=" + mFirstInstall +
            ", mLogDebug=" + mLogDebug +
            ", mApiServerUrlDetailsInput=" + mApiServerUrlDetailsInput +
            ", mImageServerUrlDetailsInput=" + mImageServerUrlDetailsInput +
            ", mH5ServerUrlDetailsInput=" + mH5ServerUrlDetailsInput +
            '}';
}

} ```

```java package com.windfallsheng.envconfig.bean;

import java.io.Serializable;

/* * @Author: lzsheng * @CreateDate: 2020/12/16 15:43 * @LastModifiedTime: 2020/12/16 15:43 * @Description: 如測試環境下的配置 json示例: * { * "configId": "first_install", * "groupId": "first_install", * "labelName": "是否為首次安裝啟動(選中代表是)", * "isChecked": false, * "isRequired": false, * "isGroup": false, * "viewType": 4 * } * 或者: * { * "configId": "dev_pilot_server_url_details", * "groupId": "server_url_details", * "labelName": "試執行環境", * "modeOfEnv": "devPilotRun", * "isChecked": false, * "isRequired": true, * "isGroup": true, * "viewType": 1, * "apiServerUrlDetails": { * "typeId": "api", * "typeName": "API伺服器地址", * "urlStr": "http://bnmd.hjk.api.com.cn:698" * }, * "imageServerUrlDetails": { * "typeId": "image", * "typeName": "image伺服器地址", * "urlStr": "http://bnmd.hjk.image.com.cn:698" * }, * "h5ServerUrlDetails": { * "typeId": "h5", * "typeName": "H5伺服器地址", * "urlStr": "http://bnmd.hjk.h5.com.cn:698" * } * } / public class EnvConfigBean implements Serializable, Cloneable {

/**
 * 一般如果設定的話,就需要是唯一的;
 */
private String configId;
/**
 * 此屬性值相同的為同一組的選項,同一組內的選項有且只有一個可被選中;
 */
private String groupId;
/**
 * 名稱;
 */
private String labelName;
/**
 * 是否被選中;
 */
private boolean isChecked;
/**
 * 是否是必選項,如果是則當前分組中至少有一個被選中;
 */
private boolean isRequired;
/**
 * 是單個項,還是有多項組成的一組;
 */
private boolean isGroup;
/**
 * UI 列表顯示的 itemView 型別;
 */
private int viewType;

public String getConfigId() {
    return configId;
}

public void setConfigId(String configId) {
    this.configId = configId;
}

public String getGroupId() {
    return groupId;
}

public void setGroupId(String groupId) {
    this.groupId = groupId;
}

public boolean isGroup() {
    return isGroup;
}

public void setGroup(boolean group) {
    isGroup = group;
}

public String getLabelName() {
    return labelName;
}

public void setLabelName(String labelName) {
    this.labelName = labelName;
}

public boolean getChecked() {
    return isChecked;
}

public int getViewType() {
    return viewType;
}

public void setViewType(int viewType) {
    this.viewType = viewType;
}

public boolean isRequired() {
    return isRequired;
}

public void setRequired(boolean required) {
    this.isRequired = required;
}

public boolean isChecked() {
    return isChecked;
}

public void setChecked(boolean checked) {
    this.isChecked = checked;
}

@Override
public String toString() {
    return "EnvConfigBean{" +
            "configId='" + configId + '\'' +
            ", groupId='" + groupId + '\'' +
            ", labelName='" + labelName + '\'' +
            ", isChecked=" + isChecked +
            ", isRequired=" + isRequired +
            ", isGroup=" + isGroup +
            ", viewType=" + viewType +
            '}';
}

@Override
public Object clone() {
    EnvConfigBean envConfigBean = null;
    try {
        envConfigBean = (EnvConfigBean) super.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return envConfigBean;
}

} ```

```java package com.windfallsheng.envconfig.bean;

/* * @Author: lzsheng * @CreateDate: 2020/12/16 15:43 * @LastModifiedTime: 2020/12/16 15:43 * @Description: 如測試環境下的配置 json示例: * { * "configId": "dev_test_server_url_details", * "groupId": "server_url_details", * "modeOfEnv": "devTestServer", * "labelName": "測試環境(測試伺服器)", * "isChecked": true, * "isRequired": true, * "isGroup": true, * "viewType": 1, * "apiServerUrlDetails": { * "typeId": "api", * "typeName": "API伺服器地址", * "urlStr": "http://cvbn.ert.api.com.cn:598" * }, * "imageServerUrlDetails": { * "typeId": "image", * "typeName": "image伺服器地址", * "urlStr": "http://cvbn.ert.image.com.cn:598" * }, * "h5ServerUrlDetails": { * "typeId": "h5", * "typeName": "H5伺服器地址", * "urlStr": "http://cvbn.ert.h5.com.cn:598" * } * } / public class AllServerUrlDetailsBean extends EnvConfigBean {

/**
 * 當前環境的mode;
 */
private String modeOfEnv;
/**
 * 當前環境內所有伺服器的詳情;
 */
private ServerUrlDetailsBean apiServerUrlDetails;
private ServerUrlDetailsBean imageServerUrlDetails;
private ServerUrlDetailsBean h5ServerUrlDetails;

public String getModeOfEnv() {
    return modeOfEnv;
}

public void setModeOfEnv(String modeOfEnv) {
    this.modeOfEnv = modeOfEnv;
}

public ServerUrlDetailsBean getApiServerUrlDetails() {
    return apiServerUrlDetails;
}

public void setApiServerUrlDetails(ServerUrlDetailsBean apiServerUrlDetails) {
    this.apiServerUrlDetails = apiServerUrlDetails;
}

public ServerUrlDetailsBean getImageServerUrlDetails() {
    return imageServerUrlDetails;
}

public void setImageServerUrlDetails(ServerUrlDetailsBean imageServerUrlDetails) {
    this.imageServerUrlDetails = imageServerUrlDetails;
}

public ServerUrlDetailsBean getH5ServerUrlDetails() {
    return h5ServerUrlDetails;
}

public void setH5ServerUrlDetails(ServerUrlDetailsBean h5ServerUrlDetails) {
    this.h5ServerUrlDetails = h5ServerUrlDetails;
}

@Override
public String toString() {
    return "AllServerUrlDetailsBean{" +
            "modeOfEnv='" + modeOfEnv + '\'' +
            ", apiServerUrlDetails=" + apiServerUrlDetails +
            ", imageServerUrlDetails=" + imageServerUrlDetails +
            ", h5ServerUrlDetails=" + h5ServerUrlDetails +
            "} " + super.toString();
}

@Override
public Object clone() {
    AllServerUrlDetailsBean envConfigBean = null;
    envConfigBean = (AllServerUrlDetailsBean) super.clone();
    if (this.apiServerUrlDetails != null) {
        envConfigBean.setApiServerUrlDetails((ServerUrlDetailsBean) this.apiServerUrlDetails.clone());
    }
    if (this.imageServerUrlDetails != null) {
        envConfigBean.setImageServerUrlDetails((ServerUrlDetailsBean) this.imageServerUrlDetails.clone());
    }
    if (this.h5ServerUrlDetails != null) {
        envConfigBean.setH5ServerUrlDetails((ServerUrlDetailsBean) this.h5ServerUrlDetails.clone());
    }
    return envConfigBean;
}

}

```

```java package com.windfallsheng.envconfig.bean;

import java.io.Serializable;

/* * @Author: lzsheng * @CreateDate: 2020/12/16 15:43 * @LastModifiedTime: 2020/12/16 15:43 * @Description: json示例: * "serverUrlDetailsBean": { * "typeId": "h5", * "typeName": "H5伺服器地址", * "urlStr": "http://www.baidu.com", * "isChecked": false * } / public class ServerUrlDetailsBean implements Serializable, Cloneable {

private String typeId;
private String urlStr;
private String typeName;
private boolean isChecked;

public ServerUrlDetailsBean(String typeId, String urlStr, String typeName, boolean isChecked) {
    this.typeId = typeId;
    this.urlStr = urlStr;
    this.typeName = typeName;
    this.isChecked = isChecked;
}

public String getTypeId() {
    return typeId;
}

public void setTypeId(String typeId) {
    this.typeId = typeId;
}

public String getUrlStr() {
    return urlStr;
}

public void setUrlStr(String urlStr) {
    this.urlStr = urlStr;
}

public String getTypeName() {
    return typeName;
}

public void setTypeName(String typeName) {
    this.typeName = typeName;
}

@Override
public Object clone() {
    ServerUrlDetailsBean urlDetailBean = null;
    try {
        urlDetailBean = (ServerUrlDetailsBean) super.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return urlDetailBean;
}

public boolean isChecked() {
    return isChecked;
}

public void setChecked(boolean checked) {
    isChecked = checked;
}

@Override
public String toString() {
    return "UrlDetailBean{" +
            "typeId='" + typeId + '\'' +
            ", urlStr='" + urlStr + '\'' +
            ", typeName='" + typeName + '\'' +
            '}';
}

}

```

URL 常量定義: ```java package com.windfallsheng.envconfig;

/* * @Author: lzsheng * @CreateDate: 2020/12/18 15:31 * @LastModifiedTime: 2020/12/18 15:31 * @Description: / public class EnvServerUrlConsts {

/**
 * 伺服器地址;
 */
public static String API_SERVER = Product.API_SERVER;
/**
 * image伺服器地址;
 */
public static String IMAGE_SERVER = Product.IMAGE_SERVER;
/**
 * H5伺服器地址;
 */
public static String H5_SERVER = Product.H5_SERVER;

/**
 * 生產環境的地址
 */
public static class Product {

    /**
     * 生產環境的API地址
     */
    public static final String API_SERVER = "http://asdf.qwe.api.com.cn:798";
    /**
     * 生產環境的image地址
     */
    public static final String IMAGE_SERVER = "http://asdf.qwe.image.com.cn:798";
    /**
     * 生產環境的H5地址
     */
    public static final String H5_SERVER = "http://asdf.qwe.h5.com.cn:798";
}

/**
 * 試執行環境的地址
 */
public static class PilotRun {

    /**
     * 生產環境的API地址
     */
    public static final String API_SERVER = "http://bnmd.hjk.api.com.cn:698";
    /**
     * 生產環境的image地址
     */
    public static final String IMAGE_SERVER = "http://bnmd.hjk.image.com.cn:698";
    /**
     * 生產環境的H5地址
     */
    public static final String H5_SERVER = "http://bnmd.hjk.h5.com.cn:698";
}

/**
 * 伺服器測試環境的地址
 */
public static class TestServer {

    /**
     * 生產環境的API地址
     */
    public static final String API_SERVER = "http://cvbn.ert.api.com.cn:598";
    /**
     * 生產環境的image地址
     */
    public static final String IMAGE_SERVER = "http://cvbn.ert.image.com.cn:598";
    /**
     * 生產環境的H5地址
     */
    public static final String H5_SERVER = "http://cvbn.ert.h5.com.cn:598";
}

/**
 * 本地測試環境的地址
 */
public static class TestLocal {

    /**
     * 生產環境的API地址
     */
    public static final String API_SERVER = "http://dfgt.uio.api.com.cn:898";
    /**
     * 生產環境的image地址
     */
    public static final String IMAGE_SERVER = "http://dfgt.uio.image.com.cn:898";
    /**
     * 生產環境的H5地址
     */
    public static final String H5_SERVER = "http://dfgt.uio.h5.com.cn:898";
}

} **一些常量定義**java package com.windfallsheng.lib;

/* * @Author: lzsheng * @CreateDate: 2020/12/18 15:31 * @LastModifiedTime: 2020/12/18 15:31 * @Description: / public class Consts {

public static class ENV_CONFIG {

    public static final String ENV_CONFIG_SP = "EnvConfig";

    /**
     *
     */
    public static final String ENV_CONFIG_WRAPPER_JSON = "env_config_wrapper_json";

}

public static class ENV_MODE {

    public static final String RELEASE = "release";
    public static final String DEBUG = "debug";
    public static final String DEV_TEST_LOCAL = "devTestLocal";
    public static final String DEV_TEST_SERVER = "devTestServer";
    public static final String DEV_PILOT_RUN = "devPilotRun";
    public static final String DEV_PRODUCT = "devProduct";
    public static final String DEVELOPMENT = "development";
    public static final String PILOT_RUN = "pilotRun";
    public static final String PRODUCT = "product";
}

} **部分 json 格式**xml { "allServerUrlDetailsOfCheckedEnv": { "apiServerUrlDetails": { "isChecked": false, "typeId": "api", "typeName": "API伺服器地址", "urlStr": "http://bnmd.hjk.api.com.cn:698" }, "configId": "dev_pilot_server_url_details", "groupId": "server_url_details", "h5ServerUrlDetails": { "isChecked": false, "typeId": "h5", "typeName": "H5伺服器地址", "urlStr": "http://bnmd.hjk.h5.com.cn:698" }, "imageServerUrlDetails": { "isChecked": false, "typeId": "image", "typeName": "image伺服器地址", "urlStr": "http://bnmd.hjk.image.com.cn:698" }, "isChecked": true, "isGroup": true, "isRequired": true, "labelName": "試執行環境", "modeValue": "devPilotRun", "viewType": 1 }, "apiServerUrlDetailsInput": { "isChecked": true, "typeId": "api", "typeName": "API伺服器地址:", "urlStr": "http://note.youdao.com/api" }, "firstInstall": { "configId": "first_install", "groupId": "first_install", "isChecked": true, "isGroup": false, "isRequired": false, "labelName": "是否為首次安裝啟動(選中代表是)", "viewType": 4 }, "h5ServerUrlDetailsInput": { "isChecked": true, "typeId": "h5", "typeName": "H5伺服器地址:", "urlStr": "http://note.youdao.com/h5" }, "imageServerUrlDetailsInput": { "isChecked": true, "typeId": "image", "typeName": "image伺服器地址:", "urlStr": "http://note.youdao.com/image" }, "logDebug": { "configId": "log_debug", "groupId": "log_debug", "isChecked": true, "isGroup": false, "isRequired": false, "labelName": "是否關閉Log日誌(選中代表關閉)", "viewType": 5 }, "mModeBuild": "development", "skipAdPage": { "configId": "skip_ad_page", "groupId": "skip_ad_page", "isChecked": true, "isGroup": false, "isRequired": false, "labelName": "是否跳過廣告頁(選中代表是)", "viewType": 2 } } ```

xml [ { "configId": "dev_test_server_url_details", "groupId": "server_url_details", "modeOfEnv": "devTestServer", "labelName": "測試環境(測試伺服器)", "isChecked": true, "isRequired": true, "isGroup": true, "viewType": 1, "apiServerUrlDetails": { "typeId": "api", "typeName": "API伺服器地址", "urlStr": "http://cvbn.ert.api.com.cn:598" }, "imageServerUrlDetails": { "typeId": "image", "typeName": "image伺服器地址", "urlStr": "http://cvbn.ert.image.com.cn:598" }, "h5ServerUrlDetails": { "typeId": "h5", "typeName": "H5伺服器地址", "urlStr": "http://cvbn.ert.h5.com.cn:598" } }, { "configId": "dev_test_local_server_url_details", "groupId": "server_url_details", "labelName": "本地測試環境(本地地址)", "modeOfEnv": "devTestLocal", "isChecked": false, "isRequired": true, "isGroup": true, "viewType": 1, "apiServerUrlDetails": { "typeId": "api", "typeName": "API伺服器地址", "urlStr": "http://dfgt.uio.api.com.cn:898" }, "imageServerUrlDetails": { "typeId": "image", "typeName": "image伺服器地址", "urlStr": "http://dfgt.uio.image.com.cn:898" }, "h5ServerUrlDetails": { "typeId": "h5", "typeName": "H5伺服器地址", "urlStr": "http://dfgt.uio.h5.com.cn:898" } }, { "configId": "dev_pilot_server_url_details", "groupId": "server_url_details", "labelName": "試執行環境", "modeOfEnv": "devPilotRun", "isChecked": false, "isRequired": true, "isGroup": true, "viewType": 1, "apiServerUrlDetails": { "typeId": "api", "typeName": "API伺服器地址", "urlStr": "http://bnmd.hjk.api.com.cn:698" }, "imageServerUrlDetails": { "typeId": "image", "typeName": "image伺服器地址", "urlStr": "http://bnmd.hjk.image.com.cn:698" }, "h5ServerUrlDetails": { "typeId": "h5", "typeName": "H5伺服器地址", "urlStr": "http://bnmd.hjk.h5.com.cn:698" } }, { "configId": "dev_product_server_url_details", "groupId": "server_url_details", "modeOfEnv": "devProduct", "labelName": "生產環境", "isChecked": false, "isRequired": true, "isGroup": true, "viewType": 1, "apiServerUrlDetails": { "typeId": "api", "typeName": "API伺服器地址", "urlStr": "http://asdf.qwe.api.com.cn:798" }, "imageServerUrlDetails": { "typeId": "image", "typeName": "image伺服器地址", "urlStr": "http://asdf.qwe.image.com.cn:798" }, "h5ServerUrlDetails": { "typeId": "h5", "typeName": "H5伺服器地址", "urlStr": "http://asdf.qwe.h5.com.cn:798" } } ]

xml [ { "configId": "first_install", "groupId": "first_install", "labelName": "是否為首次安裝啟動(選中代表是)", "isChecked": false, "isRequired": false, "isGroup": false, "viewType": 4 }, { "configId": "log_debug", "groupId": "log_debug", "labelName": "是否關閉Log日誌(選中代表關閉)", "isChecked": false, "isRequired": false, "isGroup": false, "viewType": 5 }, { "configId": "skip_ad_page", "groupId": "skip_ad_page", "labelName": "是否跳過廣告頁(選中代表是)", "isChecked": false, "isRequired": false, "isGroup": false, "viewType": 2 } ]

專案中使用的不同的 json 資料格式,可以在示例工程中的 README.md 中檢視,作為修改的參考依據;

總結

  本文的程式示例只是提供一種實現思路,若專案需求和本例的實現恰好相同或者接近,則可以參考使用,或者差別較大,也希望在其它的設計思路上能夠有一定的幫助。