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

说到网络库就会想到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月新番-夏目友人帐第六季
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容