事由
由于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="https://juejin.im/user/888061125471917/posts"
#测试环境
BASE_URL_DEBUG="https://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 = "https://juejin.im/user/888061125471917/posts"
//正式
const val BASE_URL_API_DEBUG = "https://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 新小梦
* 掘金:https://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到一个技能。点赞,然后实战吧。