2025 源码应用: Retrofit之动态更换BaseUrl

这边文章写在三年前,现在回头看看还有有些用处,再次老文新享 源码解析可参考我的另一篇文章juejin.cn/post/752032... 我懂,先上关键代码: 第一步:自定义注解,urlKey表示一个key,用来获取对应的baseUrl,需要自定本地提前存好对应的urlKey和baseurl对应起来

less 复制代码
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
/**
 * 自定义Retrofit域名注解
 */
public @interface UrlToDomainName {
    String urlKey();
}

第二步:给自己IService中的请求增加对应的UrlToDomainName注解

less 复制代码
public interface IService{
    @UrlToDomainName(url=DomainUrlConstantUtil.TYPE_CDN_HOME_URL)
    @GET
    Observable<ResponseResult<PageBean>> getMenus(@Url String url);
}

第三步:自定义拦截器DomainInterceptor,解析替换

java 复制代码
/**
 * @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

less 复制代码
public interface ParserDomainInterceptor {
    HttpUrl parserDomain(@NonNull HttpUrl domainUrl, @NonNull HttpUrl httpUrl);
}
less 复制代码
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进行分析: 2.java注解相关知识点

3.为什么使用invocation Retrofit.create()-->validateServiceInterface(service)-->loadServiceMethod(method) -->ServiceMethod.parseAnnotations(retrofit,method) -->HttpServiceMethod.parseAnnotations(retrofit,method,requesFactory)第三个参数RequestFactory很关键是整个retrofit请求的核心类,用于解析参数,解析注解, 解析方法,解析header等 ,具体原因在主线流程结束的前一个框内, 通过源码追踪分析具体执行见流程图:

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

目前作者在魔都求职中, 希望被各位大佬推荐谢谢

相关推荐
whysqwhw14 分钟前
OkHttp平台抽象机制分析
android
hsx6661 小时前
Android 内存泄漏避坑
android
whysqwhw1 小时前
OkHttp之okhttp-bom模块的分析
android
餐桌上的王子1 小时前
Android 构建可管理生命周期的应用(二)
android
幽你一默2 小时前
Android 版本差异速查表(开发者视角)
android
不萌2 小时前
android 项目中的屏幕适配方案
android
幽你一默2 小时前
Android开发三分钟读懂mvc,mvp,mvvm,mvi
android
AmazingMQ4 小时前
Android 13----在framworks层映射一个物理按键
android
小李飞飞砖5 小时前
Android 插件化实现原理详解
android