以下是我這個系列的相關文章,有興趣可以參考一下,可以給個喜歡或者關注我的文章。
相信很多人都會用過頂頂大名的Retrofit2框架,本篇就介紹元件化網路請求問題。
先說一下重點原理吧
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// 呼叫介面方法
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
//可忽略
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
//快取服務介面
ServiceMethod<Object, Object> serviceMethod =
(ServiceMethod<Object, Object>) loadServiceMethod(method);
//觸發回撥
OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
}
});
}
複製程式碼
其使用了動態代理的方式來代理介面呼叫,通過serviceMethod來快取介面對應的資訊,然後通過okHttpCall來啟動請求。
當工程有多個業務存在,你有一些業務可能訪問的地址頭會有多個,即baseUrl的切換問題。
解決方案1
@Get , @Post 這些標註到每個介面方法上的註解不僅可以傳相對路徑,還可以傳全路徑,全路徑可以簡單解決,但是這樣只能固定死地址,如果伺服器當機,想換個後備伺服器你要怎麼辦?你要麼只能多備一份後備伺服器地址,如果你非常不走運,都被黑客攻爆了,我只能同情你了。
解決方案2
官方出了@Url的註解用於改變路徑地址的方案,這個還好是傳進去的,是可以解決方案1中的問題,如果業務模組多了,每次都要單獨傳入地址,有點麻煩。
public interface UserService {
@GET
public Call<ResponseBody> profilePicture(@Url String url);
}
Retrofit retrofit = Retrofit.Builder()
.baseUrl("https://your.api.url/");
.build();
UserService service = retrofit.create(UserService.class);
service.profilePicture("https://s3.amazon.com/profile-picture/path");
實際只會請求@Url中的全地址
複製程式碼
解決方案3
在Base做一個公用的,特別業務自身一個Retrofit例項然後建立ApiService,這樣就能夠間隔不同的業務,其Base底層提供一個複用OkHttpClient。如果模組中有其他特殊地質,使用第二種方案解決。
缺點是Retrofit例項變多。
解決方案4
研究過OkHttp的同學,應該知道OkHttp的攔截器機制,自定義一個攔截器,不清楚Okttp攔截器機制的,可以看這篇文章okhttp3 攔截器原始碼分析。在其請求的時候攔截掉請求,再替換掉Url,這樣有點破壞上層的封裝,你們看到的RetrofitUrlManager就是使用這個方案來完成的,在請求方法加入@Header的註解來標註使用哪個訪問基類地址。
//宣告請求對應的Headers
public interface ApiService {
@Headers({"Domain-Name: douban"}) // Add the Domain-Name header
@GET("/v2/book/{id}")
Observable<ResponseBody> getBook(@Path("id") int id);
}
// 註冊基類baseUrl
RetrofitUrlManager.getInstance().putDomain("douban", "https://api.douban.com");
// 切換基類baseUrl
RetrofitUrlManager.getInstance().setGlobalDomain("your BaseUrl");
複製程式碼
private RetrofitUrlManager() {
if (!DEPENDENCY_OKHTTP) { //使用本管理器必須依賴 Okhttp
throw new IllegalStateException("Must be dependency Okhttp");
}
setUrlParser(new DefaultUrlParser());
//攔截器
this.mInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
if (!isRun()) // 可以在 App 執行時, 隨時通過 setRun(false) 來結束本管理器的執行
return chain.proceed(chain.request());
return chain.proceed(processRequest(chain.request()));
}
};
}
//掛接攔截器
public OkHttpClient.Builder with(OkHttpClient.Builder builder) {
return builder
.addInterceptor(mInterceptor);
}
//掛接到OkHttp的攔截器
this.mOkHttpClient = RetrofitUrlManager.getInstance().with(new OkHttpClient.Builder()) //RetrofitUrlManager 初始化
.readTimeout(5, TimeUnit.SECONDS)
.connectTimeout(5, TimeUnit.SECONDS)
.build();
複製程式碼
public Request processRequest(Request request) {
Request.Builder newBuilder = request.newBuilder();
String url = request.url().toString();
//如果 Url 地址中包含 IDENTIFICATION_IGNORE 識別符號, 管理器將不會對此 Url 進行任何切換 BaseUrl 的操作
if (url.contains(IDENTIFICATION_IGNORE)) {
return pruneIdentification(newBuilder, url);
}
//通過請求註解獲取
String domainName = obtainDomainNameFromHeaders(request);
HttpUrl httpUrl;
Object[] listeners = listenersToArray();
// 如果有 header,獲取 header 中 domainName 所對映的 url,若沒有,則檢查全域性的 BaseUrl,未找到則為null
if (!TextUtils.isEmpty(domainName)) {
notifyListener(request, domainName, listeners);
httpUrl = fetchDomain(domainName);
newBuilder.removeHeader(DOMAIN_NAME);
} else {
notifyListener(request, GLOBAL_DOMAIN_NAME, listeners);
httpUrl = getGlobalDomain();
}
//獲取新的baseUrl地址
if (null != httpUrl) {
HttpUrl newUrl = mUrlParser.parseUrl(httpUrl, request.url());
if (debug)
Log.d(RetrofitUrlManager.TAG, "The new url is { " + newUrl.toString() + " }, old url is { " + request.url().toString() + " }");
if (listeners != null) {
for (int i = 0; i < listeners.length; i++) {
((onUrlChangeListener) listeners[i]).onUrlChanged(newUrl, request.url()); // 通知監聽器此 Url 的 BaseUrl 已被切換
}
}
//返回鏈式呼叫
return newBuilder
.url(newUrl)
.build();
}
return newBuilder.build();
}
複製程式碼
缺點是當極端情況,模組間互動請求Http的時候,伴隨每次都要每個請求都需要先切換Url再請求,以保障url是正確的。
解決方案5
修改Retrofit的原始碼,新增一個HashMap的儲存多個訪問地址頭,封裝介面,需要的時候再取出配置。
缺點是無法跟隨Retrofit版本更新。當然外部去維護一個HashMap,然後可以試著使用Hook的方法來hook掉baseUrl引數(壞笑)
這裡並不存在最好的方案,相對於元件化使用,第三種方案相對解耦程度會高一點,第二種方案會靈活性上可以伺服器動態配置,第四五中方案Retrofit都是單例,記憶體消耗會低一點。
群1已經滿了,學習元件化可以進群2 763094035