Retrofit之动态更换BaseUrl以及根据Retrofit
我懂,先上关键代码:
第一步:自定义注解,urlKey表示一个key,用来获取对应的baseUrl,需要自定本地提前存好对应的urlKey和baseurl对应起来
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
/**
* 自定义Retrofit域名注解
*/
public @interface UrlToDomainName {
String urlKey();
}
第二步:给自己IService中的请求增加对应的UrlToDomainName注解
public interface IService{
@UrlToDomainName(url=DomainUrlConstantUtil.TYPE_CDN_HOME_URL)
@GET
Observable<ResponseResult<PageBean>> getMenus(@Url String url);
}
第三步:自定义拦截器DomainInterceptor,解析替换
/**
* @Author : jiyajie
* @Time : On 2022/1/5 17:45
* @Description : DomainInterceptor
* 自定义域名拦截器 用于动态更换baseUrl
*/
public class DomainInterceptor implements Interceptor {
private DomainParserInterceptor mDomainParser;
public DomainInterceptor(DomainParserInterceptor domainParser) {
mDomainParser = domainParser;
}
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
return chain.proceed(processRequest(chain.request()));
}
/**
* 处理请求,切换 BaseUrl
* @param request
* @return
*/
private Request processRequest(Request request){
//如果支持动态配置 BaseUrl
Invocation invocation = request.tag(Invocation.class);
if(invocation != null){
/*
在RequestFactory的create方法下 返回值为当前请求的Request,通过源码可见每一个Request都会关联一个Invocation,
Invocation中构造方法中有两个参数一个为method另一个为argumentList请求参数,因为我们定义的注解会在运行时存在,所以
可以通过method获取对应的注解对象
*/
UrlToDomainName domainName = invocation.method().getAnnotation(UrlToDomainName.class);
if(domainName != null){
String value = domainName.url();
String domain = GrayAndCdnUrlTools.getGrayOrCdnDomainName(value);
HttpUrl domainUrl = HttpUrl.parse(domain);
HttpUrl url = request.url();
Logger.i("DomainInterceptor inner domainName="+value+" url="+url);
if (domainUrl != null) {
HttpUrl httpUrl = mDomainParser.parserDomain(domainUrl,request.url());
Logger.i("DomainInterceptor inner httpUrl="+httpUrl);
//如果不为空,则切换 BaseUrl
if(httpUrl != null){
return request.newBuilder()
.url(httpUrl)
.build();
}
}
}
}
HttpUrl baseUrl = HttpUrl.parse(DomainUrlConstantUtil.TYPE_CDN_HOME_DOMAIN);
if(baseUrl != null){
HttpUrl httpUrl = mDomainParser.parserDomain(baseUrl,request.url());
Logger.i("DomainInterceptor outtor httpUrl="+httpUrl);
//如果不为空,则切换 BaseUrl
if(httpUrl != null){
return request.newBuilder()
.url(httpUrl)
.build();
}
}
return request;
}
}
工具类DomainParserInterceptor,ParserDomainInterceptor
public interface ParserDomainInterceptor {
HttpUrl parserDomain(@NonNull HttpUrl domainUrl, @NonNull HttpUrl httpUrl);
}
public class DomainParserInterceptor implements ParserDomainInterceptor {
private LruCache<String,String> mCacheUrlMap;
public DomainParserInterceptor(int cacheSize) {
mCacheUrlMap = new LruCache<>(cacheSize);
}
@Override
public HttpUrl parserDomain(@NonNull HttpUrl domainUrl, @NonNull HttpUrl httpUrl) {
HttpUrl.Builder builder = httpUrl.newBuilder();
//优先从缓存里面取
String url = mCacheUrlMap.get(getUrlKey(domainUrl,httpUrl));
if(TextUtils.isEmpty(url)){
for (int i = 0; i < httpUrl.pathSize(); i++) {
builder.removePathSegment(0);
}
List<String> strings = domainUrl.encodedPathSegments();
List<String> strings1 = httpUrl.encodedPathSegments();
Logger.i("DomainParserInterceptor: domainUrl.encodedPathSegments"+strings);
Logger.i("DomainParserInterceptor: httpUrl.encodedPathSegments()"+strings1);
List<String> pathSegments = new ArrayList<>();
// pathSegments.addAll(domainUrl.encodedPathSegments());
pathSegments.addAll(httpUrl.encodedPathSegments());
for (String pathSegment : pathSegments) {
builder.addEncodedPathSegment(pathSegment);
}
}else{
builder.encodedPath(url);
}
/*
scheme: 协议名称 http/https
port: uriPort
host: uriHost
在Okhttp-->Address.kt中提供了url的构建方式,参考写法如下
*/
Logger.i("DomainParserInterceptor: domainUrl.host:"+domainUrl.host()+" domainUrl.port():"+domainUrl.port());
Logger.i("DomainParserInterceptor: httpUrl.host:"+httpUrl.host()+" httpUrl.port():"+httpUrl.port());
HttpUrl resultUrl = builder.scheme(domainUrl.scheme())
.host(domainUrl.host())
.port(domainUrl.port())
.build();
if(TextUtils.isEmpty(url)){
//缓存 Url
mCacheUrlMap.put(getUrlKey(domainUrl,httpUrl),resultUrl.encodedPath());
}
return resultUrl;
}
/**
* 获取用于缓存 Url 时的 key,
* @return 返回key
*/
private String getUrlKey(@NonNull HttpUrl domainUrl, @NonNull HttpUrl currentUrl){
return String.format("%s_%s",domainUrl.encodedPath(),currentUrl.encodedPath());
}
}
关键技术点:
1.拦截器
2.java 注解相关知识点
3.为什么使用Invocation去获取注解(顺带分析整个Retrofit的执行流程)
本文均以图文结合的形式去分析
1.拦截器源码执行流程:
Interceptor(Chain) --> RealInterceptorChain
参考模拟实现解析形式
Intercept 执行逻辑
Chain 抽象方法
用HttpLoggingInterceptor进行分析:
image.png
2.java注解相关知识点
3458176-c5642804973c8b6e.jpeg
3.为什么使用invocation
Retrofit.create()-->validateServiceInterface(service)-->loadServiceMethod(method)
-->ServiceMethod.parseAnnotations(retrofit,method)
-->HttpServiceMethod.parseAnnotations(retrofit,method,requesFactory)第三个参数RequestFactory很关键是整个retrofit请求的核心类,用于解析参数,解析注解,
解析方法,解析header等 ,具体原因在主线流程结束的前一个框内, 通过源码追踪分析具体执行见流程图:
retrofit执行流程如图.png
希望能够帮助到看到这里的你,有不当之处希望能多多指点哟