Retrofit 反射动态修改BaseURL

语言: CN / TW / HK

事由

由于Retrofit官方并没有提供修改BaseUrl的接口,但我们却有这样的需求。例如我,在开发快速记账APP的时候,我有四个BaseUrl,在家里开发时用的是HomeBaseUrl,然后有事出门手机拔了就走,没有把BaseUrl切换到正式环境,出门在外消费了,想记个账发现记不了。

网上搜了一下,如何动态修改BaseUrl,大多数说的都是写拦截器或者多个Retrofit实例。我都感觉特别麻烦,所以我就放弃了。

转折

转折来了,最近有空在看Retrofit的源码,发现了我们传入的BaseUrl会被封装成HttpUrl,而且整个APP只有一个实例,所以只要我们修改这个实例相关方法或者属性,不就可以实现我们想要的功能。HttpUrl中的大多数属性是final或者val修饰的,所以只能通过反射来修改。

下面先介绍如何切换正式与测试BaseUrl,再介绍通过反射更换BaseUrl。

正式与测试BaseUrl

最基本的需求,就是我开发的时候用测试环境,打包用正式环境,这个是刚需。分两种情况,一种是整个工程,一种组件化开发。

整个工程

在项目的gradle.propertis文件声明一下我们的API地址BaseUrl:

#正式环境
BASE_URL_RELEASE="http://juejin.im/user/888061125471917/posts"
#测试环境
BASE_URL_DEBUG="http://github.com/Android-XXM/XXM-BLOG"
复制代码

在app组件的build.gradle文件添加下面内容,配置BuildConfig。

 buildTypes {
        release {
            ...
            buildConfigField("String", "BASE_API", project.BASE_URL_RELEASE)
        }

        debug{
            ...
            buildConfigField("String", "BASE_API", project.BASE_URL_DEBUG)
        }
    }
复制代码

这样话,在配置Retrofit的BaseUrl时,我们就可以通过BuildConfig.BASE_API来配置,会根据我们打的是正式包还是测试包自动切换API地址。

Retrofit.Builder().baseUrl(BuildConfig.BASE_API)//这里
    .addCallAdapterFactory(CoroutineCallAdapterFactory.invoke())
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .client(client)
    .build()
复制代码

组件化

这种的难点是,一般我们把网络请求放在基础库,例如base组件。通过判断BuildConfig是达不到切换效果,所以就需要通过代码来判断当前是否正式或测试环境了。

### Java代码
public static boolean isDebug(Context context) {
    if (debugFlag == -1) {
       isDebug = context.getApplicationInfo() != null &&
          (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
     }
    return isDebug;
}
复制代码

很棒,我们现在已经可以判断正式与测试 环境,我们也通过代码来设置BaseUrl。

### Kotlin代码
    //测试
    const val BASE_URL_API_RELEASE = "http://juejin.im/user/888061125471917/posts"
    //正式
    const val BASE_URL_API_DEBUG = "http://github.com/Android-XXM/XXM-BLOG"

    fun getBaseUrl(): String {
        return if (SystemUtils.isDebug(BaseApplication.instance!!.applicationContext)) {
            BASE_URL_API_DEBUG
        } else {
            BASE_URL_API_RELEASE
        }
    }
复制代码

很棒,到现在我们已经掌握了,如何通过配置切换正式与测试环境了,不需要手动修改了。

反射修改BaseUrl

通过观察HttpUrl类,有挺多属性可以供我们修改的,例如修改协议,修改主机,修改端口号等等,我们关注的就是修改url属性。

定义BaseUrlHelper类来实现我们的功能,为了保持HttpUrl整个APP只有一个实例,BaseUrlHelper类我们使用单例来实现。下面直接给出整个累的实现,有时需要可以直接拷贝。

/**
 * @author  新小梦
 * 掘金:http://juejin.im/user/888061125471917
 */
public class BaseUrlHelper {
    //协议:http or https
    private static final Field schemeField;
    //主机:www.baidu.com or 118.25.3.6
    private static final Field hostField;
    //端口:80
    private static final Field portField;
    //url 我们要修改的属性   
    private static final Field urlField;

    private final HttpUrl httpUrl;

    static {
        Field scheme = null;
        Field host = null;
        Field port = null;
        Field url = null;
        try {
            scheme = HttpUrl.class.getDeclaredField("scheme");
            port = HttpUrl.class.getDeclaredField("port");
            host = HttpUrl.class.getDeclaredField("host");
            url = HttpUrl.class.getDeclaredField("url");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        urlField = url;
        hostField = host;
        portField = port;
        schemeField = scheme;
    }

    public HttpUrl getHttpUrl() {
        return httpUrl;
    }

    public void setHostField(String host) {
        try {
            hostField.setAccessible(true);
            hostField.set(httpUrl, host);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public void setUrlField(String url) {
        try {
            urlField.setAccessible(true);
            urlField.set(httpUrl, url);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public void setSchemeField(String scheme) {
        try {
            schemeField.setAccessible(true);
            schemeField.set(httpUrl, scheme);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public void setPortField(int port) {
        try {
            portField.setAccessible(true);
            portField.set(httpUrl, port);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private BaseUrlHelper(HttpUrl httpUrl) {
        this.httpUrl = httpUrl;
    }

    public static BaseUrlHelper getInstance() {
        return Instance.getInstance();
    }

    private static class Instance {
        private static final BaseUrlHelper helper = new BaseUrlHelper(
                HttpUrl.get(getBaseApi()));

        public static BaseUrlHelper getInstance() {
            return helper;
        }
        //此处BaseApi的生成,可以参考上一小节的内容
        private static String getBaseApi() {
            String url = BUtilsKt.getSpValue(BaseApplication.Companion.getContext(), "base_api", BuildConfig.BASE_API, "local_data");
            return url;
        }
    }
}

复制代码

BaseUrlHelper类不仅提供修改BaseUrl的方法,也提供了修改其他属性的方法,更多属性的修改,自己可以看看HttpUrl的属性,添加即可。静态内容类Instance的getBaseApi函数我是根据自己的需求定义默认的BaseUrl的,大家可以参考前一小节的配置,实现自己的需求。

怎么使用BaseUrlHelper呢?

Retrofit.Builder().baseUrl(BaseUrlHelper.getInstance().httpUrl)//看这里
        .addCallAdapterFactory(CoroutineCallAdapterFactory.invoke())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .addConverterFactory(GsonConverterFactory.create())
        .client(client)
        .build()
复制代码

在想要修改地方,调用下面方法即可:

 BaseUrlHelper.getInstance().setUrlField(BASE_URL_API_RELEASE)
复制代码

真棒,在没有伤害一个脑细胞的情况下,又Get到一个技能。点赞,然后实战吧。