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後所能重寫的方法:
可以看到,真的是已經很接近了,這裡已經可以拿到註解進行判斷了,可惜的是,方法要求的返回值是RequestBody。沒有辦法編輯Headers。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); } }
如果在專案中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中附帶,那麼就完全可以通過自定義註解來解決這個問題了。