Retrofit原码学习——体会Retrofit设计的精妙

82 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情

前言

在日常开发中,网络请求必不可少,作为Android中当今最好的网络请求框架之一,Retrofit由大名鼎鼎的square公司出品,并且已经赢得了大部分移动端开发者的信任。本文将以设计模式为侧重点带大家阅读Retrofit的原码,体会Retrofit设计者的巧思。

Retrofit的使用

  1. 创建访问的接口

    1. public interface ApiService { 
          public static final String BASE_URL = "https://xxxx.com"; 
          @Streaming 
          @GET 
          Observable<ResponseBody> executeDownload(@Header("Range") String range , 
          @Url() String url); 
      } 
      
  1. 创建Retrofit实例

    1. okHttpClient = new OkHttpClient.Builder() 
              .connectTimeout(10, TimeUnit.SECONDS) 
              .build(); 
      retrofit = new Retrofit.Builder() 
              .addConverterFactory(GsonConverterFactory.create()) 
              .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 
              .baseUrl(baseUrl) 
              .client(okHttpClient) 
              .build();
      
    2.   通过动态代理获取访问接口的代理类对象
    3. apiService = retrofit.create(ApiService.class); 
      
  1. 发起网络请求

    1.     apiService.executeDownload("bytes=" + Long.toString(range) + 
      totalLength, url)
              .subscribe(new Observer<ResponseBody>() { 
                  @Override 
                  public void onSubscribe(Disposable d) { 
                  }
                  @Override 
                  public void onNext(ResponseBody responseBody) { 
                  }
                  @Override 
                  public void onError(Throwable e) {
                  }
                  @Override 
                  public void onComplete() { 
                  } 
              });
      

我们可以看到,使用retrofit只需我们开发者做两件事:1. 确定javabean 2. 创建retrofit实例

不用写网络适配,不用写一个类,只需要写接口

builder模式创建Retrofit实例

retrofit = new Retrofit.Builder() 
            .addConverterFactory(GsonConverterFactory.create()) //添加一个转换 
器,将gson数据转换为bean类 
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 添 
加一个适配器,与RxJava配合使用 
            .baseUrl(baseUrl) 
            .client(okHttpClient) 
            .build();

retrofit的创建采用了建造者设计模式(build)。当构造参数>5个且参数可选时,我们可以采用建造者模式进行设计。retrofit准备了一系列可能会用到的变量。

成功建立一个Retrofit对象的标准:配置好Retrofit类里的成员变量

baseUrl:网络请求的url地址

callFactory:网络请求工厂,生产网络请求器(Call)

callbackExecutor:回调方法执行器,请求结果回调处理器

adapterFactories:网络请求适配器工厂的集合,放置网络请求适配器工厂converterFactories:数据转换器工厂的集合,放置数据转换器工厂

下面我们来看看builder模式究竟做了什么,首先看看 builder( ) 这个方法

public static final class Builder { 
    public Builder() { 
        this(Platform.get()); 
    } 
    Builder(Platform platform) { 
        this.platform = platform; // 
        converterFactories.add(new BuiltInConverters()); //添加转换器,下面会专门讲解 
    } 
}

new Retrofit.Builder()先调用了Retrofit类里的Builder这个内部类的无参构造方法,构造方法

里再次调用了Builder内部类中的有参构造方法,参数为Platform.get();有参构造器根据传入的platform 参数对platform赋值,且添加了转换器。

下面我们来看看 Platform 是啥。

class Platform { 
    private static final Platform PLATFORM = findPlatform(); 
    static Platform get() { 
        return PLATFORM; 
    }
    private static Platform findPlatform() { 
        try { 
            Class.forName("android.os.Build"); 
            if (Build.VERSION.SDK_INT != 0) { 
                return new Android(); //安卓平台 
            }
        } catch (ClassNotFoundException ignored) { 
        }
        try { 
            Class.forName("java.util.Optional"); 
            return new Java8(); //java8平台 
        } catch (ClassNotFoundException ignored) { 
        }
        return new Platform(); 
    } 
}

可以看到,Platform是用来获取平台参数的,Retrofit会根据不同的平台设置不同的线程池。

接下来看一下 baseUrl() 方法。

public Class Buider { 
    ...... 
    public Builder baseUrl(String baseUrl) { 
        checkNotNull(baseUrl, "baseUrl == null"); 
        HttpUrl httpUrl = HttpUrl.parse(baseUrl); 
        if (httpUrl == null) { 
            throw new IllegalArgumentException("Illegal URL: " + baseUrl); 
        }
        return baseUrl(httpUrl); 
    }
    public Builder baseUrl(HttpUrl baseUrl) { 
        checkNotNull(baseUrl, "baseUrl == null"); 
        List<String> pathSegments = baseUrl.pathSegments(); 
        if (!"".equals(pathSegments.get(pathSegments.size() - 1))) { 
            throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl); 
        }
        this.baseUrl = baseUrl; 
        return this; 
    } 
...... 
} 

这里就是将url字符串解析成HttpUrl然后赋值给全局baseUrl 赋值

我们再看一下 addConverterFactory()

public Class Builder {
    ...... 
    private final List<Converter.Factory> converterFactories = new ArrayList<>(); 
    public Builder addConverterFactory(Converter.Factory factory) { 
        converterFactories.add(checkNotNull(factory, "factory == null")); 
        return this; 
    }
    ....... 
}

这个方法创建一个List集合(泛型为Converter.Factory类型的数组集合),通过addConverterFactory()方法将要添加的Converter.Factory放入集合中。

接着看 addCallAdapterFactory() 方法

public final class Retrofit { 
    private final List<CallAdapter.Factory> adapterFactories = new ArrayList<>(); 
    ....... 
    public static final class Builder { 
    ...... 
    public Builder addCallAdapterFactory(CallAdapter.Factory factory) { 
    adapterFactories.add(checkNotNull(factory, "factory == null")); 
    return this; 
    }
    ...... 
}
    ...... 

显而易见,这是一个通过List集合维护的Call适配器,也就是说用户可以添加多个CallAdapter以适配

Retrofit在不同平台或者框架上的使用;当然Retrofit也会有一个默认的设置,默认设置我们可以再build方法中看到

public Retrofit build() { 
    if (baseUrl == null) { 
        throw new IllegalStateException("Base URL required."); 
    } 
    //创建一个call,默认情况下使用okhttp作为网络请求器 
    okhttp3.Call.Factory callFactory = this.callFactory; //1 
    if (callFactory == null) { 
        callFactory = new OkHttpClient(); 
    }
    Executor callbackExecutor = this.callbackExecutor; 
    if (callbackExecutor == null) { 
        callbackExecutor = platform.defaultCallbackExecutor(); 
    }
    // Make a defensive copy of the adapters and add the default Call adapter. 
    List<CallAdapter.Factory> adapterFactories = new ArrayList<> 
   (this.adapterFactories); 
    adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); 
    //2 
    // Make a defensive copy of the converters. 
    List<Converter.Factory> converterFactories = new ArrayList<> 
    (this.converterFactories); 
    return new Retrofit(callFactory, baseUrl, converterFactories, 
        adapterFactories, callbackExecutor, validateEagerly); 
} 

注释1中创建一个call,默认情况下使用okhttp作为网络请求器,在注释2处设置了一个默认的

CallAdaptor

可以看到默认的callAdapterExecutorCallAdapterFactorycallAdapter其实也是运用了适配器模式,其实质就是网络请求器Call的适配器,而在Retrofit中Call就是指OKHttp,那么**CallAdapter** 就是用来将 OKHttp 适配给不同的平台的,在Retrofit中提供了四种CallAdapter,分别如下:

  1. ExecutorCallAdapterFactory(默认使用)
  1. GuavaCallAdapterFactory
  1. Java8CallAdapterFactory
  1. RxJavaCallAdapterFactory

我们已经将使用Builder模式创建出来的Retrofit实例分析完毕了,我们只需要对相关的功能进行配

置即可,Retrofit负责接收我们配置的功能然后进行对象的初始化,这个也就是Builder模式屏蔽掉创建

对象的复杂过程的好处。

image.png

creat中迪米特法则和门面模式的运用

迪米特法则

定义:一个对象应该对其他对象保持最少的了解。说白点就是尽量降低耦合。

  • 门面模式
    •   定义:基于迪米特法则拓展出来的一种设计模式,旨在将复杂的模块/系统访问入口控制的更加单一。

我们使用的时候直接和retrofit打交道时 只用到了build和creat,就是一种门面模式。注解功能更是retrofit的亮点。如果直接使用OkHttp,当在构造Request时要做很多繁琐的工作,且Request可能会在多处被构造,写的越多越容易出错。而retrofit通过注解的形式构造Request不仅观感上更加简洁,而且帮开发者撇除了多余信息(开发者只需关心请求内容,而不需关心解析、构造、发起请求的过程)。

所以利用门面模式帮助开发者屏蔽多余信息,使我们更加专注于业务开发是retrofit收到追捧的重要原因之一。

Retrofit的精髓——creat方法和其中动态代理的使用

使用retrofit时,我们写一个接口方法,然后调用retrofit.create()方法 之后,就可以直接进行网络请求了,这丝滑的实现背后,是动态代理的妙用。

网络请求接口的创建:

apiService = retrofit.create(ApiService.class);

何为动态代理?

什么是代理模式?

代理模式就是给对象提供一种代理对象去控制对该对象的访问,代理类与委托类有同样的接口,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

相当显示生活中的:外卖、房产中介

优点: 代理模式在访问实际对象时引入一定程度的间接性,可以做到在不修改对象代码的基础上对原对象 的功能进行修改或增强(低耦合性,高扩展性)

  • Java中的代理模式:
    • 目标类:原对象,我们需要通过代理对象控制它的访问,扩展其功能
    • 代理类:代理模式产生的对象,是原对象的“替身"

动态代理的概念

动态代理技术,是在内存中生成代理对象的一种技术。整个代理过程在内存中进行,我们不需要手写代理类的代码,而是直接在运行期,在JVM中凭空造出一个代理类对象供我们使用。

了解了动态代理后,我们看一下creat

public <T> T create(final Class<T> service) { 
    Utils.validateServiceInterface(service); 
    if (validateEagerly) { 
        eagerlyValidateMethods(service); 
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { 
        service },//动态代理
    new InvocationHandler() { 
        private final Platform platform = Platform.get(); 
        //类是在运行的过程中产生的,所以请求都会走到invoke,用到了AOP思想 ,
        //所以在invoke中,可以基于注解、参数合成、封装okhttp请求call给okhttp完成网络请求。
        //请求结果经过invoke的处理后变成javaBean
        @Override 
        public Object invoke(Object proxy, Method method, @Nullable 
            Object[] args)
        throws Throwable { 
            // If the method is a method from Object then defer to normal 
            invocation. 
            if (method.getDeclaringClass() == Object.class) { 
                return method.invoke(this, args); 
            }
            if (platform.isDefaultMethod(method)) { 
                return platform.invokeDefaultMethod(method, service, proxy, args); 
            }
        ServiceMethod<Object, Object> serviceMethod = 
            (ServiceMethod<Object, Object>) loadServiceMethod(method); // 1 
        OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args); 
        return serviceMethod.callAdapter.adapt(okHttpCall); //2 
        } 
    }); 
}

我们重点来分析一下注释1和注释2的代码,先来看看

loadServiceMethod(method)

ServiceMethod<?, ?> loadServiceMethod(Method method) { 
    ServiceMethod<?, ?> result = serviceMethodCache.get(method); 
    if (result != null) return result; 
    synchronized (serviceMethodCache) { 
    result = serviceMethodCache.get(method); // 1 
        if (result == null) { 
            result = new ServiceMethod.Builder<>(this, method).build(); // 2
            serviceMethodCache.put(method, result); 
        } 
    }
    return result; 
} 

这里将接口方法作为参数,通过loadServiceMethod()方法的封装返回一个ServiceMethod对象;在注释1里,会现在缓存里查找,如果没有,才会在注释2处重新build一个出来

ServiceMethod的 builder 内部类以及里面的build方法:

Class Builder{ 
    ....... 
    Builder(Retrofit retrofit, Method method) { 
        this.retrofit = retrofit; 
        this.method = method; // 获取接口中的方法名 
        this.methodAnnotations = method.getAnnotations(); //获取方法里的注解 
        this.parameterTypes = method.getGenericParameterTypes(); //获取方法
        //里的参数类型 
        this.parameterAnnotationsArray = method.getParameterAnnotations();//获取
        //接口方法里的注解内容 
    } 
    public ServiceMethod build() { 
        callAdapter = createCallAdapter(); // 1 
        //这里主要是根据接口方法的返回值类型已经注解为参数在适配器集合中获取想应的适配器
        responseType = callAdapter.responseType(); 
        if (responseType == Response.class || responseType == okhttp3.Response.class) 
    { 
            throw methodError("'" 
                + Utils.getRawType(responseType).getName() 
                + "' is not a valid response body type. Did you mean ResponseBody?"); 
        }
        responseConverter = createResponseConverter(); 
        for (Annotation annotation : methodAnnotations) { 
            parseMethodAnnotation(annotation); // 2 
        }
        int parameterCount = parameterAnnotationsArray.length; 
        parameterHandlers = new ParameterHandler<?>[parameterCount]; 
        for (int p = 0; p < parameterCount; p++) { 
            Type parameterType = parameterTypes[p]; 
            if (Utils.hasUnresolvableType(parameterType)) { 
                throw parameterError(p, "Parameter type must not include a type variable or 
    wildcard: %s", 
                  parameterType); 
            }
            Annotation[] parameterAnnotations = parameterAnnotationsArray[p]; 
            if (parameterAnnotations == null) { 
                throw parameterError(p, "No Retrofit annotation found."); 
            }
            parameterHandlers[p] = parseParameter(p, parameterType, 
            parameterAnnotations); 
            return new ServiceMethod<>(this); 
        }
}

还记得我们在构造retrofit的时候给builder传的参数吗,他们在这里的createCallAdapter()用上了。

回到刚才的ServiceMethod.Builder的build方法内

return new ServiceMethod<>(this);

最后返回一个ServiceMethod对象,此对象储存了解析了接口方法后的所有信息,还有一个CallAdapter对象

最后,回到retrofit的create的InvocationHandler的invoke方法的最后一行:

ServiceMethod serviceMethod = loadServiceMethod(method); 
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); 
return serviceMethod.callAdapter.adapt(okHttpCall);

最后调用了ServiceMethod的CallAapter对象的adapt方法生成了Call对象

invoke的切面事务的简单分析:

  • 基于接口参数信息,通过loadServiceMethod()拿到serviceMethod,再根据serviceMethod拿到相应的call
    •   ServiceMethod干的事: “备料”, loadServiceMethod拿到method,调用build(),在 build()中:拿到CallAdapter,拿到gson解析器

暂时无法在飞书文档外展示此内容

image.png