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