[Android组件解读] 初次接触Retrofit

作者: 乱码桑 | 来源:发表于2017-04-06 09:38 被阅读400次

    说到网络库就会想到Google的HttpUrlConnection和Apache的HttpClient。

    Google推荐使用HttpUrlConnection,抛弃了HttpClient

    HttpClient已在Android6.0被废弃了(吐槽:明明HttpClient功能更强大啊),

    查看为什么抛弃HttpClient,可以看下【Android】Android2.3版本以上谷歌为何推荐使用HttpURLConnection却弃用 Apache HttpClient

    想继续使用HttpClient的话,可以参考android6.0SDK 删除HttpClient的相关类的解决方法

    除了HttpUrlConnection,还有Google推荐的Volley(没怎么用过),和square的okhttp,还有就是今天介绍的square的retrofit

    这个年头不会点retrofit + rxjava + mvp...的组合拳,都不好意思说是新时代的Android开发

    为什么我用的组件都是square公司出品的呢?只能说明他们做的太好了。


    回到正题

    关于Retrofit的介绍,引用它在github上的一句话

    Type-safe HTTP client for Android and Java by Square
    用于Android和Java的类型安全的Http框架
    

    现在市面上使用的是retrofit2.0版本,跟以前的1.9版本有部分API不一样,可以查看Retrofit 1.9 迁移到 Retrofit 2.0,建议使用新的版本

    Retrofit是使用RESETful API的。

    这里简单的介绍下关于RESETful的架构:

    (1)每一个URI代表一种资源;
    (2)客户端和服务器之间,传递这种资源的某种表现层;
    (3)客户端通过四个HTTP动词(GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源),
    对服务器端资源进行操作,实现"表现层状态转化"。
    

    更多关于RESET API可以看阮一峰的理解RESTful架构

    Retrofit的git地址:https://github.com/square/retrofit

    Retrofit的官方地址:http://square.github.io/retrofit/

    使用姿势

    1. 往build.gradle添加依赖,不需要额外添加okhttp,retrofit内部支持

       // retrofit
       compile 'com.squareup.retrofit2:retrofit:2.2.0'
       // gson转化器
       compile 'com.squareup.retrofit2:converter-gson:2.2.0'
      
    2. 创建接口,声明API

       // 获取请求API
       // 请求接口 https://api.github.com
       public interface GitHub {
       @GET("/repos/{owner}/{repo}/contributors")
       Call<List<Contributor>> contributors(
               @Path("owner") String owner,
               @Path("repo") String repo);
       }
       
       // 请求对象类
       public static class Contributor {
           public final String login;
           public final int contributions;
      
           public Contributor(String login, int contributions) {
           this.login = login;
           this.contributions = contributions;
           }
       }
      
    3. 方法调用

       public void request() throws IOException {
      
          // 1.创建Retrofit对象,根据Retrofit模板创建
          Retrofit retrofit = new Retrofit.Builder()
                  // 传入baseurl地址,
                  .baseUrl("https://api.github.com")
                  // 传入gson转化器
                  .addConverterFactory(GsonConverterFactory.create())
                  .build();
      
          // 2.获取GitHub的对象
          GitHub github = retrofit.create(GitHub.class);
      
          // 3.传入参数
              Call<List<Contributor>> call = github.contributors("square", "retrofit");
      
          // 4.异步请求接口
          // 请求接口格式为"https://api.github.com/repos/square/retrofit/contributors"
          call.enqueue(new Callback<List<Contributor>>() {
              @Override
              public void onResponse(Call<List<Contributor>> call, Response<List<Contributor>> response) {
                  List<Contributor> contributors = response.body();
                  for (Contributor contributor : contributors) {
                      Log.e("MainActivity",contributor.login + " (" + contributor.contributions + ")");
                  }
              }
      
              @Override
              public void onFailure(Call<List<Contributor>> call, Throwable t) {
      
              }
          });
       }
      

    通过上面的例子,应该能够理解Retrofit的使用了吧

    关于使用,可以参考下官网介绍http://square.github.io/retrofit/

    对此,作者菌有几个问题

    • Retrofit提供的这么多注解是怎么运用的?
    • Retrofit有多少种注解
    • Retrofit生成API对象后,怎么发起请求的

    源码解读

    先查看retrofit的代码构造

    .
    └── retrofit2
        ├── BuiltInConverters.java --默认的转化器,支持流,字符串
        ├── Call.java  -- 请求的发送/取消,并获取当前请求的状态
        ├── CallAdapter.java
        ├── Callback.java. --回调方法
        ├── Converter.java -- 转化器,将okhttp中的RequestBody,ResponseBody进行转化,支持XML,JSON,ProtocolBuf,如GsonConverterFactory,将返回的内容或者发送Body中的内容转化,
        ├── DefaultCallAdapterFactory.java
        ├── ExecutorCallAdapterFactory.java
        ├── HttpException.java
        ├── OkHttpCall.java --实现了Call的接口,使用okhttp3.0的API
        ├── ParameterHandler.java --参数句柄,处理retrofit头文件的操作请求
        ├── Platform.java --判断当前运行环境是Android还是Java8
        ├── RequestBuilder.java
        ├── Response.java 服务端返回内容,可以通过response.body()获取到返回内容
        ├── Retrofit.java  --`对外方法`
        ├── ServiceMethod.java
        ├── Utils.java
        ├── http --http文件夹中包含的是retrofit所有的注解类,关于Http的操作和上传字段,以及Header配置
        │   ├── Body.java  用于Post,根据转换方式将实例对象转化为对应字符串传递参数.比如Retrofit添加GsonConverterFactory则是将body转化为gson字符串进行传递
        │   ├── DELETE.java Delete请求
        │   ├── Field.java  用于Post方式传递参数,需要在请求接口方法上添加@FormUrlEncoded,即以表单的方式传递参数
        │   ├── FieldMap.java
        │   ├── FormUrlEncoded.java 同@Field使用
        │   ├── GET.java GET请求
        │   ├── HEAD.java
        │   ├── HTTP.java
        │   ├── Header.java 添加http header
        │   ├── HeaderMap.java
        │   ├── Headers.java 跟@Header作用一样,只是使用方式不一样,@Header是作为请求方法的参数传入,@Headers是以固定方式直接添加到请求方法上
        │   ├── Multipart.java 配合@Multipart使用,一般用于文件上传
        │   ├── OPTIONS.java
        │   ├── PATCH.java
        │   ├── POST.java  Post请求
        │   ├── PUT.java   PUT请求
        │   ├── Part.java 一般用于文件上传
        │   ├── PartMap.java
        │   ├── Path.java 用于URL上占位符
        │   ├── Query.java 用于Http Get请求传递参数
        │   ├── QueryMap.java
        │   ├── QueryName.java  用于Http Get请求传递参数
        │   ├── Streaming.java  用于Http Get请求传递流形式参数
        │   ├── Url.java 直接赋值URL
        │   └── package-info.java
        └── package-info.java
    

    附上一篇别人写的###Retrofit注解含义###

    讲解下Retrofit的使用原理

    1. 首先讲解Retrofit初始化
     Retrofit retrofit = new Retrofit.Builder()
                // 传入baseurl地址,
                .baseUrl("https://api.github.com")
                // 传入gson转化器
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    

    此处用了Build机制来初始化,现在很多初始化通过Build机制,如Dialog,OKHttp,Picasso等

    Build构建时可以传入以下配置

    Builder(Platform platform) {
          this.platform = platform;
          // Add the built-in converter factory first. This prevents overriding its behavior but also
          // ensures correct behavior when using converters that consume all types.
          converterFactories.add(new BuiltInConverters());
        }
    
        public Builder() {
            // 获取当前的系统版本
            this(Platform.get());
        }
    
          /**
           * 配置Client,默认使用okhttp3.OkHttpClient
           * @param client
           * @return
           */
        public Builder client(OkHttpClient client) {
          return callFactory(checkNotNull(client, "client == null"));
        }
    
          /**
           * 创建CallFactory,用于生成请求Http的Call
           * @param factory
           * @return
           */
        public Builder callFactory(okhttp3.Call.Factory factory) {
          this.callFactory = checkNotNull(factory, "factory == null");
          return this;
        }
    
          /**
           * 配置URL
           * @param baseUrl
           * @return
           */
        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);
        }
    
        /**
         * 配置URL,下面有几组实例配置URL
         * <p>
         * <b>Correct:</b><br>
         * Base URL: http://example.com/api/<br>
         * Endpoint: foo/bar/<br>
         * Result: http://example.com/api/foo/bar/
         * <p>
         * <b>Incorrect:</b><br>
         * Base URL: http://example.com/api<br>
         * Endpoint: foo/bar/<br>
         * Result: http://example.com/foo/bar/
         * <p>
         * This method enforces that {@code baseUrl} has a trailing {@code /}.
         * <p>
         * <b>Endpoint values which contain a leading {@code /} are absolute.</b>
         * <p>
         * Absolute values retain only the host from {@code baseUrl} and ignore any specified path
         * components.
         * <p>
         * Base URL: http://example.com/api/<br>
         * Endpoint: /foo/bar/<br>
         * Result: http://example.com/foo/bar/
         * <p>
         * Base URL: http://example.com/<br>
         * Endpoint: /foo/bar/<br>
         * Result: http://example.com/foo/bar/
         * <p>
         * <b>Endpoint values may be a full URL.</b>
         * <p>
         * Values which have a host replace the host of {@code baseUrl} and values also with a scheme
         * replace the scheme of {@code baseUrl}.
         * <p>
         * Base URL: http://example.com/<br>
         * Endpoint: https://github.com/square/retrofit/<br>
         * Result: https://github.com/square/retrofit/
         * <p>
         * Base URL: http://example.com<br>
         * Endpoint: //github.com/square/retrofit/<br>
         * Result: http://github.com/square/retrofit/ (note the scheme stays 'http')
         */
        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;
        }
    
          /**
           * 添加转换器
           * @param factory
           * @return
           */
        public Builder addConverterFactory(Converter.Factory factory) {
          converterFactories.add(checkNotNull(factory, "factory == null"));
          return this;
        }
    
        /**
         * Add a call adapter factory for supporting service method return types other than {@link
         * Call}.
         */
        public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
          adapterFactories.add(checkNotNull(factory, "factory == null"));
          return this;
        }
    
          /**
           * 回调线程
           * @param executor
           * @return
           */
        public Builder callbackExecutor(Executor executor) {
          this.callbackExecutor = checkNotNull(executor, "executor == null");
          return this;
        }
    
        public Builder validateEagerly(boolean validateEagerly) {
          this.validateEagerly = validateEagerly;
          return this;
        }
    
        /**
         * Create the {@link Retrofit} instance using the configured values.
         * <p>
         * Note: If neither {@link #client} nor {@link #callFactory} is called a default {@link
         * OkHttpClient} will be created and used.
         */
        public Retrofit build() {
          if (baseUrl == null) {
            throw new IllegalStateException("Base URL required.");
          }
    
          // 默认使用okhttp3.0的OKHttpClient
          okhttp3.Call.Factory callFactory = this.callFactory;
          if (callFactory == null) {
            callFactory = new OkHttpClient();
          }
    
          // 若Platform是Android,回调的是主线程
          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));
    
          // Make a defensive copy of the converters.
          List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
    
          return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
              callbackExecutor, validateEagerly);
        }
    

    此处注意的是baseUrl,正确写法

    * Base URL: http://example.com/api/
    * Endpoint: foo/bar/
    * Result: http://example.com/api/foo/bar/
    

    Platform判断当前运行环境是Android还是JAVA8

    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();
        } catch (ClassNotFoundException ignored) {
        }
        return new Platform();
      }
    

    2.获取GitHub的对象,传入参数

    GitHub github = retrofit.create(GitHub.class);
    //传入参数
    Call<List<Contributor>> call = github.contributors("square", "retrofit");
    

    通过代理模式来处理接口

    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();
    
                        @Override
                        public Object invoke(Object proxy, Method method, 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,进行URL组装
                            ServiceMethod<Object, Object> serviceMethod =
                                    (ServiceMethod<Object, Object>) loadServiceMethod(method);
                            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                            return serviceMethod.callAdapter.adapt(okHttpCall);
                        }
                    });
        }
    

    3.调用call.enqueue(new Callback...);

    使用的是OKHttpCall,使用了OKHttp的Call,然后进行Http请求。

    Call的接口定义

    public interface Call<T> extends Cloneable {
      /**
       * 同步发送请求,返回结果
       * @return
       * @throws IOException
       */
      Response<T> execute() throws IOException;
    
      /**
       * 异步发送请求,通过Callback回调返回
       * @param callback
       */
      void enqueue(Callback<T> callback);
    
      /**
       * 是否正在执行发送
       * @return
       */
      boolean isExecuted();
    
      /**
       * 取消发送的请求,若请求还未执行
       */
      void cancel();
    
      /**
       * 请求是否被取消
       * @return
       */
      boolean isCanceled();
    
      /**
       * Create a new, identical call to this one which can be enqueued or executed even if this call
       * has already been.
       */
      Call<T> clone();
    
      /** The original HTTP request. */
      Request request();
    }
    

    4.其他:如何构建Http请求,主要实在ServiceMethod.java这个类中

    以@Path为例

     else if (annotation instanceof Path) {
            if (gotQuery) {
              throw parameterError(p, "A @Path parameter must not come after a @Query.");
            }
            if (gotUrl) {
              throw parameterError(p, "@Path parameters may not be used with @Url.");
            }
            if (relativeUrl == null) {
              throw parameterError(p, "@Path can only be used with relative url on @%s", httpMethod);
            }
            gotPath = true;
    
            Path path = (Path) annotation;
            String name = path.value();
            validatePathName(p, name);
    
            Converter<?, String> converter = retrofit.stringConverter(type, annotations);
            return new ParameterHandler.Path<>(name, converter, path.encoded());
            }
    

    通过刚才的代理模式调用ServiceMethod,将接口中的注解生成完整的Http请求

    相关资料

    你真的会用Retrofit2吗?Retrofit2完全教程

    Retrofit各个注解的含义及作用

    Android 网络框架 Retrofit2.0介绍、使用和封装

    理解RESTful架构

    RESTful API 设计指南

    jdk动态代理实现原理


    期待下4月新番-夏目友人帐第六季

    相关文章

      网友评论

      • G米:期待一拳超人第二季:heart_eyes:

      本文标题: [Android组件解读] 初次接触Retrofit

      本文链接:https://www.haomeiwen.com/subject/larfattx.html