Retrofit更换baseUrl以及源码分析

  • 一般一个应用都只创建一个Retrofit实例,但是当碰到需要的网络请求的baseUrl不止一个的时候,就要考虑如何给retrofit对象根据不同的请求接口更换不同的baseUrl了。

  • 实现方法

    //创建retrofit对象
    private void initRetrofit(final Context context) {
         // 设置超时
         builder.retryOnConnectionFailure(true).connectTimeout(60,TimeUnit.SECONDS);
         builder.readTimeout(TIMEOUT, TimeUnit.SECONDS);
         builder.writeTimeout(TIMEOUT, TimeUnit.SECONDS);
         //设置替换baseUrl拦截器,更换baseUrl是通过这个拦截器实现的
         builder.addInterceptor(new ChangeBaseUrlInterceptor());
         OkHttpClient client = builder.build();
         mRetrofit = new Retrofit.Builder()
                 // 设置请求的域名,这个是初始的baseUrl
                 .baseUrl(BASE_URL)
                 // 设置解析转换工厂,用自己定义的
                 .addConverterFactory(ResponseConvert.create())
                 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                 .client(client)
                 .build();
     }
    
     /**
      * 创建API
      */
     public <T> T create(Class<T> clazz) {
         return mRetrofit.create(clazz);
     }
    

    下面看一下ApiService的接口是怎么实现的

     //获取扫码状态(二维码过期,扫码成功,扫码失败)
     @Headers({"domin:common"})
     @GET("/cgi-bin/mmwebwx-bin/login")
     Call<ResponseBody> getLoginStatus(@Query("loginicon") boolean loginicon,
                                    @Query("uuid") String uuid,
                                    @Query("tip") int tip,
                                    @Query("r") long r,
                                    @Query("_") long time);
    
     //上传好友信息到后台服务器
     @Headers({"domin:updatecontacts"})
     @POST("/api/wechat/status/change")
     Call<ResponseBody> updateContacts(@Body RequestBody body);
    
     //登录微信
     @Headers({"domin:login"})
     @GET("/cgi-bin/mmwebwx-bin/webwxnewloginpage")
     Call<ResponseBody> webLoginWeixin(@Query("ticket") String ticket,
                                       @Query("uuid") String uuid,
                                       @Query("lang") String lang,
                                       @Query("scan") String scan,
                                       @Query("fun") String fun,
                                       @Query("r")long r);
    
     //登录微信(因为微信的baseUrl有两个)
     @Headers({"domin:loginother"})
     @GET("/cgi-bin/mmwebwx-bin/webwxnewloginpage")
     Call<ResponseBody> webLoginWeixinT(@Query("ticket") String ticket,
                                       @Query("uuid") String uuid,
                                       @Query("lang") String lang,
                                       @Query("scan") String scan,
                                        @Query("fun") String fun,
                                        @Query("r")long r);
    
     //登录成功初始化微信
     @Headers({"domin:init"})
     @POST("/cgi-bin/mmwebwx-bin/webwxinit")
     Call<ResponseBody> webinitWeixin(@Query("r") long t,
                                      @Query("pass_ticket") String pass_ticket,
                                      @Body RequestBody body);
    

    这五个接口的baseUrl都是不一样的,怎么区分呢,通过给接口添加注解,设置其请求头属性domin的值
    下面看一下自定义拦截器是怎么根据请求头的domin属性更换为每个接口更换baseUrl的
    public class ChangeBaseUrlInterceptor implements Interceptor {

         @Override
         public Response intercept(Chain chain) throws IOException {
             //获取request
             Request request = chain.request();
             //从request中获取原有的HttpUrl实例oldHttpUrl
             HttpUrl oldHttpUrl = request.url();
             //获取request的创建者builder
             Request.Builder builder = request.newBuilder();
             //从request中获取headers,通过给定的键url_name
             List<String> headerValues = request.headers(Constant.DOMIN);
             //
             if (headerValues != null && headerValues.size() > 0) {
                 builder.removeHeader(Constant.DOMIN);
                 //匹配获得新的BaseUrl,通过domin值,判断这个是哪一个方法需要使用什么baseUrl
                 String headerValue = headerValues.get(0);
                 HttpUrl newBaseUrl;
                 if (URLConstant.LOGIN.equals(headerValue)) {
                     newBaseUrl = HttpUrl.parse(URLConstant.BASE_LOGIN_URL);
                 } else if (URLConstant.DOWN_LOAD.equals(headerValue)){
                     newBaseUrl = HttpUrl.parse(URLConstant.BASE_DOWNLOAD_URL);
                 }else if (URLConstant.INIT.equals(headerValue)){
                     newBaseUrl = HttpUrl.parse(URLConstant.BASE_INIT_URL);
                 }else if (URLConstant.SYNCHECK.equals(headerValue)){
                     newBaseUrl = HttpUrl.parse(URLConstant.BASE_SYNCCHECK_URL);
                 }else if (URLConstant.LOGIN_OTHER.equals(headerValue)){
                     newBaseUrl = HttpUrl.parse(URLConstant.BASE_LOGIN_OTHER_URL);
                 }else if (URLConstant.UPDATE_CONTACTS.equals(headerValue)){
                     newBaseUrl = HttpUrl.parse(URLConstant.BASE_UPDATE_CONTACTS_URL);
                 }else {
                     newBaseUrl = HttpUrl.parse(URLConstant.BASE_URL);
                 }
                 //重建新的HttpUrl,修改需要修改的url部分
                 HttpUrl newFullUrl = oldHttpUrl
                         .newBuilder()
                         .scheme(newBaseUrl.scheme())
                         .host(newBaseUrl.host())
                         .port(newBaseUrl.port())
                         .build();
                 return chain.proceed(builder.url(newFullUrl).build());
             }
     
             return chain.proceed(request);
         }
     }
    
     public class URLConstant {
         public static final String LOGIN = "login";
         public static final String DOWN_LOAD = "download";
         public static final String INIT = "init";
         public static final String SYNCHECK = "syncheck";
         public static final String LOGIN_OTHER = "loginother";
         public static final String UPDATE_CONTACTS = "updatecontacts";
    
         public static final String BASE_URL = "https://login.weixin.qq.com";
         public static final String BASE_LOGIN_URL = "https://wx.qq.com";
         public static final String BASE_DOWNLOAD_URL = "";
         public static  String BASE_INIT_URL = "https://wx2.qq.com";
         public static  String BASE_SYNCCHECK_URL = "https://webpush2.weixin.qq.com";
         public static  String BASE_LOGIN_OTHER_URL = "https://wx2.qq.com";
         public static String BASE_UPDATE_CONTACTS_URL = "http://api.xiaomor.com";
     }
    

    这是一个自定义的网络请求拦截器,在创建Retrofit对象的时候已经把拦截器设置给了Retrofit,那么网络请求就会分发到这个拦截器,这个拦截器接收到网络请求之后,就会根据需求对网络请求做一些处理,然后再通过请求链把这个网络请求再向下一个拦截器分发。这个过程使用了责任链的设计模式,就是当一个任务下达之后,从上一级交由下一级处理,上一级可以根据实际情况是拦截任务自己处理还是继续分发给下一级,这个任务最终会被某一级处理掉或者都无法处理被抛弃掉,这个就和Android的view事件分发机制的原理是一样的。

    Http请求采用这种责任链模式的好处是网络请求可以分层处理,每一层只实现某一特定功能即可,这样对整个过程进行了解耦,易于理解。Retrofit请求更换baseUrl就是这样实现的

  • 原理分析
    Chain是一个接口,它的实现类是RealInterceptorChain,它持有请求链上的所有的拦截器,每一个拦截器对请求Request处理之后,都会传给一个新建的RealInterceptorChain对象next,然后通过更改指示器值,获取下一个拦截器,再调用下一个拦截器的intercept(next)方法,下一个拦截器便会获取这个新建的RealInterceptorChain对象next,然后再从这个对象中取出请求Request,对这个请求进行处理,处理完成后,在调用RealInterceptorChain的proceed(Request var)方法,在这个方法里再新建一个RealInterceptorChain对象next,同时也是把刚才处理的Request对象传递给next,然后通过更改指示器值,再获取下一个拦截器interceptor,再调用下一个拦截器的intercept(next)进行处理,这样周而复始,直至调用到最后一个拦截器返回结果,或者是中间某个拦截器直接返回了结果,不用下一个拦截器处理。

    Chain

         public interface Interceptor {
             Response intercept(Interceptor.Chain var1) throws IOException;
         
             public interface Chain {
                 Request request();
         
                 Response proceed(Request var1) throws IOException;
         
                 @Nullable
                 Connection connection();
             }
         }
    

    Chain的实现类

     public final class RealInterceptorChain implements Chain {
         private final List<Interceptor> interceptors;
         private final int index;
         private final Request request;
         ...
     
         public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection, int index, Request request) {
             this.interceptors = interceptors;
             this.index = index;
             this.request = request;
             ...
         }
     
          ...
     
         public Response proceed(Request request) throws IOException {
             return this.proceed(request, this.streamAllocation, this.httpCodec, this.connection);
         }
     
         public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
             if (this.index >= this.interceptors.size()) {
                 throw new AssertionError();
             } else {
                     //创建一个新的chain,并且把request,interceptor,index + 1,传递到这个对象中
                     RealInterceptorChain next = new RealInterceptorChain(this.interceptors, streamAllocation, httpCodec, connection, this.index + 1, request);
                     //获取下一个拦截器
                     Interceptor interceptor = (Interceptor)this.interceptors.get(this.index);
                     //调用下一个拦截器的intercept方法
                     Response response = interceptor.intercept(next);
                     return response;
             }
         }
     }
    

    下面看看拦截器调用拦截器的intercept方法都做了什么,就以我们自定义的拦截器为例,系统其它的拦截器处理方法和这个相同

      public class ChangeBaseUrlInterceptor implements Interceptor {
         
             @Override
             public Response intercept(Chain chain) throws IOException {
                 //获取request
                 Request request = chain.request();
                 //从request中获取原有的HttpUrl实例oldHttpUrl
                 HttpUrl oldHttpUrl = request.url();
                 //获取request的创建者builder
                 Request.Builder builder = request.newBuilder();
                 //从request中获取headers,通过给定的键url_name
                 List<String> headerValues = request.headers(Constant.DOMIN);
                 //
                 if (headerValues != null && headerValues.size() > 0) {
                     builder.removeHeader(Constant.DOMIN);
                     //匹配获得新的BaseUrl,通过domin值,判断这个是哪一个方法需要使用什么baseUrl
                     String headerValue = headerValues.get(0);
                     HttpUrl newBaseUrl;
                     if (URLConstant.LOGIN.equals(headerValue)) {
                         newBaseUrl = HttpUrl.parse(URLConstant.BASE_LOGIN_URL);
                     } else if (URLConstant.DOWN_LOAD.equals(headerValue)){
                         newBaseUrl = HttpUrl.parse(URLConstant.BASE_DOWNLOAD_URL);
                     }else if (URLConstant.INIT.equals(headerValue)){
                         newBaseUrl = HttpUrl.parse(URLConstant.BASE_INIT_URL);
                     }else if (URLConstant.SYNCHECK.equals(headerValue)){
                         newBaseUrl = HttpUrl.parse(URLConstant.BASE_SYNCCHECK_URL);
                     }else if (URLConstant.LOGIN_OTHER.equals(headerValue)){
                         newBaseUrl = HttpUrl.parse(URLConstant.BASE_LOGIN_OTHER_URL);
                     }else if (URLConstant.UPDATE_CONTACTS.equals(headerValue)){
                         newBaseUrl = HttpUrl.parse(URLConstant.BASE_UPDATE_CONTACTS_URL);
                     }else {
                         newBaseUrl = HttpUrl.parse(URLConstant.BASE_URL);
                     }
                     //重建新的HttpUrl,修改需要修改的url部分
                     HttpUrl newFullUrl = oldHttpUrl
                             .newBuilder()
                             .scheme(newBaseUrl.scheme())
                             .host(newBaseUrl.host())
                             .port(newBaseUrl.port())
                             .build();
                     return chain.proceed(builder.url(newFullUrl).build());
                 }
         
                 return chain.proceed(request);
             }
         }
    

    通过代码,可以看出拦截器的intercept方法就是从传过来的chain对象中取出request对象,对它做一些加工,然后再调用chain的proceed方法把这个处理后的请求再传递下去,chain的proceed方法上面已经分析了,就是在创建一个新的Chain对象,把request封装进去,然后获取下一个拦截器,通过这个新创建的chain对象,把请求传递给下一个拦截器进行处理

    这些拦截器和这个request对象是从哪里来的呢?
    我们可以看一下RealCall类的getResponseWithInterceptorChain()方法和它的构造函数

     RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
         Factory eventListenerFactory = client.eventListenerFactory();
         this.client = client;
         this.originalRequest = originalRequest;
         this.forWebSocket = forWebSocket;
         this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
         this.eventListener = eventListenerFactory.create(this);
     }
     
     Response getResponseWithInterceptorChain() throws IOException {
         List<Interceptor> interceptors = new ArrayList();
         //这个是添加我们自定义的拦截器,我们的拦截器就是传递给了OkHttpClient对象
         interceptors.addAll(this.client.interceptors());
         interceptors.add(this.retryAndFollowUpInterceptor);
         interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
         interceptors.add(new CacheInterceptor(this.client.internalCache()));
         interceptors.add(new ConnectInterceptor(this.client));
         if (!this.forWebSocket) {
             interceptors.addAll(this.client.networkInterceptors());
         }
    
         interceptors.add(new CallServerInterceptor(this.forWebSocket));
         Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest);
         return chain.proceed(this.originalRequest);
     }
    

    可以看到这个request是通过realCall对象传进来的,并且拦截器是在getResponseWithInterceptorChain()这个方法里面添加的,并且网络请求也是从这个方法开始执行的
    那么我们看一下是哪里创建了这个RealCall对象,并且调用了它的getResponseWithInterceptorChain()这个方法。

     public void enqueue(final Callback<T> callback) {
         okhttp3.Call call;
         ...
         call = this.rawCall;
         if (call == null && failure == null) {
             try {
                 call = this.rawCall = this.createRawCall();
             } catch (Throwable var7) {
          }
             call.enqueue(new okhttp3.Callback() {
                 public void onResponse(okhttp3.Call call, Response rawResponse) throws IOException {
                     retrofit2.Response response;
                     try {
                         response = OkHttpCall.this.parseResponse(rawResponse);
                     } catch (Throwable var5) {
                         this.callFailure(var5);
                         return;
                     }
    
                     this.callSuccess(response);
                 }
                 ...
             );
         }
     }
    

    可以看到realCall的创建是由OkhttpCall的enqueue方法完成的,当realCall为null的时候,它调用了createRealCall创建了realCall对象,接着又调用了realCall的enqueue方法。
    我们先看一下createRealCall方法都做了什么

     private okhttp3.Call createRawCall() throws IOException {
         Request request = this.serviceMethod.toRequest(this.args);
         okhttp3.Call call = this.serviceMethod.callFactory.newCall(request);
         if (call == null) {
             throw new NullPointerException("Call.Factory returned null.");
         } else {
             return call;
         }
     }
     
     //工厂类的newCall方法
     public Call newCall(Request request) {
         return new RealCall(this, request, false);
     }
    

    可以看到这个方法通过serviceMethod对象创建了Request对象,然后又使用工厂类方法和request对象创建了realCall对象,这样request对象就传递给了realCall对象。那么这个serviceMethod是什么呢?
    serviceMethod是调用接口方法时,接口代理对象通过反射创建的,它封装了方法的一些注解信息,比如请求头,releativeUrl,还有一些方法中参数的名称,方法的名称等信息,然后serviceMethod通过toRequest方法和请求参数args,来拼装请求所使用的Request对象。

    再看一下realCall的enqueue方法做了什么操作?

     public void enqueue(Callback responseCallback) {
         synchronized(this) {
             if (this.executed) {
                 throw new IllegalStateException("Already Executed");
             }
    
             this.executed = true;
         }
    
         this.captureCallStackTrace();
         this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
     }
    

    可以看到它client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));执行了这段代码
    这个RealCall.AsyncCall是一个继承了Runnable的内部类内部类
    在进到Dispatcher的enqueue的方法

      synchronized void enqueue(AsyncCall call) {
         if (this.runningAsyncCalls.size() < this.maxRequests && this.runningCallsForHost(call) < this.maxRequestsPerHost) {
             this.runningAsyncCalls.add(call);
             this.executorService().execute(call);
         } else {
             this.readyAsyncCalls.add(call);
         }
     }
         
     public synchronized ExecutorService executorService() {
         if (this.executorService == null) {
             this.executorService = new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
         }
    
         return this.executorService;
     }
    

    可以看到它执行了 this.executorService().execute(call);这句,这个executorService()就是一个线程池对象,那么我们最终可以知道请求被封装成一个Runnable对象,被添加到线程池中执行了。这也是异步请求的过程。
    最后它们会执行Runnable的execute方法

     final class AsyncCall extends NamedRunnable {
         private final Callback responseCallback;
    
         AsyncCall(Callback responseCallback) {
             super("OkHttp %s", new Object[]{RealCall.this.redactedUrl()});
             this.responseCallback = responseCallback;
         }
          ...
         protected void execute() {
             boolean signalledCallback = false;
    
             try {
                 Response response = RealCall.this.getResponseWithInterceptorChain();
                 if (RealCall.this.retryAndFollowUpInterceptor.isCanceled()) {
                     signalledCallback = true;
                     this.responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
                 } else {
                     signalledCallback = true;
                     this.responseCallback.onResponse(RealCall.this, response);
                 }
             } catch (IOException var6) {
                
             } finally {
                 
             }
         }
     }
    

    可以看到Runnable的方法调用了getResponseWithInterceptorChain(),开始执行了网络请求
    我们上面说的,是OkhttpCall的enqueue方法创建了realCall对象,并且执行了realCall的enqueue方法执行了网络请求,那么在哪里执行了OkhttpCall的enqueue方法呢?

     serviceApi.getLoginStatus(true,mUuid,0,r,time).enqueue(new Callback<ResponseBody>() {
         @Override
         public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
             
         }
    
         @Override
         public void onFailure(Call<ResponseBody> call, Throwable t) {
    
         }
     });
    

    OkhttpCall的enqueue方法是上层调用的, serviceApi.getLoginStatus(true,mUuid,0,r,time)通过反射创建了一个okhttpCall对象。
    好了,关于Retrofit请求更换baseUrl的实现过程以及原理就说到这里,有问题请大家及时指正

    原文作者:元创造力
    原文地址: https://www.jianshu.com/p/2aa5e8dc487d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞