以下是我这个系列的相关文章,有兴趣可以参考一下,可以给个喜欢或者关注我的文章。
相信很多人都会用过顶顶大名的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学习组件化
组件化架构2群.png
网友评论
https://www.jianshu.com/p/df2a6717009d