Retrofit之动态更换BaseUrl以及根据Retrofit

2022-01-10  本文已影响0人  小仙女喂得猪呀

我懂,先上关键代码:
第一步:自定义注解,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

希望能够帮助到看到这里的你,有不当之处希望能多多指点哟

上一篇 下一篇

猜你喜欢

热点阅读