Android实现动态切换环境配置3.0版本


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

前言

在上一篇幅《Android实现动态切换环境配置2.0版本》我们实现了可以动态切换环境配置的功能,但是一些不同类型的配置参数没有进行区别对待,造成实现逻辑比较耦合,当前3.0版本从页面结构和代码实现上进行了拆分,方便后续不同的参数配置业务功能的修改和扩展。

先上图

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

apk 生产版安装包地址:https://download.csdn.net/download/Silence1515/36760167

项目工程地址:https://download.csdn.net/download/Silence1515/36760903

启动初始化环境配置

  初始化环境配置的方式:

 EnvConfigHelper.getInstance()
                .initDefaultEnvConfig()

  在应用启动时,尽可能早的初始化环境配置,可以将当前环境下的各URL尽快赋值。

package com.windfallsheng;

import android.annotation.SuppressLint;
import android.app.Application;
import android.os.Build;
import android.util.Log;
import com.windfallsheng.envconfig.EnvConfigHelper;
import com.windfallsheng.envconfig.bean.AppInfoBean;
import com.windfallsheng.envconfig.envconfigexample.BuildConfig;
import com.windfallsheng.lib.envconfig.util.EnvUtils;
import com.windfallsheng.lib.envconfig.util.LogUtils;

/**
 * @Author: lzsheng
 * @date: 2021/11/3 13:02
 * @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;
        BaseApplicationHelper.getInstance().initApplicationContext(MyApplication.this);
        // 日志开关;
        @SuppressLint("WrongConstant") boolean isProductRelease = EnvUtils.isProductRelease(BuildConfig.MODE, BuildConfig.BUILD_TYPE);
        Log.i("MyApplication", "onCreate#isProductRelease=" + isProductRelease);
        LogUtils.initDebug(!isProductRelease);
        //
        String manufacturer = Build.MANUFACTURER;
        String model = Build.MODEL;
        String device = Build.DEVICE;
        String release = Build.VERSION.RELEASE;
        LogUtils.dl("MyApplication", "method:onCreate#manufacturer=" + manufacturer +
                ", model=" + model + ", device=" + device + ", release=" + release);

        // 如果配置模块是单独放在一个 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, Build.MANUFACTURER,
                Build.MODEL, Build.DEVICE, Build.VERSION.RELEASE);
        LogUtils.dl("MyApplication", "method:onCreate#appInfoBean=" + appInfoBean);
        EnvConfigHelper.getInstance()
                .initContext(mApplication)
                .setAppInfoBean(appInfoBean);
        // 初始化环境配置参数;
        EnvConfigHelper.getInstance()
                .initDefaultEnvConfig();
        // 打印下默认环境地址;
        if (!isProductRelease) {
            EnvConfigHelper.getInstance()
                    .printEnvInfo();
        }
    }
}

环境配置入口

  通过自定义UI的事件进行触发,进入环境配置入口页面。并且可以对触发条件,通过UI属性进行设置,是否单次点击、是否连续多次点击、点击次数上限,点击时间间隔、是否仅支持查看配置、用户是否可见、是否禁用等等。
在这里插入图片描述

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.BaseDialogFragment;
import com.windfallsheng.envconfig.dialog.EnvConfigDialogFragment;
import com.windfallsheng.envconfig.dialog.EnvConfigViewDetailsDialogFragment;
import com.windfallsheng.lib.envconfig.EnvMode;
import com.windfallsheng.lib.envconfig.util.LogUtils;

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

    private final String TAG = "EnvConfigShadowLayout";
    /**
     * 连续点击次数累计到触发弹框的上限值默认值;
     */
    private final int LIMIT_CLICK_COUNT_DEFAULT = 14;
    /**
     * 连续点击次数的间隔时间默认值;
     */
    private final int MIN_TIME_INTERVAL_DEFAULT = 300;
    /**
     * 连续点击次数的间隔时间
     */
    private int mMinTimeInterval;
    private Context mContext;
    private View mRootView;
    private TextView mTvClickIt;
    /**
     * 是否是单次点击触发弹出配置弹框,false是需要连续多次点击;
     */
    private boolean isSingleClick;
    /**
     * 是否是显式的展示,即用户在UI上是否是可见的;
     */
    private boolean isExplicitDisplay;
    /**
     * 记录连续点击时上次点击的时间;
     */
    private long mLastClickTimeMillis;
    /**
     * 记录连续点击的次数统计;
     */
    private int mClickCount;
    /**
     * 是否禁用;
     */
    private boolean isDisabled;
    /**
     * 是否支持修改环境配置,如果不支持则只能查看;
     */
    private boolean isSupportedUpdateEnv;
    /**
     * 连续点击次数累计到触发弹框的上限值;
     */
    private int mLimitClickCount;

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

    public void setSingleClick(boolean isSingleClick) {
        this.isSingleClick = isSingleClick;
    }

    public void setDisabled(boolean isDisabled) {
        this.isDisabled = isDisabled;
        if (isDisabled) {
            isExplicitDisplay = true;
            mTvClickIt.setOnClickListener(null);
        } else {
            isExplicitDisplay = false;
            initClickEvent();
        }
        initViewDisplayStatus(isExplicitDisplay);
    }

    public void setExplicitDisplay(boolean isExplicitDisplay) {
        this.isExplicitDisplay = isExplicitDisplay;
        initViewDisplayStatus(isExplicitDisplay);
    }

    public void setLimitClickCount(int limitClickCount) {
        mLimitClickCount = limitClickCount;
        handleLimitClickCount();
    }

    public void setMinTimeInterval(int minTimeInterval) {
        mMinTimeInterval = minTimeInterval;
        handleTimeInterval();
    }

    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);
        // 用户是否可见
        isExplicitDisplay = typedArray.getBoolean(R.styleable.env_config_shadow_layout_is_explicit_display, false);
        isSingleClick = typedArray.getBoolean(R.styleable.env_config_shadow_layout_is_single_click, false);
        mMinTimeInterval = typedArray.getInt(R.styleable.env_config_shadow_layout_min_time_interval, MIN_TIME_INTERVAL_DEFAULT);
        isDisabled = typedArray.getBoolean(R.styleable.env_config_shadow_layout_is_disabled, false);
        isSupportedUpdateEnv = typedArray.getBoolean(R.styleable.env_config_shadow_layout_is_supported_update_env, true);
        mLimitClickCount = typedArray.getInt(R.styleable.env_config_shadow_layout_limit_click_count, LIMIT_CLICK_COUNT_DEFAULT);
        typedArray.recycle();

        handleTimeInterval();

        handleLimitClickCount();

        if (isDisabled) {
            LogUtils.dl(TAG, "init#return#isDisabled=" + isDisabled);
            return;
        }

        initViewDisplayStatus(isExplicitDisplay);

        initClickEvent();
    }

    /**
     * 设置数据的上下限;
     */
    private void handleLimitClickCount() {
        if (mLimitClickCount < 2) {
            mLimitClickCount = 2;
        } else if (mMinTimeInterval > 1000) {
            mLimitClickCount = 1000;
        }
    }

    /**
     * 设置数据的上下限;
     */
    private void handleTimeInterval() {
        if (mMinTimeInterval < 100) {
            mMinTimeInterval = 100;
        } else if (mMinTimeInterval > 1000) {
            mMinTimeInterval = 1000;
        }
    }

    private void initViewDisplayStatus(boolean isExplicitDisplay) {
        LogUtils.dl(TAG, "initViewDisplayStatus#isExplicitDisplay" + isExplicitDisplay);
        if (isExplicitDisplay) {
            // 用户是否可见
            mTvClickIt.setTextColor(Color.parseColor("#FA8166"));
            mRootView.setBackgroundColor(Color.parseColor("#F2F2F2"));
        } else {
            mTvClickIt.setTextColor(Color.parseColor("#00000000"));
            mRootView.setBackgroundColor(Color.parseColor("#00000000"));
        }
        // 界面刷新;
        postInvalidate();
    }

    private void initClickEvent() {
        mTvClickIt.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                LogUtils.dl("initClickEvent#onClick#mTvClickIt");
                if (isSingleClick) {
                    // 显示环境配置弹框
                    showEnvConfigDialog();
                    return;
                }
                long currentTimeMillis = System.currentTimeMillis();
                if (mLastClickTimeMillis > 0) {
                    long timeInterval = currentTimeMillis - mLastClickTimeMillis;
                    if (timeInterval < mMinTimeInterval) {
                        mClickCount++;
                    } else {
                        resetCounter();
                    }
                } else {
                    // 首次点击;
                    mClickCount++;
                }
                LogUtils.dl(TAG, "initClickEvent#onClick#mClickCount=" + mClickCount + ", mLimitClickCount=" + mLimitClickCount);

                if (mClickCount >= mLimitClickCount) {
                    // 显示环境配置弹框
                    showEnvConfigDialog();
                    //
                    resetCounter();
                } else {
                    mLastClickTimeMillis = currentTimeMillis;
                }
            }
        });
    }

    /**
     * 弹出选择环境配置的各个页面;
     * <p>
     * 同时,这里做了限制,打包构建时的生产和试运行环境弹出的页面和开发环境是不同的;
     */
    private void showEnvConfigDialog() {
        AppInfoBean appInfoBean = EnvConfigHelper.getInstance().getAppInfoBean();
        if (appInfoBean == null) {
            Toast.makeText(mContext, "appInfoBean = null", Toast.LENGTH_LONG).show();
            return;
        }
        String modeOfBuild = appInfoBean.getModeValueOfBuild();
        if (TextUtils.isEmpty(modeOfBuild)) {
            Toast.makeText(mContext, "modeOfBuild 为空", Toast.LENGTH_LONG).show();
            return;
        }
//            String modeBuild = BuildConfig.MODE;
        // 构建的包是生产和试运行环境时,暂时不支持修改地址;这个业务功能可以根据需求调整是否限制;
        if (TextUtils.equals(modeOfBuild, EnvMode.PILOT_RUN.getMode()) ||
                TextUtils.equals(modeOfBuild, EnvMode.PRODUCT.getMode())) {
            LogUtils.wl(TAG, "method:showEnvConfigDialog#return#modeOfBuild=" + modeOfBuild);
//            Toast.makeText(mContext, modeOfBuild + "环境暂不支持修改环境配置", Toast.LENGTH_LONG).show();
            EnvConfigViewDetailsDialogFragment envConfigViewDetailsDialogFragment = EnvConfigViewDetailsDialogFragment.newInstance();
            showDialogFragment(envConfigViewDetailsDialogFragment, "envConfigViewDetailsDialogFragment");
            return;
        }

        // 根据是否支持修改环境配置状态判断弹框页面的内容;
        if (isSupportedUpdateEnv) {
            EnvConfigDialogFragment envConfigDialogFragment = EnvConfigDialogFragment.newInstance();
            showDialogFragment(envConfigDialogFragment, "envConfigDialogFragment");
        } else {
            EnvConfigViewDetailsDialogFragment envConfigViewDetailsDialogFragment = EnvConfigViewDetailsDialogFragment.newInstance();
            showDialogFragment(envConfigViewDetailsDialogFragment, "envConfigViewDetailsDialogFragment");
        }
    }

    private void showDialogFragment(BaseDialogFragment targetDialogFragment, String tag) {
        if (mContext instanceof FragmentActivity) {
            FragmentActivity activity = (FragmentActivity) mContext;
            targetDialogFragment.show(activity.getSupportFragmentManager(), tag);
        } else {
            Toast.makeText(mContext, "Context not instance of FragmentActivity.", Toast.LENGTH_LONG).show();
            LogUtils.wl(TAG, "method:showEnvConfigDialog#Context not instance of FragmentActivity.");
        }
    }

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

环境配置初始化

  用户初始化默认选择的环境配置参数,同时可以根据选择的环境修改相应的数据;

package com.windfallsheng.envconfig;

import android.content.Context;
import android.text.TextUtils;
import com.windfallsheng.envconfig.bean.AllServersUrlConfigBean;
import com.windfallsheng.envconfig.bean.AppInfoBean;
import com.windfallsheng.envconfig.bean.EnvConfigBean;
import com.windfallsheng.envconfig.bean.EnvConfigWrapper;
import com.windfallsheng.envconfig.bean.MockUserInfoWrapper;
import com.windfallsheng.envconfig.bean.UrlDetailsBean;
import com.windfallsheng.lib.envconfig.Consts;
import com.windfallsheng.lib.envconfig.EnvMode;
import com.windfallsheng.lib.envconfig.EnvServerUrlConsts;
import com.windfallsheng.lib.envconfig.MODE;
import com.windfallsheng.lib.envconfig.util.CollectionUtils;
import com.windfallsheng.lib.envconfig.util.LogUtils;
import com.windfallsheng.lib.xxpermissions.Permission;
import com.windfallsheng.lib.xxpermissions.XXPermissions;
import java.util.Map;

/**
 * @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 EnvMode mEnvModeInUse;
    /**
     * 根据环境配置中查看是否跳过登录;
     */
    private boolean hasSkipLogin;
    /**
     * 根据环境配置中查看是否跳过广告页;
     */
    private boolean hasSkipAdPage;

    /**
     * 根据环境配置中查看是否使用模拟用户;
     */
    private boolean hasUseMockUser;

    private boolean startService1;
    private boolean disableService1;
    private boolean ignoreService1;
    private boolean forceService1;
    /**
     * 根据环境配置中查看模拟用户;
     */
    private MockUserInfoWrapper mMockUserInfoWrapper;
    /**
     * 主项目中注入的应用信息;
     */
    private AppInfoBean mAppInfoBean;

    private EnvConfigHelper() {
    }

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

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

    /**
     * 获取当前打包构建的环境模式;
     *
     * @return
     */
    public String getModeOfBuild() {
        if (mAppInfoBean != null) {
            return mAppInfoBean.getFlavor();
        }
        return "";
    }

    public String getModeValueInUse() {
        if (mEnvModeInUse != null) {
            return mEnvModeInUse.getMode();
        }
        return "";
    }

    public EnvMode getEnvModeInUse() {
        return mEnvModeInUse;
    }

    public AppInfoBean getAppInfoBean() {
        return mAppInfoBean;
    }

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

    public MockUserInfoWrapper getMockUserInfoWrapper() {
        return mMockUserInfoWrapper;
    }

    public void setMockUserInfoWrapper(MockUserInfoWrapper mockUserInfoWrapper) {
        mMockUserInfoWrapper = mockUserInfoWrapper;
    }

    /**
     * 打印当前使用的环境的配置信息详情;
     */
    public void printEnvInfo() {
        String envDesc = null;
        if (mEnvModeInUse != null) {
            envDesc = mEnvModeInUse.getDesc();
        }
        LogUtils.dl(TAG, "printEnvInfo★★" +
                "\n★★当前使用中的环境=" + envDesc + "=modeValue=" + mEnvModeInUse.getMode() + "★★" +
                ",\n★★打包模式modeOfBuild/FLAVOR=" + getModeOfBuild() + "★★" +
                ",\n★★EnvServerUrlConsts.TargetServer.API=" + EnvServerUrlConsts.TargetServer.API + "★★" +
                ",\n★★EnvServerUrlConsts.TargetServer.IMAGE=" + EnvServerUrlConsts.TargetServer.IMAGE + "★★" +
                ",\n★★EnvServerUrlConsts.TargetServer.H5=" + EnvServerUrlConsts.TargetServer.H5 + "★★" +
                ",\n★★hasSkipLogin=" + hasSkipLogin + "★★" +
                ",\n★★hasSkipAdPage=" + hasSkipAdPage + "★★" +
                ",\n★★startService1=" + startService1 + "★★" +
                ",\n★★disableService1=" + disableService1 + "★★" +
                ",\n★★hasUseMockUser=" + hasUseMockUser + "★★" +
                ",\n★★ignoreService1=" + ignoreService1 + "★★" +
                ",\n★★forceService1=" + forceService1 + "★★" +
                ",\n★★mMockUserInfoWrapper=" + mMockUserInfoWrapper + "★★");
    }

    /**
     * 在应用启动时最先初始化配置信息;
     * <p>
     * 如果有缓存的配置数据,则从缓存数据读取;
     */
    public void initDefaultEnvConfig() {
        LogUtils.dl(TAG, "method:initDefaultEnvConfig#mAppInfoBean=" + mAppInfoBean);
        if (mAppInfoBean == null) {
            LogUtils.el(TAG, "method:initDefaultEnvConfig#return#mAppInfoBean == null");
            return;
        }

        String modeValueOfBuild = mAppInfoBean.getModeValueOfBuild();
        LogUtils.dl(TAG, "method:initDefaultEnvConfig#modeValueOfBuild=" + modeValueOfBuild);

        if (TextUtils.isEmpty(modeValueOfBuild)) {
            LogUtils.el(TAG, "method:initDefaultEnvConfig#return#modeValueOfBuild=" + modeValueOfBuild);
            return;
        }
        // 如果打包构建的环境是生产和试运行,则以打包环境为准
        if (TextUtils.equals(modeValueOfBuild, Consts.EnvMode.PILOT_RUN) ||
                TextUtils.equals(modeValueOfBuild, Consts.EnvMode.PRODUCT)) {
            updateEnvServerUrlBySelectedMode(modeValueOfBuild);
        } else if (TextUtils.equals(modeValueOfBuild, Consts.EnvMode.DEVELOPMENT)) {
            // 如果是开发环境,先检查权限,没有权限则直接按打包环境状态修改;
            if (!XXPermissions.isGrantedPermission(mContext, Permission.WRITE_EXTERNAL_STORAGE,
                    Permission.READ_EXTERNAL_STORAGE)) {
                updateEnvServerUrlBySelectedMode(modeValueOfBuild);
                LogUtils.dl(TAG, "method:initDefaultEnvConfig#return#XXPermissions#isGrantedPermission = false");
                return;
            }
            // 如果是开发环境,先检查是否有上次缓存的配置数据;
            EnvConfigWrapper envConfigWrapper = EnvConfigUtils.initEnvConfigWrapperInUse(mContext);
            LogUtils.dl(TAG, "method:initDefaultEnvConfig#envConfigWrapper=" + envConfigWrapper);
            if (envConfigWrapper == null || envConfigWrapper.isEmpty()) {
                // 如果没有缓存数据,则以构建环境为准;
                modeValueOfBuild = mAppInfoBean.getModeValueOfBuild();
                updateEnvServerUrlBySelectedMode(modeValueOfBuild);
            } else {
                // 如果上次缓存的有数据,则以缓存数据为准;
                updateEnvServerUrlBySelectedMode(envConfigWrapper);
            }
        }
    }

    /**
     * 根据选择的环境类型,调整环境配置;
     *
     * @param modeValue
     */
    public void updateEnvServerUrlBySelectedMode(@MODE String modeValue, EnvConfigWrapper envConfigWrapper) {
        // 修改当前使用的环境模式为已修改的模式;
        this.mEnvModeInUse = getEnvModeByModeValue(modeValue);
        // 是否有手动输入的地址项;
        String urlInputApiStr = "";
        String urlImageStr = "";
        String urlH5Str = "";
        if (envConfigWrapper != null) {
            // 处理一些开关项配置;
            initSwitchStatus(envConfigWrapper.getSwitchConfigMap());
            /**
             * 判断是否有手动输入的地址项;
             * 初始化手动输入的URL地址;
             */
            final Map<String, UrlDetailsBean> serverUrlDetailsSelectedMap = envConfigWrapper.getUrlConfigDetailsInputSelectedMap();
            if (CollectionUtils.isNotEmptyMap(serverUrlDetailsSelectedMap)) {
                urlInputApiStr = EnvConfigUtils.initSelectedUrlStrByTypeId(EnvConfigConsts.UrlInputType.TYPE_ID_API, serverUrlDetailsSelectedMap);
                urlImageStr = EnvConfigUtils.initSelectedUrlStrByTypeId(EnvConfigConsts.UrlInputType.TYPE_ID_IMAGE, serverUrlDetailsSelectedMap);
                urlH5Str = EnvConfigUtils.initSelectedUrlStrByTypeId(EnvConfigConsts.UrlInputType.TYPE_ID_H5, serverUrlDetailsSelectedMap);
                // 手动输入的地址项优先赋值;
                if (!TextUtils.isEmpty(urlInputApiStr)) {
                    if (!urlInputApiStr.endsWith("/")) {
                        // retrofit的baseUrl必须以“/”结尾,否则抛异常
                        urlInputApiStr += "/";
                    }
                    EnvServerUrlConsts.TargetServer.API = urlInputApiStr;
                }
                if (!TextUtils.isEmpty(urlImageStr)) {
                    EnvServerUrlConsts.TargetServer.IMAGE = urlImageStr;
                }
                if (!TextUtils.isEmpty(urlH5Str)) {
                    EnvServerUrlConsts.TargetServer.H5 = urlH5Str;
                }
            }
        }
        // 根据环境选择,进行各个URL地址的赋值操作,及部分环境默认的配置开关的状态处理;
        switch (modeValue) {
            case Consts.EnvMode.DEV_TEST_LOCAL:
                // 手动输入的地址项优先赋值,若没有输入地址,则使用默认配置地址;
                if (TextUtils.isEmpty(urlInputApiStr)) {
                    EnvServerUrlConsts.TargetServer.API = EnvServerUrlConsts.Protocol.HTTP + EnvServerUrlConsts.TestLocalServer.API;
                }
                if (TextUtils.isEmpty(urlImageStr)) {
                    EnvServerUrlConsts.TargetServer.IMAGE = EnvServerUrlConsts.Protocol.HTTP + EnvServerUrlConsts.TestLocalServer.IMAGE;
                }
                if (TextUtils.isEmpty(urlH5Str)) {
                    EnvServerUrlConsts.TargetServer.H5 = EnvServerUrlConsts.Protocol.HTTP + EnvServerUrlConsts.TestLocalServer.H5;
                }
                break;
            case Consts.EnvMode.DEVELOPMENT:
            case Consts.EnvMode.DEV_TEST_SERVER_1:
                // 手动输入的地址项优先赋值,若没有输入地址,则使用默认配置地址;
                if (TextUtils.isEmpty(urlInputApiStr)) {
                    EnvServerUrlConsts.TargetServer.API = EnvServerUrlConsts.Protocol.HTTP + EnvServerUrlConsts.TestServer_1.API;
                }
                if (TextUtils.isEmpty(urlImageStr)) {
                    EnvServerUrlConsts.TargetServer.IMAGE = EnvServerUrlConsts.Protocol.HTTP + EnvServerUrlConsts.TestServer_1.IMAGE;
                }
                if (TextUtils.isEmpty(urlH5Str)) {
                    EnvServerUrlConsts.TargetServer.H5 = EnvServerUrlConsts.Protocol.HTTP + EnvServerUrlConsts.TestServer_1.H5;
                }
                break;
            case Consts.EnvMode.DEV_TEST_SERVER_2:
                // 手动输入的地址项优先赋值,若没有输入地址,则使用默认配置地址;
                if (TextUtils.isEmpty(urlInputApiStr)) {
                    EnvServerUrlConsts.TargetServer.API = EnvServerUrlConsts.Protocol.HTTP + EnvServerUrlConsts.TestServer_2.API;
                }
                if (TextUtils.isEmpty(urlImageStr)) {
                    EnvServerUrlConsts.TargetServer.IMAGE = EnvServerUrlConsts.Protocol.HTTP + EnvServerUrlConsts.TestServer_2.IMAGE;
                }
                if (TextUtils.isEmpty(urlH5Str)) {
                    EnvServerUrlConsts.TargetServer.H5 = EnvServerUrlConsts.Protocol.HTTP + EnvServerUrlConsts.TestServer_2.H5;
                }
                break;
            case Consts.EnvMode.DEV_PILOT_RUN:
                // 手动输入的地址项优先赋值,若没有输入地址,则使用默认配置地址;
                if (TextUtils.isEmpty(urlInputApiStr)) {
                    EnvServerUrlConsts.TargetServer.API = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.PilotRunServer.API;
                }
                if (TextUtils.isEmpty(urlImageStr)) {
                    EnvServerUrlConsts.TargetServer.IMAGE = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.PilotRunServer.IMAGE;
                }
                if (TextUtils.isEmpty(urlH5Str)) {
                    EnvServerUrlConsts.TargetServer.H5 = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.PilotRunServer.H5;
                }
                break;
            case Consts.EnvMode.DEV_PRODUCT:
                // 手动输入的地址项优先赋值,若没有输入地址,则使用默认配置地址;
                if (TextUtils.isEmpty(urlInputApiStr)) {
                    EnvServerUrlConsts.TargetServer.API = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.ProductServer.API;
                }
                if (TextUtils.isEmpty(urlImageStr)) {
                    EnvServerUrlConsts.TargetServer.IMAGE = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.ProductServer.IMAGE;
                }
                if (TextUtils.isEmpty(urlH5Str)) {
                    EnvServerUrlConsts.TargetServer.H5 = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.ProductServer.H5;
                }
                break;
            case Consts.EnvMode.PILOT_RUN:
                handleEnvOfPilotRun();
                break;
            case Consts.EnvMode.PRODUCT:
                handleEnvOfProduct();
                break;
            default:
                LogUtils.el(TAG, "method:updateEnvServerUrlBySelectedMode#未处理当前配置环境的逻辑分支");
                break;
        }
        //
        String envDesc = null;
        if (mEnvModeInUse != null) {
            envDesc = mEnvModeInUse.getDesc();
        }
        LogUtils.dl(TAG, "updateEnvServerUrlBySelectedMode★★" +
                "\n★★当前使用中的环境=" + envDesc + "=modeValue=" + modeValue + "★★" +
                ",\n★★打包模式modeOfBuild/FLAVOR=" + getModeOfBuild() + "★★" +
                ",\n★★EnvServerUrlConsts.TargetServer.API=" + EnvServerUrlConsts.TargetServer.API + "★★" +
                ",\n★★EnvServerUrlConsts.TargetServer.IMAGE=" + EnvServerUrlConsts.TargetServer.IMAGE + "★★" +
                ",\n★★EnvServerUrlConsts.TargetServer.H5=" + EnvServerUrlConsts.TargetServer.H5 + "★★" +
                ",\n★★hasSkipLogin=" + hasSkipLogin + "★★" +
                ",\n★★hasSkipAdPage=" + hasSkipAdPage + "★★" +
                ",\n★★startService1=" + startService1 + "★★" +
                ",\n★★disableService1=" + disableService1 + "★★" +
                ",\n★★hasUseMockUser=" + hasUseMockUser + "★★" +
                ",\n★★ignoreService1=" + ignoreService1 + "★★" +
                ",\n★★forceService1=" + forceService1 + "★★" +
                ",\n★★mMockUserInfoWrapper=" + mMockUserInfoWrapper + "★★");
    }

    /**
     * 处理试运行环境的配置信息;
     */
    private void handleEnvOfPilotRun() {
        hasSkipLogin = false;
        hasSkipAdPage = false;
        startService1 = false;
        disableService1 = false;
        hasUseMockUser = false;
        ignoreService1 = false;
        // 试运行环境需要开启
        forceService1 = true;
        //
        EnvServerUrlConsts.TargetServer.API = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.PilotRunServer.API;
        EnvServerUrlConsts.TargetServer.IMAGE = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.PilotRunServer.IMAGE;
        EnvServerUrlConsts.TargetServer.H5 = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.PilotRunServer.H5;
    }

    /**
     * 处理试生产环境的配置信息;
     */
    private void handleEnvOfProduct() {
        hasSkipLogin = false;
        hasSkipAdPage = false;
        startService1 = false;
        disableService1 = false;
        hasUseMockUser = false;
        ignoreService1 = false;
        // 生产环境需要开启
        forceService1 = true;
        //
        EnvServerUrlConsts.TargetServer.API = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.ProductServer.API;
        EnvServerUrlConsts.TargetServer.IMAGE = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.ProductServer.IMAGE;
        EnvServerUrlConsts.TargetServer.H5 = EnvServerUrlConsts.Protocol.HTTPS + EnvServerUrlConsts.ProductServer.H5;
    }

    private EnvMode getEnvModeByModeValue(String modeSelected) {
        switch (modeSelected) {
            case Consts.EnvMode.DEV_TEST_LOCAL:
                return EnvMode.DEV_TEST_LOCAL;
            case Consts.EnvMode.DEV_TEST_SERVER_1:
                return EnvMode.DEV_TEST_SERVER_1;
            case Consts.EnvMode.DEV_TEST_SERVER_2:
                return EnvMode.DEV_TEST_SERVER_2;
            case Consts.EnvMode.DEV_PILOT_RUN:
                return EnvMode.DEV_PILOT_RUN;
            case Consts.EnvMode.DEV_PRODUCT:
                return EnvMode.DEV_PRODUCT;
            case Consts.EnvMode.DEVELOPMENT:
                return EnvMode.DEVELOPMENT;
            case Consts.EnvMode.PILOT_RUN:
                return EnvMode.PILOT_RUN;
            case Consts.EnvMode.PRODUCT:
                return EnvMode.PRODUCT;
            default:
                LogUtils.el(TAG, "method:getEnvModeByModeValue#未处理当前逻辑分支");
                break;
        }
        return null;
    }

    /**
     * 根据选择的环境类型,调整环境配置;
     *
     * @param envConfigWrapper
     */
    public void updateEnvServerUrlBySelectedMode(EnvConfigWrapper envConfigWrapper) {
        if (envConfigWrapper == null) {
            LogUtils.el(TAG, "method:updateEnvServerUrl#return#envConfigWrapper == null");
            return;
        }
        AllServersUrlConfigBean allServersUrlConfigBean = envConfigWrapper.getAllServerUrlDetails();
        if (allServersUrlConfigBean == null) {
            LogUtils.el(TAG, "method:updateEnvServerUrl#return#allServersUrlConfigBean == null");
            return;
        }
        String modeValue = allServersUrlConfigBean.getModeOfEnv();
        updateEnvServerUrlBySelectedMode(modeValue, envConfigWrapper);
    }

    /**
     * 根据选择的环境类型,调整环境配置;
     *
     * @param modeValue
     */
    public void updateEnvServerUrlBySelectedMode(@MODE String modeValue) {
        updateEnvServerUrlBySelectedMode(modeValue, null);
    }

    /**
     * 根据环境配置中查看是否跳过登录、开启禁止代理等开关项的配置;
     *
     * @param switchConfigMap
     */
    public void initSwitchStatus(Map<String, EnvConfigBean> switchConfigMap) {
        if (CollectionUtils.isEmptyMap(switchConfigMap)) {
            return;
        }
        hasSkipLogin = initSwitchStatusByConfigId(EnvConfigConsts.ConfigId.SKIP_LOGIN, switchConfigMap);
        hasSkipAdPage = initSwitchStatusByConfigId(EnvConfigConsts.ConfigId.SKIP_AD_PAGE, switchConfigMap);
        hasUseMockUser = initSwitchStatusByConfigId(EnvConfigConsts.ConfigId.USE_MOCK_USER, switchConfigMap);
        startService1 = initSwitchStatusByConfigId(EnvConfigConsts.ConfigId.START_SERVICE_1, switchConfigMap);
        disableService1 = initSwitchStatusByConfigId(EnvConfigConsts.ConfigId.DISABLE_SERVICE_1, switchConfigMap);
        ignoreService1 = initSwitchStatusByConfigId(EnvConfigConsts.ConfigId.IGNORE_SERVICE_1, switchConfigMap);
        forceService1 = initSwitchStatusByConfigId(EnvConfigConsts.ConfigId.FORCE_SERVICE_1, switchConfigMap);
    }

    /**
     * 默认返回false;
     *
     * @param configId
     * @param switchConfigMap
     * @return
     */
    private boolean initSwitchStatusByConfigId(String configId, Map<String, EnvConfigBean> switchConfigMap) {
        if (!switchConfigMap.containsKey(configId)) {
            return false;
        }
        final EnvConfigBean envConfigBean = switchConfigMap.get(configId);
        if (envConfigBean == null) {
            return false;
        }
        return envConfigBean.isChecked();
    }

    /**
     * 根据环境配置中查看是否跳过登录;
     */
    public boolean hasSkipLogin() {
        return hasSkipLogin;
    }

    /**
     * 根据环境配置中查看是否使用模拟用户;
     */
    public boolean hasUseMockUser() {
        return hasUseMockUser;
    }

    public boolean hasSkipAdPage() {
        return hasSkipAdPage;
    }


    public boolean isStartService1() {
        return startService1;
    }

    public boolean isDisableService1() {
        return disableService1;
    }

    public boolean isIgnoreService1() {
        return ignoreService1;
    }

    public boolean isForceService1() {
        return forceService1;
    }

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

静态URL配置

  解析 json 配置中的各个环境的地址,根据本地缓存的相应数据的状态,再修改相应数据的内容,展示在UI列表上,同时处理选择环境的事件逻辑,及页面数据的重置。

package com.windfallsheng.envconfig.fragment;

import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.windfallsheng.envconfig.EnvConfigConsts;
import com.windfallsheng.envconfig.EnvConfigHelper;
import com.windfallsheng.envconfig.EnvConfigUtils;
import com.windfallsheng.envconfig.R;
import com.windfallsheng.envconfig.adapter.AllServerUrlDetailsConfigAdapter;
import com.windfallsheng.envconfig.bean.AllServersUrlConfigBean;
import com.windfallsheng.envconfig.bean.AppInfoBean;
import com.windfallsheng.lib.ToastUtils;
import com.windfallsheng.lib.envconfig.EnvMode;
import com.windfallsheng.lib.envconfig.util.CollectionUtils;
import com.windfallsheng.lib.envconfig.util.LogUtils;
import java.util.List;

/**
 * @Author: lzsheng
 * @date: 2021/10/26 15:32
 * @description: URL配置相关
 */
public class ServerUrlConfigListFragment extends Fragment {

    private final String TAG = "ServerUrlConfigListFragment";
    private Context mContext;
    private View rootView;
    private RecyclerView mRecyclerView;

    private AllServerUrlDetailsConfigAdapter mAllServerUrlDetailsConfigAdapter;
    private List<AllServersUrlConfigBean> mEnvConfigList;
    private AllServersUrlConfigBean mAllServerUrlDetailsSelected;

    private ServerUrlConfigListFragment() {
    }

    public static ServerUrlConfigListFragment newInstance() {
        ServerUrlConfigListFragment fragment = new ServerUrlConfigListFragment();
        return fragment;
    }

    @Override
    public void onAttach(Context context) {
        mContext = context;
        super.onAttach(context);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        rootView = inflater.inflate(R.layout.fragment_all_server_url_details_config, container, false);
        mRecyclerView = rootView.findViewById(R.id.recyclerview_config);
        return rootView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(mContext);
        mRecyclerView.setLayoutManager(mLinearLayoutManager);
        mRecyclerView.setNestedScrollingEnabled(false);
        // 初始化列表数据;
        initListData();

        mAllServerUrlDetailsConfigAdapter = new AllServerUrlDetailsConfigAdapter();

        mAllServerUrlDetailsConfigAdapter.addAll(mEnvConfigList);
        mRecyclerView.setAdapter(mAllServerUrlDetailsConfigAdapter);
        // 行点击事件
        mAllServerUrlDetailsConfigAdapter.setOnRecyclerViewItemClick((AllServerUrlDetailsConfigAdapter.OnRecyclerViewItemClick<AllServersUrlConfigBean>)
                (childView, position, allServersUrlConfigBean) -> {
                    refreshListData(position, allServersUrlConfigBean);
                    mAllServerUrlDetailsConfigAdapter.notifyDataSetChanged();
                });
        // 在相关的数据初始化完成后,获取选中项;
        mAllServerUrlDetailsSelected = getConfigSelected();
    }

    private void initListData() {
        mEnvConfigList = EnvConfigUtils.getAllServersUrlConfigFromJson();
        // 本地缓存数据;
        AllServersUrlConfigBean  allServerUrlDetailsCache = EnvConfigUtils.getAllServersUrlConfigFromCache(mContext);
        // 如果最近一次的选中数据,则修改默认选中项;
        initEnvConfigListDataAndViews(mEnvConfigList, allServerUrlDetailsCache);
    }

    public void resetListAndViews() {
        mEnvConfigList = EnvConfigUtils.getAllServersUrlConfigFromJson();
        mAllServerUrlDetailsConfigAdapter.addNewAll(mEnvConfigList);
        mAllServerUrlDetailsConfigAdapter.notifyDataSetChanged();
        // 在相关的数据初始化完成后,获取选中项;
        mAllServerUrlDetailsSelected = getConfigSelected();
    }

    /**
     * 返回这个页面的配置信息,将选中的配置项返回;
     *
     * @return
     */
    public AllServersUrlConfigBean getAllServerUrlDetailsSelected() {
        if (mAllServerUrlDetailsSelected == null) {
            ToastUtils.showToast("URL配置项未选中");
            return null;
        } else {
            // 将数据对象的copy返回给外界使用,不能和当前页面持有的对象产生关联影响;
            return (AllServersUrlConfigBean) mAllServerUrlDetailsSelected.clone();
        }
    }

    /**
     * 根据点击事件发生所在position,及数据类型,修改了数据集合中相关数据的状态;
     *
     * @param position
     */
    private void refreshListData(int position, AllServersUrlConfigBean allServersUrlConfigBean) {
        LogUtils.d(TAG, "method:updateListData#allServersUrlConfigBean=" + allServersUrlConfigBean);
        final List<AllServersUrlConfigBean> envConfigList = mAllServerUrlDetailsConfigAdapter.getEnvConfigList();
        for (int i = 0; i < envConfigList.size(); i++) {
            AllServersUrlConfigBean item = envConfigList.get(i);
            final String groupId = item.getGroupId();
            if (TextUtils.equals(groupId, EnvConfigConsts.GroupId.SERVER_URL_DETAILS)) {
                // 处理各服务器地址选项的选择事件;
                if (position == i) {
                    // 被点击的行的处理业务;
                    boolean isChecked = item.isChecked();
                    if (isChecked) {
                        // 如果当前项在点击前就是选中状态时,要根据是否是必有选项进行处理;
                        final boolean isRequired = allServersUrlConfigBean.isRequired();
                        if (isRequired) {
                            // 必须有选择项时,不修改选中状态;
                        } else {
                            item.setChecked(!isChecked);
                        }
                        mAllServerUrlDetailsSelected = item;
                    } else {
                        // 如果当前项在点击前就是未选中状态时;
                        item.setChecked(!isChecked);
                        mAllServerUrlDetailsSelected = item;
                    }
                } else {
                    // 未被点击的行都应该是false;
                    item.setChecked(EnvConfigConsts.Bool.UNCHECKED);
                }
            }
        }
    }

    /**
     * 如果最近一次的选中数据有值,则修改默认选中项,如果没有则按默认项处理;
     *
     * @param allServersUrlConfigList
     */
    private void initEnvConfigListDataAndViews(List<AllServersUrlConfigBean> allServersUrlConfigList, AllServersUrlConfigBean allServerUrlDetailsCache) {
        if (allServerUrlDetailsCache == null) {
            // 没有本地缓存数据时,设置默认选项;
            AppInfoBean appInfoBean = EnvConfigHelper.getInstance().getAppInfoBean();
            if (appInfoBean == null) {
                Toast.makeText(mContext, "appInfoBean = null", Toast.LENGTH_LONG).show();
                return;
            }
            String modeOfBuild = appInfoBean.getModeValueOfBuild();
            if (TextUtils.isEmpty(modeOfBuild)) {
                Toast.makeText(mContext, "modeOfBuild 为空", Toast.LENGTH_LONG).show();
                return;
            }
            for (AllServersUrlConfigBean serversUrlConfigBean : allServersUrlConfigList) {
                handServerUrlDetalsDefault(modeOfBuild, serversUrlConfigBean);
            }
            return;
        }
        // 有本地缓存时,将本地缓存数据修改到列表中;
        final String modeOfEnvCache = allServerUrlDetailsCache.getModeOfEnv();
        if (CollectionUtils.isNotEmpty(allServersUrlConfigList)) {
            for (int i = 0; i < allServersUrlConfigList.size(); i++) {
                final AllServersUrlConfigBean allServersUrlConfigBean = allServersUrlConfigList.get(i);
                final String modeOfEnv = allServersUrlConfigBean.getModeOfEnv();
                if (TextUtils.equals(modeOfEnv, modeOfEnvCache)) {
                    allServersUrlConfigBean.setChecked(allServerUrlDetailsCache.isChecked());
                } else {
                    allServersUrlConfigBean.setChecked(EnvConfigConsts.Bool.UNCHECKED);
                }
            }
        }
    }

    /**
     * 获取选中数据;
     */
    private AllServersUrlConfigBean getConfigSelected() {
        if (CollectionUtils.isNotEmpty(mEnvConfigList)) {
            for (int i = 0; i < mEnvConfigList.size(); i++) {
                final AllServersUrlConfigBean allServersUrlConfigBean = mEnvConfigList.get(i);
                if (allServersUrlConfigBean.isChecked()) {
                    return allServersUrlConfigBean;
                }
            }
        }
        return null;
    }

    private void handServerUrlDetalsDefault(String modeBuild, AllServersUrlConfigBean allServersUrlConfigBean) {
        String modeValue = allServersUrlConfigBean.getModeOfEnv();
        if (TextUtils.equals(modeBuild, EnvMode.DEVELOPMENT.getMode())) {
            // 开发环境默认使用 DEV_TEST_SERVER;
            String modeDevServer = EnvMode.DEV_TEST_SERVER_1.getMode();
            if (TextUtils.equals(modeValue, modeDevServer)) {
                allServersUrlConfigBean.setChecked(EnvConfigConsts.Bool.CHECKED);
            } else {
                allServersUrlConfigBean.setChecked(EnvConfigConsts.Bool.UNCHECKED);
            }
        } else if (TextUtils.equals(modeBuild, EnvMode.PILOT_RUN.getMode())) {
            // 试运行环境默认使用 DEV_PILOT_RUN;
            String modeDevServer = EnvMode.DEV_PILOT_RUN.getMode();
            if (TextUtils.equals(modeValue, modeDevServer)) {
                allServersUrlConfigBean.setChecked(EnvConfigConsts.Bool.CHECKED);
            } else {
                allServersUrlConfigBean.setChecked(EnvConfigConsts.Bool.UNCHECKED);
            }
        } else if (TextUtils.equals(modeBuild, EnvMode.PRODUCT.getMode())) {
            // 生产环境默认使用 DEV_PRODUCT;
            String modeDevServer = EnvMode.DEV_PRODUCT.getMode();
            if (TextUtils.equals(modeValue, modeDevServer)) {
                allServersUrlConfigBean.setChecked(EnvConfigConsts.Bool.CHECKED);
            } else {
                allServersUrlConfigBean.setChecked(EnvConfigConsts.Bool.UNCHECKED);
            }
        }
    }
}

手输URL配置

  在静态的URL配置之外,还支持手动输入URL,如果手动输入的URL地址被选择,则会优先使用手动输入的地址。另外在配置被应用后,可以保存输入的地址到历史记录中,方便后期再次使用,URL的历史记录保存数量有上限,并且将最新输入内容保存在最上面,同时地址不会重复保存:

package com.windfallsheng.envconfig.fragment;

import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import com.google.gson.Gson;
import com.windfallsheng.envconfig.EnvConfigConsts;
import com.windfallsheng.envconfig.EnvConfigUtils;
import com.windfallsheng.envconfig.R;
import com.windfallsheng.envconfig.bean.UrlDetailsBean;
import com.windfallsheng.envconfig.bean.UrlHistoryBean;
import com.windfallsheng.envconfig.dialog.SingleSelectionDialog;
import com.windfallsheng.envconfig.widget.UrlDetailsInputLayout;
import com.windfallsheng.lib.ToastUtils;
import com.windfallsheng.lib.envconfig.util.CollectionUtils;
import com.windfallsheng.lib.envconfig.util.LogUtils;
import com.windfallsheng.lib.envconfig.util.SharedPrefsManager;
import com.windfallsheng.lib.xxpermissions.Permission;
import com.windfallsheng.lib.xxpermissions.XXPermissions;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: lzsheng
 * @date: 2021/10/26 15:32
 * @description: 手动输入的URL配置相关
 */
public class ServerUrlManualInputFragment extends Fragment implements
        UrlDetailsInputLayout.OnShowUrlHistoryListener, UrlDetailsInputLayout.OnCheckBoxClickListener {

    //    private static final String ARG_PARAM1 = "param1";
//    private static final String ARG_PARAM2 = "param2";
    private final String TAG = "ServerUrlManualInputFragment";
    /**
     * 可缓存的最大历史url数量;
     */
    private final int MAX_COUNT_OF_HISTORY_URL = 50;
    private Context mContext;
    private View rootView;
    /**
     * key为{@link UrlDetailsBean#typeId};
     */
//    private Map<String, UrlDetailsBean> mUrlConfigDetailsInputSelectedMap;
    /**
     *
     */
    private UrlDetailsBean mApiUrlDetailsBean;
    private UrlDetailsBean mImageUrlDetailsBean;
    private UrlDetailsBean mH5UrlDetailsBean;
    /**
     * 手动输入URL的UI部分;
     */
    private UrlDetailsInputLayout mUrlInputLayoutApi;
    private UrlDetailsInputLayout mUrlInputLayoutImage;
    private UrlDetailsInputLayout mUrlInputLayoutH5;

    private ServerUrlManualInputFragment() {
        // Required empty public constructor
    }


    //    public static UrlConfigFragment newInstance(String param1, String param2) {
    public static ServerUrlManualInputFragment newInstance() {
        ServerUrlManualInputFragment fragment = new ServerUrlManualInputFragment();
//        Bundle args = new Bundle();
//        args.putString(ARG_PARAM1, param1);
//        args.putString(ARG_PARAM2, param2);
//        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onAttach(Context context) {
        mContext = context;
        super.onAttach(context);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        rootView = inflater.inflate(R.layout.fragment_server_url_details_input, container, false);
        // 设置ID;
        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);
        mUrlInputLayoutApi.setShowHistoryViewId(R.id.url_detail_input_api_id);
        mUrlInputLayoutImage.setShowHistoryViewId(R.id.url_detail_input_image_id);
        mUrlInputLayoutH5.setShowHistoryViewId(R.id.url_detail_input_h5_id);
        return rootView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // 设置事件回调;
        mUrlInputLayoutApi.setOnShowUrlHistoryListener(ServerUrlManualInputFragment.this);
        mUrlInputLayoutApi.setOnCheckBoxClickListener(ServerUrlManualInputFragment.this);
        mUrlInputLayoutImage.setOnShowUrlHistoryListener(ServerUrlManualInputFragment.this);
        mUrlInputLayoutImage.setOnCheckBoxClickListener(ServerUrlManualInputFragment.this);
        mUrlInputLayoutH5.setOnShowUrlHistoryListener(ServerUrlManualInputFragment.this);
        mUrlInputLayoutH5.setOnCheckBoxClickListener(ServerUrlManualInputFragment.this);
        // 如果最近一次的选中数据,则修改默认选中项;
        initData();
        LogUtils.dl(TAG, "method:onActivityCreated#mApiUrlDetailsBean=" + mApiUrlDetailsBean +
                ", mWsUrlDetailsBean=" + mImageUrlDetailsBean);
        // 根据数据状态修改UI;
        mUrlInputLayoutApi.refreshViewsByUrlDetails(mApiUrlDetailsBean);
        mUrlInputLayoutImage.refreshViewsByUrlDetails(mImageUrlDetailsBean);
        mUrlInputLayoutH5.refreshViewsByUrlDetails(mH5UrlDetailsBean);
    }

    /**
     * 将所有的数据全部返回;
     *
     * @return
     */
    public Map<String, UrlDetailsBean> getServerUrlDetailsInput() {
        Map<String, UrlDetailsBean> map = new HashMap<>();
        // 输入的地址内容可能会发生改变,需要重新获取内部的值;
        mApiUrlDetailsBean.setUrlHistoryBean(mUrlInputLayoutApi.getUrlHistoryBean());
        mImageUrlDetailsBean.setUrlHistoryBean(mUrlInputLayoutImage.getUrlHistoryBean());
        mH5UrlDetailsBean.setUrlHistoryBean(mUrlInputLayoutH5.getUrlHistoryBean());
        putUrlConfigInputMap(map, (UrlDetailsBean) mApiUrlDetailsBean.clone());
        putUrlConfigInputMap(map, (UrlDetailsBean) mImageUrlDetailsBean.clone());
        putUrlConfigInputMap(map, (UrlDetailsBean) mH5UrlDetailsBean.clone());
        return map;
    }

    public UrlDetailsBean getApiUrlDetailsBean() {
        // 输入的地址内容可能会发生改变,需要重新获取内部的值;
        mApiUrlDetailsBean.setUrlHistoryBean(mUrlInputLayoutApi.getUrlHistoryBean());
        return mApiUrlDetailsBean;
    }

    public UrlDetailsBean getImageUrlDetailsBean() {
        // 输入的地址内容可能会发生改变,需要重新获取内部的值;
        mImageUrlDetailsBean.setUrlHistoryBean(mUrlInputLayoutImage.getUrlHistoryBean());
        return mImageUrlDetailsBean;
    }

    public UrlDetailsBean getH5UrlDetailsBean() {
        mH5UrlDetailsBean.setUrlHistoryBean(mUrlInputLayoutH5.getUrlHistoryBean());
        return mH5UrlDetailsBean;
    }
    private void putUrlConfigInputMap(Map<String, UrlDetailsBean> map, UrlDetailsBean urlDetailsBean) {
        if (map == null || urlDetailsBean == null) {
            return;
        }
//        if (urlDetailsBean.isChecked()) {
        final String typeIdApi = urlDetailsBean.getTypeId();
        if (!TextUtils.isEmpty(typeIdApi)) {
            map.put(typeIdApi, (UrlDetailsBean) urlDetailsBean.clone());
        }
//        }
    }

    /**
     * 重置所有数据;
     */
    public void resetDataAndViews() {
        Map<String, UrlDetailsBean> urlConfigInputMap = EnvConfigUtils.getUrlConfigInputMapFromJson();
        mApiUrlDetailsBean = urlConfigInputMap.get(EnvConfigConsts.UrlInputType.TYPE_ID_API);
        mImageUrlDetailsBean = urlConfigInputMap.get(EnvConfigConsts.UrlInputType.TYPE_ID_IMAGE);
        mH5UrlDetailsBean = urlConfigInputMap.get(EnvConfigConsts.UrlInputType.TYPE_ID_H5);
        mUrlInputLayoutApi.refreshViewsByUrlDetails(mApiUrlDetailsBean);
        mUrlInputLayoutImage.refreshViewsByUrlDetails(mImageUrlDetailsBean);
        mUrlInputLayoutH5.refreshViewsByUrlDetails(mH5UrlDetailsBean);
    }

    /**
     * 如果最近一次的选中数据有值,则修改默认选中项,如果没有则按默认项处理;
     */
    private void initData() {
        Map<String, UrlDetailsBean> urlConfigInputMap = EnvConfigUtils.getUrlConfigInputMapFromJson();
        Map<String, UrlDetailsBean> urlConfigInputCacheMap = EnvConfigUtils.getUrlConfigDetailsOfInputFromCache(mContext);
        if (CollectionUtils.isEmptyMap(urlConfigInputMap)) {
            return;
        }
        //初始化json中的配置;
        mApiUrlDetailsBean = urlConfigInputMap.get(EnvConfigConsts.UrlInputType.TYPE_ID_API);
        mImageUrlDetailsBean = urlConfigInputMap.get(EnvConfigConsts.UrlInputType.TYPE_ID_IMAGE);
        mH5UrlDetailsBean = urlConfigInputMap.get(EnvConfigConsts.UrlInputType.TYPE_ID_H5);
        if (CollectionUtils.isEmptyMap(urlConfigInputCacheMap)) {
            // 如果没有缓存数据就不再处理;
            return;
        }
        // 根据本地缓存的数据,修改状态;
        UrlDetailsBean apiUrlDetailsCache = urlConfigInputCacheMap.get(EnvConfigConsts.UrlInputType.TYPE_ID_API);
        UrlDetailsBean imageUrlDetailsCache = urlConfigInputCacheMap.get(EnvConfigConsts.UrlInputType.TYPE_ID_IMAGE);
        UrlDetailsBean h5UrlDetailsCache = urlConfigInputCacheMap.get(EnvConfigConsts.UrlInputType.TYPE_ID_H5);
        //
        initDataWithCache(apiUrlDetailsCache, mApiUrlDetailsBean);
        //
        initDataWithCache(imageUrlDetailsCache, mImageUrlDetailsBean);
        //
        initDataWithCache(h5UrlDetailsCache, mH5UrlDetailsBean);
    }

    private void initDataWithCache(UrlDetailsBean urlDetailsBeanCache, UrlDetailsBean urlDetailsBean) {
        if (urlDetailsBeanCache != null) {
            urlDetailsBean.setChecked(urlDetailsBeanCache.isChecked());
            urlDetailsBean.setUrlHistoryBean(urlDetailsBeanCache.getUrlHistoryBean());
        }
    }

    @Override
    public void onShowUrlHistory(View view, UrlHistoryBean urlHistory) {
        List<UrlHistoryBean> urlList = EnvConfigUtils.initUrlHistoryList(mContext);
        int id = view.getId();
        new SingleSelectionDialog.Builder(mContext)
                .setTitle(R.string.title_select_url)
                .setUrlHistoryList(urlList)
                .setOnUrlSelecteComplete((dialog, childView, position, urlHistoryBean) -> {
                    if (id == R.id.url_detail_input_api_id) {
                        refreshItemDateAndViews(urlHistoryBean, mUrlInputLayoutApi, mApiUrlDetailsBean);
                    } else if (id == R.id.url_detail_input_image_id) {
                        refreshItemDateAndViews(urlHistoryBean, mUrlInputLayoutImage, mImageUrlDetailsBean);
                    } else if (id == R.id.url_detail_input_h5_id) {
                        refreshItemDateAndViews(urlHistoryBean, mUrlInputLayoutH5, mH5UrlDetailsBean);
                    }
                    dialog.dismiss();
                })
                .setOnCloseClickedListener(dialog -> dialog.dismiss())
                .create()
                .show();
    }

    private void refreshItemDateAndViews(UrlHistoryBean urlHistoryBean, UrlDetailsInputLayout urlInputLayout, UrlDetailsBean urlDetailsBean) {
        // 刷新UI;
        urlInputLayout.refreshViewsByUrlDetails(urlHistoryBean.getUrlStr());
        urlInputLayout.setUrlInput(urlHistoryBean.getUrlStr());
        urlInputLayout.setChecked(EnvConfigConsts.Bool.CHECKED);
        //数据状态修改
        urlDetailsBean.setChecked(EnvConfigConsts.Bool.CHECKED);
        urlDetailsBean.setUrlHistoryBean(urlHistoryBean);
    }

    @Override
    public void OnCheckBoxClick(View view, boolean isChecked, String input) {
        if (isChecked) {
            if (TextUtils.isEmpty(input)) {
                ToastUtils.showToast("请输入URL地址");
            }
        }
        int id = view.getId();
        if (id == R.id.url_detail_input_api_id) {
            refreshDataOnChecked(isChecked, mApiUrlDetailsBean, mUrlInputLayoutApi);
        } else if (id == R.id.url_detail_input_image_id) {
            refreshDataOnChecked(isChecked, mImageUrlDetailsBean, mUrlInputLayoutImage);
        } else if (id == R.id.url_detail_input_h5_id) {
            refreshDataOnChecked(isChecked, mH5UrlDetailsBean, mUrlInputLayoutImage);
        }
    }

    private void refreshDataOnChecked(boolean isChecked, UrlDetailsBean urlDetailsBean, UrlDetailsInputLayout urlInputLayout) {
        urlDetailsBean.setChecked(isChecked);
        urlDetailsBean.setUrlHistoryBean(urlInputLayout.getUrlHistoryBean());
    }

    /**
     * 将手动输入的URL地址缓存到本地,同时注意重复数据问题等;
     */
    public void saveUrlInputHistory() {
        if (!XXPermissions.isGrantedPermission(mContext, Permission.WRITE_EXTERNAL_STORAGE,
                Permission.READ_EXTERNAL_STORAGE)) {
            return;
        }
        // 查找当前页面数据中有没有手动输入地址的内容,有的话放入集合中,如果没有则不必要再执行保存数据的操作;
        List<UrlHistoryBean> newInputUrlHistoryList = initCurrentInputUrlHistoryList();
        if (CollectionUtils.isEmpty(newInputUrlHistoryList)) {
            return;
        }
        //移除目标集合中有重复URL地址的项;
        removeDuplicateUrls(newInputUrlHistoryList);
        // 初始化现有的所有历史数据,默认配置和缓存内容;
        List<UrlHistoryBean> urlHistoryList = EnvConfigUtils.initUrlHistoryList(mContext);
        // 新数据和缓存中的所有数据进行对比处理URL地址重复的情况;
        if (urlHistoryList != null) {
            //  移除新输入的地址,如果它与缓存中元素重复;
            int sizeInput = newInputUrlHistoryList.size();
            for (int i = 0; i < sizeInput; i++) {
                int sizeHistory = urlHistoryList.size();
                UrlHistoryBean urlInputBean = newInputUrlHistoryList.get(i);
                String urlInput = urlInputBean.getUrlStr();
//                // 如果输入的地址和历史中的记录重复时;
                for (int j = 0; j < sizeHistory; j++) {
                    UrlHistoryBean item = urlHistoryList.get(j);
                    String urlHistory = item.getUrlStr();
                    if (TextUtils.equals(urlInput, urlHistory)) {
                        // 移除原历史列表中的相同地址和记录;
                        urlHistoryList.remove(item);
                        j--;
                        sizeHistory--;
                    }
                }
            }
        } else {
            urlHistoryList = new ArrayList<>();
        }
        // 只保留手动输入的地址内容,其它的全部从列表中移除;
        if (urlHistoryList.size() > 0) {
            int size = urlHistoryList.size();
            for (int i = 0; i < size; i++) {
                UrlHistoryBean urlInputBean = urlHistoryList.get(i);
                String typeInput = urlInputBean.getType();
                // 如果输入的不是手动输入的地址,则从输入列表中移除,这样能保证默认的地址不在新增的历史记录里;
                if (!TextUtils.equals(typeInput, EnvConfigConsts.UrlHistoryType.INPUT)) {
                    urlHistoryList.remove(urlInputBean);
                    i--;
                    size--;
                }
            }
        }
        urlHistoryList.addAll(0, newInputUrlHistoryList);
        // 限制缓存数量,将超出缓存上限的数据移除;
        limitCacheCount(urlHistoryList);
        // 将手动输入的URL数据缓存;
        if (urlHistoryList.size() > 0) {
            final Gson gson = new Gson();
            String newJson = gson.toJson(urlHistoryList);
            SharedPrefsManager sharedPrefsManager = SharedPrefsManager.getInstance()
                    .init(mContext, EnvConfigConsts.EnvConfig.ENV_CONFIG_SP);
            sharedPrefsManager
                    .putString(EnvConfigConsts.EnvConfig.URL_INPUT_HISTORY_JSON, newJson)
                    .commit();
        }
    }

    /**
     * 限制缓存数量,将超出缓存上限的数据移除;
     *
     * @param urlHistoryListCache
     */
    private void limitCacheCount(List<UrlHistoryBean> urlHistoryListCache) {
        if (urlHistoryListCache.size() <= MAX_COUNT_OF_HISTORY_URL) {
            return;
        }
        for (; ; ) {
            if (urlHistoryListCache.size() > MAX_COUNT_OF_HISTORY_URL) {
                int index = urlHistoryListCache.size() - 1;
                if (index > 0) {
                    urlHistoryListCache.remove(index);
                }
            } else {
                break;
            }
        }
    }

    /**
     * 移除目标集合中有重复URL地址的项;
     *
     * @param urlHistoryBeanList
     */
    private void removeDuplicateUrls(List<UrlHistoryBean> urlHistoryBeanList) {
        int size = urlHistoryBeanList.size();
        // 移除内部URL字段相同的重复元素;
        if (size > 1) {
            for (int i = 0; i < size; i++) {
//                if (size <= 1) {
//                    break;
//                }
                UrlHistoryBean urlInputBean = urlHistoryBeanList.get(i);
                String url = urlInputBean.getUrlStr();
                for (int j = i + 1; j < size; j++) {
                    UrlHistoryBean next = urlHistoryBeanList.get(j);
                    String urlNext = next.getUrlStr();
                    // 移除内部URL字段相同的重复元素;
                    if (TextUtils.equals(url, urlNext)) {
                        urlHistoryBeanList.remove(next);
                        j--;
                        size--;
                    }
                }
            }
        }
    }

    /**
     * 查找当前页面数据中有手动输入地址的内容,有的话放入集合中;
     * 默认配置的地址项不放在集合中;
     *
     * @return
     */
    private List<UrlHistoryBean> initCurrentInputUrlHistoryList() {
        List<UrlHistoryBean> newUrlHistoryList = new ArrayList<>();
        //
        addCurrentInputUrlHistory(newUrlHistoryList, mApiUrlDetailsBean);
        //
        addCurrentInputUrlHistory(newUrlHistoryList, mImageUrlDetailsBean);
        //
        addCurrentInputUrlHistory(newUrlHistoryList, mH5UrlDetailsBean);
        return newUrlHistoryList;
    }

    private void addCurrentInputUrlHistory(List<UrlHistoryBean> newUrlHistoryList, UrlDetailsBean urlDetailsBean) {
        if (newUrlHistoryList == null || urlDetailsBean == null) {
            return;
        }
        if (!urlDetailsBean.isChecked()) {
            return;
        }
        final UrlHistoryBean urlHistoryBean = urlDetailsBean.getUrlHistoryBean();
        if (urlHistoryBean == null) {
            return;
        }
        final String urlStr = urlHistoryBean.getUrlStr();
        if (TextUtils.isEmpty(urlStr)) {
            return;
        }
        final String type = urlHistoryBean.getType();
        if (TextUtils.equals(type, EnvConfigConsts.UrlHistoryType.INPUT)) {
            newUrlHistoryList.add(urlHistoryBean);
        }
    }
}

开关配置

  开关配置项的各个开关相互独立,处理起来比较简单,基本是都是布尔值来控制相应的业务逻辑。这些开关参数设置后,可以在项目中具体的去应用,比如让应用启动后就直接跳转广告页,直接去测试其它功能,节省了开发测试的时间。

package com.windfallsheng.envconfig.fragment;

import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.windfallsheng.envconfig.EnvConfigConsts;
import com.windfallsheng.envconfig.EnvConfigUtils;
import com.windfallsheng.envconfig.R;
import com.windfallsheng.envconfig.adapter.SwitchConfigAdapter;
import com.windfallsheng.envconfig.bean.EnvConfigBean;
import com.windfallsheng.envconfig.bean.MockUserInfoWrapper;
import com.windfallsheng.envconfig.dialog.UserInfoEditDialog;
import com.windfallsheng.lib.ToastUtils;
import com.windfallsheng.lib.envconfig.util.CollectionUtils;
import com.windfallsheng.lib.envconfig.util.LogUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: lzsheng
 * @date: 2021/10/26 15:45
 * @description: 一些开关配置项的处理;
 */
public class SwitchConfigFragment extends Fragment {
    
    private final String TAG = "SwitchConfigFragment";
    private Context mContext;
    private View rootView;
    private RecyclerView mRecyclerView;
    private SwitchConfigAdapter mSwitchConfigAdapter;
    private List<EnvConfigBean> mSwitchConfigList;
    /**
     * 被选择的Mock用户;
     */
    private MockUserInfoWrapper mMockUserWrapperSelected;

    private SwitchConfigFragment() {
    }

    public static SwitchConfigFragment newInstance() {
        SwitchConfigFragment fragment = new SwitchConfigFragment();
        return fragment;
    }

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        mContext = context;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        rootView = inflater.inflate(R.layout.fragment_switch_config, container, false);
        mRecyclerView = rootView.findViewById(R.id.recyclerview_config);
        return rootView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        mMockUserWrapperSelected = EnvConfigUtils.getMockUserSelected(mContext);
        //
        LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(mContext);
        mRecyclerView.setLayoutManager(mLinearLayoutManager);

        // 如果本地有缓存的数据,则根据缓存数据的状态,列表中可能的选中项;
        mSwitchConfigList = EnvConfigUtils.initSwitchConfigListWithCache(mContext);
        LogUtils.dl(TAG, "method:onActivityCreated#mSwitchConfigList=" + mSwitchConfigList);
        mSwitchConfigAdapter = new SwitchConfigAdapter();
        mSwitchConfigAdapter.addAll(mSwitchConfigList);
        mRecyclerView.setAdapter(mSwitchConfigAdapter);
        // 行点击事件
        mSwitchConfigAdapter.setOnRecyclerViewItemClick((SwitchConfigAdapter.OnRecyclerViewItemClick<EnvConfigBean>)
                (childView, position, envConfigBean) -> {
                    handleRecyclerViewItemClick(position, envConfigBean);
                });
    }

    /**
     * 返回这个页面的配置信息,将选中的配置项返回;
     *
     * @return
     */
    public Map<String, EnvConfigBean> buildSwitchConfigMap() {
        if (CollectionUtils.isEmpty(mSwitchConfigList)) {
            return null;
        }
        Map<String, EnvConfigBean> switchConfigMap = new HashMap<>();
        for (int i = 0; i < mSwitchConfigList.size(); i++) {
            final EnvConfigBean envConfigBean = mSwitchConfigList.get(i);
            final String configId = envConfigBean.getConfigId();
            if (TextUtils.isEmpty(configId)) {
                ToastUtils.showToast("configId为空");
                continue;
            }
            // 将选中的项放在集合当中;
            if (envConfigBean.isChecked()) {
                // 将数据对象的copy返回给外界使用,不能和当前页面持有的对象产生关联影响;
                switchConfigMap.put(configId, (EnvConfigBean) envConfigBean.clone());
            }
//            if (TextUtils.equals(configId, CONFIG_ID_USE_MOCK_USER)){
//                if (envConfigBean.isChecked()){
//                    mUserInfoWrapperSelected = EnvConfigUtils.getMockUserSelected(mContext);
//                }
//            }
        }
        return switchConfigMap;
    }

    public MockUserInfoWrapper getMockUserWrapperSelected() {
        if (mMockUserWrapperSelected != null) {
            // 将数据对象的copy返回给外界使用,不能和当前页面持有的对象产生关联影响;
            mMockUserWrapperSelected.clone();
        }
        return mMockUserWrapperSelected;
    }

    public void resetListAndViews() {
        mSwitchConfigList = EnvConfigUtils.getSwitchConfigFromJson();
        mSwitchConfigAdapter.addNewAll(mSwitchConfigList);
        mSwitchConfigAdapter.notifyDataSetChanged();
        //
        mMockUserWrapperSelected = EnvConfigUtils.getMockUserSelected(mContext);
    }

    private void handleRecyclerViewItemClick(int position, EnvConfigBean envConfigBean) {
        // 单个选项为一组时的业务处理
        boolean isChecked = envConfigBean.isChecked();
        envConfigBean.setChecked(!isChecked);
        mSwitchConfigAdapter.notifyItemChanged(position);
        // 如果开启了使用mock用户的功能;
        if (envConfigBean.isChecked()) {
            final String configId = envConfigBean.getConfigId();
            if (TextUtils.equals(configId, EnvConfigConsts.ConfigId.USE_MOCK_USER)) {
                showUserInfoEditDialog();
            }
        }
    }

    private void showUserInfoEditDialog() {
        List<MockUserInfoWrapper> mockUserInfoWrapperList = EnvConfigUtils.initMockUserWrapperList(mContext);
        new UserInfoEditDialog.Builder(mContext)
                .setTitle(R.string.title_select_mock_user)
                .setMockUserInfoWrapperList(mockUserInfoWrapperList)
                .setOnSaveAllUsersCompleteListener((dialog, userInfoWrapper) -> {
                    mMockUserWrapperSelected = userInfoWrapper;
                    dialog.dismiss();
                })
                .setOnCloseClickedListener(dialog -> {
                    dialog.dismiss();
                })
                .create()
                .show();
    }
}

快捷测试入口

  根据自己业务的需要,可以在这里设置一些快捷的功能测试入口;

package com.windfallsheng.envconfig.fragment;

import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.windfallsheng.envconfig.EnvConfigUtils;
import com.windfallsheng.envconfig.R;
import com.windfallsheng.envconfig.bean.MockUserInfoWrapper;
import com.windfallsheng.envconfig.dialog.UserInfoEditDialog;
import com.windfallsheng.lib.ToastUtils;
import java.util.List;

/**
 * @Author: lzsheng
 * @date: 2021/10/26 17:03
 * @description: 一些功能的测试入口;
 */
public class TestAccessFragment extends Fragment {

    private final String TAG = "TestAccessFragment";
    private View rootView;
    private Context mContext;
    private TextView tvTest1;
    private TextView tvTest2;
    private TextView tvTest3;
    private TextView tvTest5;
    private TextView tvTest4;

    /**
     * 语音播报
     */
    private TextView tvTest;
    /**
     * 语音播报,输入金额;
     */
    private EditText etContent;
    private TextView tvMockLoginUser;

    public static TestAccessFragment newInstance() {
        TestAccessFragment fragment = new TestAccessFragment();
        return fragment;
    }

    @Override
    public void onAttach(Context context) {
        mContext = context;
        super.onAttach(context);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        rootView = inflater.inflate(R.layout.fragment_test_access, container, false);
        tvTest = rootView.findViewById(R.id.tv_test);
        etContent = rootView.findViewById(R.id.et_content);
        tvTest1 = rootView.findViewById(R.id.tv_test_1);
        tvTest5 = rootView.findViewById(R.id.tv_test_5);
        tvTest4 = rootView.findViewById(R.id.tv_test_4);
        tvMockLoginUser = rootView.findViewById(R.id.tv_mock_login_user);
        tvTest2 = rootView.findViewById(R.id.tv_test_2);
        tvTest3 = rootView.findViewById(R.id.tv_test_3);
        return rootView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        tvMockLoginUser.setOnClickListener(v -> {
            showUserInfoEditDialog();
        });

        tvTest.setOnClickListener(v -> {
            final String trim = etContent.getText().toString().trim();
            if (TextUtils.isEmpty(trim)) {
                ToastUtils.showToast("输入内容为空");
            } else {
                ToastUtils.showToast(trim);
            }
        });
        tvTest1.setOnClickListener(v -> {
            ToastUtils.showToast(tvTest1.getText().toString());
        });
        tvTest2.setOnClickListener(v -> {
            ToastUtils.showToast(tvTest2.getText().toString());
        });

        tvTest3.setOnClickListener(v -> {
            ToastUtils.showToast(tvTest3.getText().toString());
        });
        tvTest4.setOnClickListener(v -> {
            ToastUtils.showToast(tvTest4.getText().toString());
        });
        tvTest5.setOnClickListener(v -> {
            ToastUtils.showToast(tvTest5.getText().toString());
        });
    }

    private void showUserInfoEditDialog() {
        List<MockUserInfoWrapper> userInfoWrapperList = EnvConfigUtils.initMockUserWrapperList(mContext);
        new UserInfoEditDialog.
                Builder(mContext)
                .setTitle(R.string.title_select_mock_user)
                .setMockUserInfoWrapperList(userInfoWrapperList)
                .setOnSaveAllUsersCompleteListener((dialog, userInfoWrapper) -> dialog.dismiss())
                .setOnCloseClickedListener(dialog -> dialog.dismiss())
                .create()
                .show();
    }
}

环境配置主页面

  主页面展示了各个配置项的fragment,同时负担处理,完成配置、应用配置、恢复默认设置、及查看配置详情的交互业务。

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.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.flyco.tablayout.SlidingTabLayout;
import com.google.gson.Gson;
import com.windfallsheng.envconfig.EnvConfigConsts;
import com.windfallsheng.envconfig.EnvConfigHelper;
import com.windfallsheng.envconfig.R;
import com.windfallsheng.envconfig.bean.AllServersUrlConfigBean;
import com.windfallsheng.envconfig.bean.AppInfoBean;
import com.windfallsheng.envconfig.bean.EnvConfigBean;
import com.windfallsheng.envconfig.bean.EnvConfigWrapper;
import com.windfallsheng.envconfig.bean.MockUserInfoWrapper;
import com.windfallsheng.envconfig.bean.UrlDetailsBean;
import com.windfallsheng.envconfig.fragment.ServerUrlConfigListFragment;
import com.windfallsheng.envconfig.fragment.ServerUrlManualInputFragment;
import com.windfallsheng.envconfig.fragment.SwitchConfigFragment;
import com.windfallsheng.envconfig.fragment.TestAccessFragment;
import com.windfallsheng.lib.ToastUtils;
import com.windfallsheng.lib.envconfig.util.CollectionUtils;
import com.windfallsheng.lib.envconfig.util.DisplayHelper;
import com.windfallsheng.lib.envconfig.util.LogUtils;
import com.windfallsheng.lib.envconfig.util.SharedPrefsManager;
import com.windfallsheng.lib.xxpermissions.Permission;
import com.windfallsheng.lib.xxpermissions.XXPermissions;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

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

    private final String TAG = "EnvConfigDialogFragment";
    private View rootView;
    /**
     * 分类下的各页面;
     */
    private ServerUrlConfigListFragment mAllServerUrlDetailsConfigFragment;
    private ServerUrlManualInputFragment mServerUrlManualInputFragment;
    private SwitchConfigFragment mSwitchConfigFragment;
    private TestAccessFragment mTestAccessFragment;
    private TextView tvClose;
    private TextView tvCheckDetails;
    private TextView tvApply;
    private TextView tvFinish;
    private TextView tvResetAllConfig;
    /**
     * 选择完成后的回调;
     */
    private EnvConfigCheckedCompleteListener mEnvConfigCheckedCompleteListener;
    /**
     *
     */
    private SlidingTabLayout mTabLayout;
    private ViewPager mViewPager;
    private List<Fragment> mFragments = new ArrayList<>();
    private String[] mTabTitles = new String[]{"URL配置", "手输URL", "开关配置", "测试入口"};

    public static EnvConfigDialogFragment newInstance() {
        EnvConfigDialogFragment envConfigDialogFragment = new EnvConfigDialogFragment();
        return envConfigDialogFragment;
    }

    @Override
    public void onAttach(Context context) {
        mContext = context;
        super.onAttach(context);
        if (context instanceof EnvConfigCheckedCompleteListener) {
            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);
        tvClose = rootView.findViewById(R.id.tv_close);
        tvCheckDetails = rootView.findViewById(R.id.tv_check_details);
        mTabLayout = rootView.findViewById(R.id.tab_layout);
        mViewPager = rootView.findViewById(R.id.viewpager);
        tvApply = rootView.findViewById(R.id.tv_apply);
        tvFinish = rootView.findViewById(R.id.tv_finish);
        tvResetAllConfig = rootView.findViewById(R.id.tv_reset_config);
        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);
        layoutParams.height = (int) (DisplayHelper.getScreenHeight(mContext) * 1);
        layoutParams.width = (int) (DisplayHelper.getScreenWidth(mContext) * 0.95);
        rootView.setLayoutParams(layoutParams);
        FrameLayout parent = (FrameLayout) rootView.getParent();
        parent.setBackgroundResource(R.color.transparent);

        // viewpager及页面数据相关;
        mFragments = new ArrayList<>();
        mAllServerUrlDetailsConfigFragment = ServerUrlConfigListFragment.newInstance();
        mServerUrlManualInputFragment = ServerUrlManualInputFragment.newInstance();
        mSwitchConfigFragment = SwitchConfigFragment.newInstance();
        mTestAccessFragment = TestAccessFragment.newInstance();
        mFragments.add(mAllServerUrlDetailsConfigFragment);
        mFragments.add(mServerUrlManualInputFragment);
        mFragments.add(mSwitchConfigFragment);
        mFragments.add(mTestAccessFragment);

        mViewPager.setOffscreenPageLimit(mFragments.size());
        //设置viewpager的adapter
        mViewPager.setAdapter(new FragmentAdapter(getChildFragmentManager()));
        //TabLayout与ViewPager的绑定
        mTabLayout.setViewPager(mViewPager);

        tvClose.setOnClickListener(v -> dismiss());
        // 重置配置项事件;
        tvResetAllConfig.setOnClickListener(v -> handleResetAllConfig());

        tvCheckDetails.setOnClickListener(v -> showEnvConfigDetailsDialog());
        // 应用配置事件;
        tvApply.setOnClickListener(v -> {
            handlerApplyClicked();
            ToastUtils.showToast("应用成功");
        });
        tvFinish.setOnClickListener(v -> handlerFinishClicked());
    }

    private void showEnvConfigDetailsDialog() {
        final EnvConfigWrapper envConfigWrapper = buildEnvConfigWrapperFromFragments();
        new ShowEnvConfigDetailsDialog.Builder(mContext)
                .setTitle(R.string.title_configuration_details)
                .setEnvConfigWrapper(envConfigWrapper)
                .create()
                .show();
    }

    /**
     * 重置所有配置项及UI效果;
     */
    private void handleResetAllConfig() {
        // 移除缓存数据;
        if (XXPermissions.isGrantedPermission(mContext, Permission.WRITE_EXTERNAL_STORAGE,
                Permission.READ_EXTERNAL_STORAGE)) {
            final SharedPrefsManager sharedPrefsManager = SharedPrefsManager.getInstance()
                    .init(mContext, EnvConfigConsts.EnvConfig.ENV_CONFIG_SP);
            sharedPrefsManager.removeKey(EnvConfigConsts.EnvConfig.ALL_SERVER_URL_DETAILS);
            sharedPrefsManager.removeKey(EnvConfigConsts.EnvConfig.SERVER_URL_DETAILS_INPUT);
            sharedPrefsManager.removeKey(EnvConfigConsts.EnvConfig.SWITCH_CONFIG_MAP_JSON);
            sharedPrefsManager.removeKey(EnvConfigConsts.EnvConfig.MOCK_USER_CONFIG_JSON);
        }
        // UI及页面数据重置;
        mAllServerUrlDetailsConfigFragment.resetListAndViews();
        mServerUrlManualInputFragment.resetDataAndViews();
        mSwitchConfigFragment.resetListAndViews();
        // 重置页面及数据后,需要应用当前的设置;
        handlerApplyClicked();
    }

    /**
     * 处理点击完成的业务;
     */
    private void handlerApplyClicked() {
        // 封装数据;
        final EnvConfigWrapper envConfigWrapper = buildEnvConfigWrapperFromFragments();
        if (envConfigWrapper.isEmpty()) {
            ToastUtils.showToast("配置的结果对象内容为空");
            return;
        }
        // 刷新环境配置;
        EnvConfigHelper.getInstance()
                .updateEnvServerUrlBySelectedMode(envConfigWrapper);
        // 保存配置信息到本地;
        if (XXPermissions.isGrantedPermission(mContext, Permission.WRITE_EXTERNAL_STORAGE,
                Permission.READ_EXTERNAL_STORAGE)) {
            // 缓存手动输入的URL历史数据;
            mServerUrlManualInputFragment.saveUrlInputHistory();
            // 缓存其它配置数据;
            Gson gson = new Gson();
            //
            final AllServersUrlConfigBean allServerUrlDetails = envConfigWrapper.getAllServerUrlDetails();
            // 暂时使用 SharedPreferences 作为本地缓存,根据实际需要选择处理方式;
            final SharedPrefsManager sharedPrefsManager = SharedPrefsManager.getInstance();
            sharedPrefsManager.init(mContext, EnvConfigConsts.EnvConfig.ENV_CONFIG_SP);
            if (allServerUrlDetails != null) {
                String allServerUrlJson = gson.toJson(allServerUrlDetails);
                sharedPrefsManager
                        .putString(EnvConfigConsts.EnvConfig.ALL_SERVER_URL_DETAILS, allServerUrlJson)
                        .commit();
            } else {
                sharedPrefsManager.removeKey(EnvConfigConsts.EnvConfig.ALL_SERVER_URL_DETAILS);
            }
            // 手动输入的URL;
            final Map<String, UrlDetailsBean> serverUrlDetailsSelectedMap = envConfigWrapper.getUrlConfigDetailsInputSelectedMap();
            if (CollectionUtils.isNotEmptyMap(serverUrlDetailsSelectedMap)) {
                String serverUrlInputJson = gson.toJson(serverUrlDetailsSelectedMap);
                sharedPrefsManager
                        .putString(EnvConfigConsts.EnvConfig.SERVER_URL_DETAILS_INPUT, serverUrlInputJson)
                        .commit();
            } else {
                sharedPrefsManager.removeKey(EnvConfigConsts.EnvConfig.SERVER_URL_DETAILS_INPUT);
            }
            // 开关配置项;
            final Map<String, EnvConfigBean> switchConfigMap = envConfigWrapper.getSwitchConfigMap();
            if (CollectionUtils.isNotEmptyMap(switchConfigMap)) {
                String switchConfigJson = gson.toJson(switchConfigMap);
                sharedPrefsManager
                        .putString(EnvConfigConsts.EnvConfig.SWITCH_CONFIG_MAP_JSON, switchConfigJson)
                        .commit();
            } else {
                sharedPrefsManager.removeKey(EnvConfigConsts.EnvConfig.SWITCH_CONFIG_MAP_JSON);
            }
            // Mock用户;
            final MockUserInfoWrapper mockUserInfoWrapper = envConfigWrapper.getMockUserInfoWrapper();
            if (mockUserInfoWrapper != null) {
                String mockUserInfoWrapperJson = gson.toJson(mockUserInfoWrapper);
                sharedPrefsManager
                        .putString(EnvConfigConsts.EnvConfig.MOCK_USER_CONFIG_JSON, mockUserInfoWrapperJson)
                        .commit();
            } else {
                sharedPrefsManager.removeKey(EnvConfigConsts.EnvConfig.MOCK_USER_CONFIG_JSON);
            }
        }
        // 修改环境配置的方法可以根据需求放在 EnvConfigDialogFragment 的此处调用;
        // 也可以在页面的 mEnvConfigCheckedCompleteListener 回调时调用;
//        EnvConfigHelper.getInstance()
//                .updateEnvServerUrl(envConfigWrapper.getAllServerUrlDetailsOfCheckedEnv().getModeOfEnv(), envConfigWrapper);
        // 接口回调;
        if (mEnvConfigCheckedCompleteListener == null) {
            LogUtils.el(TAG, "method:handlerFinishClicked#mConfigCompleteListener=" + mEnvConfigCheckedCompleteListener);
        } else {
            mEnvConfigCheckedCompleteListener.onEnvConfigCheckedComplete(envConfigWrapper);
        }
    }

    /**
     * 处理点击完成的业务;
     */
    private void handlerFinishClicked() {
        handlerApplyClicked();
        dismiss();
    }

    /**
     * 构建返回结果,将选中的数据内容封装在对象内;
     *
     * @return
     */
    private EnvConfigWrapper buildEnvConfigWrapperFromFragments() {
        // 获取各个页面的相关数据;
        final AllServersUrlConfigBean allServerUrlDetailsSelected = mAllServerUrlDetailsConfigFragment.getAllServerUrlDetailsSelected();
        final Map<String, EnvConfigBean> switchConfigMap = mSwitchConfigFragment.buildSwitchConfigMap();
        final MockUserInfoWrapper mockUserWrapperSelected = mSwitchConfigFragment.getMockUserWrapperSelected();
        final Map<String, UrlDetailsBean> serverUrlDetailsInputSelected = mServerUrlManualInputFragment.getServerUrlDetailsInput();
        // 封装数据;
        final EnvConfigWrapper envConfigWrapper = new EnvConfigWrapper();
        if (allServerUrlDetailsSelected != null) {
            envConfigWrapper.setAllServerUrlDetails(allServerUrlDetailsSelected);
        }
        if (CollectionUtils.isNotEmptyMap(serverUrlDetailsInputSelected)) {
            envConfigWrapper.setUrlConfigDetailsInputSelectedMap(serverUrlDetailsInputSelected);
        }
        if (CollectionUtils.isNotEmptyMap(switchConfigMap)) {
            envConfigWrapper.setSwitchConfigMap(switchConfigMap);
        }
        if (mockUserWrapperSelected != null) {
            envConfigWrapper.setMockUserInfoWrapper(mockUserWrapperSelected);
        }
        AppInfoBean appInfoBean = EnvConfigHelper.getInstance().getAppInfoBean();
        if (appInfoBean != null) {
            envConfigWrapper.setModeOfBuild(appInfoBean.getModeValueOfBuild());
        } else {
            LogUtils.el(TAG, "method:buildEnvConfigWrapperFromFragments#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;
    }

    public class FragmentAdapter extends FragmentPagerAdapter {

        public FragmentAdapter(FragmentManager fm) {
            super(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
        }

        @Override
        public int getCount() {
            return mTabTitles.length;
        }

        @NonNull
        @Override
        public Fragment getItem(int position) {
            return mFragments.get(position);
        }

        @Nullable
        @Override
        public CharSequence getPageTitle(int position) {
            return mTabTitles[position];
        }
    }
}

其它

  其它的一些功能,包括展示配置详情、手动输入的URL的自定义UI处理、手动输入的URL的历史记录功能,及使用mock用户的功能,本属于额外的功能,不再做说明。

总结

  绝大部分的配置参数在修改和扩展时,只需要修改json串的内容就可以,即时是需要添加一些开关配置项,会需要改动一部分的代码,但相对来说,改动范围是可控的,也是比较方便的,基础的逻辑框架不需要更改。目前的参数配置基本是满足大部分使用场景的,实际应用还要立足于自身项目,做一些调整。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

windfallsheng

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

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

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

打赏作者

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

抵扣说明:

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

余额充值