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

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

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

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui