1. 程式人生 > >Retrofit2 Cookie管理-按宣告決定是否帶Cookie請求介面

Retrofit2 Cookie管理-按宣告決定是否帶Cookie請求介面

整理自最近的專案經驗,Retrofit關於cookie快取與使用的文章已經很多了,無論是使用CookieJar還是在OkHttpClient build的時候通過Interceptor設定header,但是在專案中有針對介面決定是否在請求中帶cookie的需求,比如獲取應用首頁資料,推薦商品之類的賬戶無關介面不需要帶cookie,而賬戶相關介面需要帶cookie。

最簡單的實現方案是將介面分為需要cookie和不需要cookie兩組分別宣告,但是這樣就限定了介面的宣告方式,對於已有的專案改起來會比較麻煩,另一方面降低了宣告的自由度,限制了其它方式的宣告。是一種比較挫的做法。

對於這個問題,我首先想到的是能不能像Retrofit那樣通過註解為介面宣告屬性,比如寫個@UseCookie註解,被這個註解標識的介面在請求時將Cookie設定在Request的Header中。

要實現這個需求需要2點:

1. 解析自定義註解

2. 在解析自定義註解的期間能夠編輯請求的Headers

這就需要閱讀Retrofit的原始碼了,有關原始碼解析可以參考鴻洋大神的文章:

通過閱讀Retrofit的原始碼可以知道Retrofit將宣告的method轉化成具體的Call主要是在ServiceMethod中進行的,如果不想動Retrofit的原始碼,而是用其對外提供的介面實現Annotation解析的功能,那就只有在ConverterFactory和CallAdapterFactory中動手腳了。

來看一下繼承Converter.Factory後所能重寫的方法:

public class AConverterFactory extends Converter.Factory {
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        return super.responseBodyConverter(type, annotations, retrofit);
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        return super.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);
    }

    @Override
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        return super.stringConverter(type, annotations, retrofit);
    }
}
可以看到,真的是已經很接近了,這裡已經可以拿到註解進行判斷了,可惜的是,方法要求的返回值是RequestBody。沒有辦法編輯Headers。

如果在專案中Session不是通過Cookie傳輸而是放在body裡面,那麼自定義註解在這裡就可以用了。

如果說一定要用Cookie來發送Session,那麼就得另尋它路。回想在Retrofit2中為請求設定統一Headers的方法是在OkHttpClient中新增Interceptor,在Interceptor中配置Header;而通過Retrofit的Headers註解可以為具體method新增指定Header。那麼應該可以在@Headers註解中為某一介面新增一個Key,在Interceptor編輯Headers的時候,如果檢測到這個Key,則斷定這個請求需要在Header中新增Cookie。

先宣告一個常量Key

public class NetConstants {
    public static final String ADD_COOKIE = "Add-Cookie";
    
}
然後在具體介面中通過@Headers註解標識
public interface ClientApi {

    @Headers(NetConstants.ADD_COOKIE)
    @GET("adat/sk/{cityId}.html")
    Call<ResponseBody> getWeather(@Path("cityId") String cityId);
}
在配置Retrofit Builder的時候,在Interceptor中新增判斷:
 Interceptor configInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request.Builder builder = chain.request().newBuilder();

        // add costom headers......

       if (chain.request().headers().get(NetConstants.ADD_COOKIE) != null) { 
            builder.removeHeader(NetConstants.ADD_COOKIE);
            if (!TextUtils.isEmpty(SessionManager.getSession())) { //SessionManager只是一個簡易Session管理,就不貼程式碼了
                builder.header("Set-Cookie", SessionManager.getSession());
            }
        }

        Request request = builder.build();
        if (BuildConfig.DEBUG) {
            Log.d("TAG", "request url : " + request.url());
        }
        return chain.proceed(request);
    }
};
Interceptor responseInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());
        //存入Session
        if (response.header("Set-Cookie") != null) {
            SessionManager.setSession(response.header("Set-Cookie"));
        }
        //重新整理API呼叫時間
        SessionManager.setLastApiCallTime(System.currentTimeMillis());

        return response;
    }
};
okHttpClientBuilder.addInterceptor(configInterceptor);
okHttpClientBuilder.addInterceptor(responseInterceptor);
mRetrofitBuilder.client(okHttpClientBuilder.build());
這樣就可以實現根據具體的介面來決定是否在請求時附帶Cookie了,理論上來說最好的解決方案還是自定義註解,但是礙於框架限制,只能使用不是最優雅的辦法。

當然,如果與後端商定SessionID在body中附帶,那麼就完全可以通過自定義註解來解決這個問題了。