事由
由於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到一個技能。點贊,然後實戰吧。