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等 ,具体原因在主线流程结束的前一个框内, 通过源码追踪分析具体执行见流程图:

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

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

相关推荐
雨白9 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk9 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING10 小时前
RN容器启动优化实践
android·react native
恋猫de小郭12 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
YF021116 小时前
AndroidStudio工具链配置
android studio
Kapaseker17 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴17 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab1 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack