Android在APP开发阶段,实现动态的环境配置切换


直接查看 《 Android实现动态切换环境配置4.0版本

少啰嗦,先上图

在这里插入图片描述在这里插入图片描述
apk 安装包地址:https://download.csdn.net/download/Silence1515/20973685

项目工程地址:https://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 相关的数据获取方式问题,以及与它相关的处理逻辑,其它功能业务不受影响。

应用示例

必要的初始化阶段:
  初始化一些必要的参数及环境配置参数;

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 就可以不必实现这个接口。
  由此可以看出这个环境配置的实现对代码的侵入还是比较小的;

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 version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.windfallsheng.MainActivity">

    <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>

</LinearLayout>

主项目中配置的 build.gradle 文件:
  配置了相关的打包环境;

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 的实现过程:
实现了弹框的触发条件,及自定义属性,实现可以灵活控制相关业务;

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;
    }
}

  自定义的EnvConfigDialogFragment 配置弹框的实现过程:
实现了页面的UI效果,同时处理了不同配置项被选中时的业务,重置配置参数的业务,及完成后保存数据的业务;

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 主要处理默认配置环境的初始化及环境切换后的赋值逻辑;

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();
    }

}

一些辅助类

部分实体类

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 +
                '}';
    }
}
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": "https://bnmd.hjk.api.com.cn:698"
 *     },
 *     "imageServerUrlDetails": {
 *       "typeId": "image",
 *       "typeName": "image服务器地址",
 *       "urlStr": "https://bnmd.hjk.image.com.cn:698"
 *     },
 *     "h5ServerUrlDetails": {
 *       "typeId": "h5",
 *       "typeName": "H5服务器地址",
 *       "urlStr": "https://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;
    }
}
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": "https://cvbn.ert.api.com.cn:598"
 *     },
 *     "imageServerUrlDetails": {
 *       "typeId": "image",
 *       "typeName": "image服务器地址",
 *       "urlStr": "https://cvbn.ert.image.com.cn:598"
 *     },
 *     "h5ServerUrlDetails": {
 *       "typeId": "h5",
 *       "typeName": "H5服务器地址",
 *       "urlStr": "https://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;
    }
}

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 常量定义:

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 = "https://asdf.qwe.api.com.cn:798";
        /**
         * 生产环境的image地址
         */
        public static final String IMAGE_SERVER = "https://asdf.qwe.image.com.cn:798";
        /**
         * 生产环境的H5地址
         */
        public static final String H5_SERVER = "https://asdf.qwe.h5.com.cn:798";
    }

    /**
     * 试运行环境的地址
     */
    public static class PilotRun {

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

    /**
     * 服务器测试环境的地址
     */
    public static class TestServer {

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

    /**
     * 本地测试环境的地址
     */
    public static class TestLocal {

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

一些常量定义

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 格式

{
  "allServerUrlDetailsOfCheckedEnv": {
    "apiServerUrlDetails": {
      "isChecked": false,
      "typeId": "api",
      "typeName": "API服务器地址",
      "urlStr": "https://bnmd.hjk.api.com.cn:698"
    },
    "configId": "dev_pilot_server_url_details",
    "groupId": "server_url_details",
    "h5ServerUrlDetails": {
      "isChecked": false,
      "typeId": "h5",
      "typeName": "H5服务器地址",
      "urlStr": "https://bnmd.hjk.h5.com.cn:698"
    },
    "imageServerUrlDetails": {
      "isChecked": false,
      "typeId": "image",
      "typeName": "image服务器地址",
      "urlStr": "https://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": "https://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": "https://note.youdao.com/h5"
  },
  "imageServerUrlDetailsInput": {
    "isChecked": true,
    "typeId": "image",
    "typeName": "image服务器地址:",
    "urlStr": "https://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
  }
}
[
  {
    "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": "https://cvbn.ert.api.com.cn:598"
    },
    "imageServerUrlDetails": {
      "typeId": "image",
      "typeName": "image服务器地址",
      "urlStr": "https://cvbn.ert.image.com.cn:598"
    },
    "h5ServerUrlDetails": {
      "typeId": "h5",
      "typeName": "H5服务器地址",
      "urlStr": "https://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": "https://dfgt.uio.api.com.cn:898"
    },
    "imageServerUrlDetails": {
      "typeId": "image",
      "typeName": "image服务器地址",
      "urlStr": "https://dfgt.uio.image.com.cn:898"
    },
    "h5ServerUrlDetails": {
      "typeId": "h5",
      "typeName": "H5服务器地址",
      "urlStr": "https://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": "https://bnmd.hjk.api.com.cn:698"
    },
    "imageServerUrlDetails": {
      "typeId": "image",
      "typeName": "image服务器地址",
      "urlStr": "https://bnmd.hjk.image.com.cn:698"
    },
    "h5ServerUrlDetails": {
      "typeId": "h5",
      "typeName": "H5服务器地址",
      "urlStr": "https://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": "https://asdf.qwe.api.com.cn:798"
    },
    "imageServerUrlDetails": {
      "typeId": "image",
      "typeName": "image服务器地址",
      "urlStr": "https://asdf.qwe.image.com.cn:798"
    },
    "h5ServerUrlDetails": {
      "typeId": "h5",
      "typeName": "H5服务器地址",
      "urlStr": "https://asdf.qwe.h5.com.cn:798"
    }
  }
]
[
  {
    "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 中查看,作为修改的参考依据;

总结

  本文的程序示例只是提供一种实现思路,若项目需求和本例的实现恰好相同或者接近,则可以参考使用,或者差别较大,也希望在其它的设计思路上能够有一定的帮助。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

windfallsheng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值