zoukankan      html  css  js  c++  java
  • Retrofit 入门 基本使用 总结 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
    MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

    目录

    Retrofit

    入门

    Retrofit 源码只有37个文件,其中22个文件是注解,还都和HTTP有关,真正暴露给用户的类并不多,看一遍 官方教程 ,大多数情景就可以无障碍使用。

    创建Retrofit实例

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://localhost:4567/")
        .build();

    Retrofit2 的baseUlr 必须以 /结束,不然会抛出一个IllegalArgumentException。

    接口定义

    public interface BlogService {
        @GET("blog/{id}")/*这里的{id} 表示是一个变量*/
        Call<ResponseBody> getBlog(@Path("id") int id);
    }

    注意,这里是interface不是class,所以我们是无法直接调用该方法,我们需要用Retrofit创建一个BlogService的代理对象

    BlogService service = retrofit.create(BlogService.class);

    接口调用
    拿到代理对象之后,就可以调用该方法啦

    Call<ResponseBody> call = service.getBlog(2);
    //用法和OkHttp的call如出一辙,不同的是,Android系统中回调方法执行在主线程
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            try {
                System.out.println(response.body().string());
                System.out.println(response.code() + "  " + response.isSuccessful());//200  true
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            t.printStackTrace();
        }
    });

    Retrofit注解

    Retrofit 共22个注解,为帮助大家更好理解,我将这22个注解分为三类,并用表格的形式展现出来。

    8个HTTP请求方法注解

    以上表格中的除HTTP以外都对应了HTTP标准中的请求方法,而HTTP注解则可以代替以上方法中的任意一个注解。
    有3个属性:

    • method 表示请求的方法,区分大小写
    • path 表示路径
    • hasBody 表示是否有请求体,默认为false,此时可以不写此参数
    public interface BlogService {
        @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
        Call<ResponseBody> getBlog(@Path("id") int id);
    }

    注:method 的值 retrofit 不会做处理,所以要自行保证其准确性,包括大小写,示例源码使用小写也可以是因为示例源码中的服务器不区分大小写

    3个标记类注解

    11个参数类注解

    注1:{占位符}和PATH尽量只用在URL的path部分,url中的参数使用Query和QueryMap代替,保证接口定义的简洁
    注2:Query、Field和Part这三者都支持数组和实现了Iterable接口的类型,如List,Set等,方便向后台传递数组。

    Gson与Converter

    在默认情况下Retrofit只支持将HTTP的响应体转换换为ResponseBody,但如果响应体只是支持转换为ResponseBody的话何必要引入泛型呢,返回值直接用一个Call就行了嘛。既然支持泛型,那说明泛型参数可以是其它类型的。

    Converter就是Retrofit为我们提供用于将ResponseBody转换为我们想要的类型,例如上面例子的接口可以写成这个样子:

    public interface BlogService {
      @GET("blog/{id}")
      Call<Result<Blog>> getBlog(@Path("id") int id);
    }

    当然只改变泛型的类型是不行的,我们在创建Retrofit时需要明确告知用于将ResponseBody转换我们泛型中的类型时需要使用的Converter。

    首先引入Gson支持:

    compile 'com.squareup.retrofit2:converter-gson:2.1.0'

    通过GsonConverterFactory为Retrofit添加Gson支持:

    Gson gson = new GsonBuilder()//配置你的Gson
            .setDateFormat("yyyy-MM-dd hh:mm:ss")
            .create();
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://localhost:4567/")
            .addConverterFactory(GsonConverterFactory.create(gson))//可以接收自定义的Gson,也可以不传
            .build();

    这样Retrofit就会使用Gson将ResponseBody转换成我们想要的类型。

    Gson 反序列化案例

    Call<Result<Blog>> call = retrofit.create(BlogService.class).getBlog(2);
    call.enqueue(new Callback<Result<Blog>>() {
        @Override
        public void onResponse(Call<Result<Blog>> call, Response<Result<Blog>> response) {
            Result<Blog> result = response.body();//已经转换为想要的类型了
            System.out.println(result);
        }
    
        @Override
        public void onFailure(Call<Result<Blog>> call, Throwable t) {
            t.printStackTrace();
        }
    });

    Gson 序列化案例
    下面演示如使创建一个Blog!

    @POST("blog")
    Call<Result<Blog>> createBlog(@Body Blog blog);

    @Body注解的的Blog将会被Gson转换成RequestBody发送到服务器。

    Blog blog = new Blog("author:包青天", "title:测试", "content:新建的Blog");
    Call<Result<Blog>> call = retrofit.create(BlogService.class).createBlog(blog);

    RxJava与CallAdapter

    上一节介绍的Converter是对于Call<T>中T的转换,而CallAdapter则可以对Call转换,这样的话返回值的类型就决定你后续的处理程序逻辑,同样Retrofit提供了多个CallAdapter,这里以RxJava的为例,用Observable代替Call。

    引入RxJava支持:

    compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
    //compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'//针对rxjava2.x(adapter-rxjava2的版本要 >= 2.2.0)

    通过RxJavaCallAdapterFactory为Retrofit添加RxJava支持:

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://localhost:4567/")
        .addConverterFactory(GsonConverterFactory.create(gson))
        .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
        //.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//针对rxjava2.x
        .build();

    接口设计:

    public interface BlogService {
        @GET("/blog")
        Observable<Result<List<Blog>>> getBlogs(@Query("page") int page);
        //如果需要Header的值,可以把返回值替换为 Observable<Response<Result<List<Blog>>>>
    }

    使用:

    retrofit.create(BlogService.class)
      .getBlogs(1)
      .subscribeOn(Schedulers.io())
      .subscribe(blogsResult -> System.out.println(blogsResult)); //Result<List<Blog>>

    像上面的这种情况最后我们无法获取到返回的Header和响应码的,如果我们需要这两者,提供两种方案:

    • Observable<retrofit2.Response<T>>代替 Observable<T>
    • Observable<retrofit2.adapter.rxjava.Result<T>>代替 Observable<T>,这个Result中包含了Response的实例

    自定义Converter

    本节的内容是教大家实现一简易的Converter,这里以返回格式为Call<String>为例。
    在此之前先了解一下Converter接口及其作用:

    public interface Converter<F, T> {
      // 实现从 F(rom) 到 T(o)的转换
      T convert(F value) throws IOException;
    
      // 用于向Retrofit提供相应Converter的工厂
      abstract class Factory {
        // 这里创建从ResponseBody其它类型的Converter,如果不能处理返回null
        // 主要用于对响应体的处理
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
          return null;
        }
    
        // 在这里创建从自定类型到ResponseBody的Converter,不能处理就返回null
        // 主要用于对Part、PartMap、Body注解的处理
        public Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
          return null;
        }
    
        // 这里用于对Field、FieldMap、Header、Path、Query、QueryMap注解的处理
        // Retrfofit对于上面的几个注解默认使用的是调用toString方法
        public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
          return null;
        }
      }
    }

    我们要想从Call<ResponseBody>转换为Call<String>,那么对应的F和T则分别对应ResponseBody和String,我们定义一个StringConverter并实现Converter接口。

    public static class StringConverter implements Converter<ResponseBody,String> {
      public static final StringConverter INSTANCE = new StringConverter();
      @Override
      public String convert(ResponseBody value) throws IOException {
        return value.string();
      }
    }

    我们需要一个Fractory来向Retrofit注册StringConverter

    public static class StringConverterFactory extends Converter.Factory {
        public static final StringConverterFactory INSTANCE = new StringConverterFactory();
    
        public static StringConverterFactory create() {
            return INSTANCE;
        }
    
        // 我们只关实现从ResponseBody 到 String 的转换,所以其它方法可不覆盖
        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
            if (type == String.class) return StringConverter.INSTANCE;
            return null;//其它类型我们不处理,返回null就行
        }
    }

    使用Retrofit.Builder.addConverterFactory向Retrofit注册我们StringConverterFactory:

    Retrofit retrofit = new Retrofit.Builder()
          .baseUrl("http://localhost:4567/")
          .addConverterFactory(StringConverterFactory.create())// 如是有Gson这类的Converter一定要放在其它前面
          .addConverterFactory(GsonConverterFactory.create())
          .build();

    注:addConverterFactory是有先后顺序的,如果有多个ConverterFactory都支持同一种类型,那么就是只有第一个才会被使用,而GsonConverterFactory是不判断是否支持的,所以这里交换顺序会有异常抛出,原因是类型不匹配。

    只要返回值类型的泛型参数就会由我们的StringConverter处理,不管是Call还是Observable

    自定义CallAdapter

    本节将介绍如何自定一个CallAdapter,并验证是否所有的String都会使用我们上一节中自定义的Converter。

    先看一下CallAdapter接口定义及各方法的作用:

    public interface CallAdapter<T> {
    
        // 直正数据的类型 如Call<T> 中的 T
        // 这个 T 会作为Converter.Factory.responseBodyConverter 的第一个参数
        // 可以参照上面的自定义Converter
        Type responseType();
    
        <R> T adapt(Call<R> call);
    
        // 用于向Retrofit提供CallAdapter的工厂类
        abstract class Factory {
            // 在这个方法中判断是否是我们支持的类型,returnType 即Call<Requestbody>和`Observable<Requestbody>`
            // RxJavaCallAdapterFactory 就是判断returnType是不是Observable<?> 类型
            // 不支持时返回null
            public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);
    
            // 用于获取泛型的参数 如 Call<Requestbody> 中 Requestbody
            protected static Type getParameterUpperBound(int index, ParameterizedType type) {
                return Utils.getParameterUpperBound(index, type);
            }
    
            // 用于获取泛型的原始类型 如 Call<Requestbody> 中的 Call
            // 上面的get方法需要使用该方法。
            protected static Class<?> getRawType(Type type) {
                return Utils.getRawType(type);
            }
        }
    }

    了解了CallAdapter的结构和其作用之后,我们就可以开始自定义我们的CallAdapter了,本节以CustomCall<String>为例。
    在此我们需要定义一个CustomCall,不过这里的CustomCall作为演示只是对Call的一个包装,并没有实际的用途。

    public static class CustomCall<R> {
      public final Call<R> call;
    
      public CustomCall(Call<R> call) {
        this.call = call;
      }
    
      public R get() throws IOException {
        return call.execute().body();
      }
    }

    有了CustomCall,我们还需要一个CustomCallAdapter来实现Call<T>CustomCall<T>的转换,这里需要注意的是最后的泛型,是我们要返回的类型。

    public static class CustomCallAdapter implements CallAdapter<CustomCall<?>> {
      private final Type responseType;
    
      // 下面的 responseType 方法需要数据的类型
      CustomCallAdapter(Type responseType) {
        this.responseType = responseType;
      }
    
      @Override
      public Type responseType() {
        return responseType;
      }
    
      @Override
      public <R> CustomCall<R> adapt(Call<R> call) {
        return new CustomCall<>(call);// 由 CustomCall 决定如何使用
      }
    }

    提供一个CustomCallAdapterFactory用于向Retrofit提供CustomCallAdapter:

    public static class CustomCallAdapterFactory extends CallAdapter.Factory {
      public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory();
    
      @Override
      public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        // 获取原始类型
        Class<?> rawType = getRawType(returnType);
        // 返回值必须是CustomCall并且带有泛型
        if (rawType == CustomCall.class && returnType instanceof ParameterizedType) {
          Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType);
          return new CustomCallAdapter(callReturnType);
        }
        return null;
      }
    }

    使用addCallAdapterFactory向Retrofit注册CustomCallAdapterFactory

    Retrofit retrofit = new Retrofit.Builder()
          .baseUrl("http://localhost:4567/")
          .addConverterFactory(Example09.StringConverterFactory.create())
          .addConverterFactory(GsonConverterFactory.create())
          .addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE)
          .build();

    注: addCallAdapterFactory与addConverterFactory同理,也有先后顺序。

    其它说明

    Retrofit.Builder的其他方法

    Retrofit.Builder中的所有方法:

    baseUrl(String baseUrl) 和 baseUrl(okhttp3.HttpUrl baseUrl)
    addConverterFactory(retrofit2.Converter.Factory factory)//对Call<T>中T的进行类型转换
    addCallAdapterFactory(retrofit2.CallAdapter.Factory factory)//对Call进行转换
    callbackExecutor(java.util.concurrent.Executor executor)
    callFactory(okhttp3.Call.Factory factory)
    client(OkHttpClient client)
    validateEagerly(boolean validateEagerly)
    • callbackExecutor(Executor):指定Call.enqueue时使用的Executor,所以该设置只对返回值为Call的方法有效
    • callFactory(Factory):设置一个自定义的okhttp3.Call.Factory,那什么是Factory呢?OkHttpClient就实现了此接口。如果你需要对okhttpclient进行详细的设置,需要构建OkHttpClient对象,然后通过callFactory方法传入,否则new一个默认的OkHttpClient。下面的client方法最终也是调用了该方法,所有两者不能共用
    • client(OkHttpClient):设置自定义的OkHttpClient,以前的Retrofit版本中,不同的Retrofit对象共同OkHttpClient,在2.0后,各对象各自持有不同的OkHttpClient实例,所以当你需要共用OkHttpClient或需要自定义时则可以使用该方法,如:处理Cookie、使用stetho调式等
    • validateEagerly(boolean):是否在调用create(Class)时检测接口定义是否正确,而不是在调用方法才检测。适合在开发、测试时使用

    很多时候,比如你使用Retrofit需要统一的log管理,给每个请求添加统一的header等,这些都应该通过OkHttpClient去操作。你可以单独写一个OkHttpClient的单例生成类,在这个里面完成你所需的所有的配置,然后通过Retrofit.Builder的callFactory方法设置给retrofit。

    Retrofit的Url组合规则

    从上面不能难看出以下规则:

    • 如果你在注解中提供的url是完整的url,则url将作为请求的url。
    • 如果你在注解中提供的url是不完整的url,且不以 / 开头,则请求的url为baseUrl+注解中提供的值
    • 如果你在注解中提供的url是不完整的url,且以 / 开头,则请求的url为baseUrl的主机部分+注解中提供的值

    Retrofit提供的Converter

    Converter Gradle依赖
    Gson com.squareup.retrofit2:converter-gson:2.0.2
    Jackson com.squareup.retrofit2:converter-jackson:2.0.2
    Moshi com.squareup.retrofit2:converter-moshi:2.0.2
    Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
    Wire com.squareup.retrofit2:converter-wire:2.0.2
    Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
    Scalars com.squareup.retrofit2:converter-scalars:2.0.2

    Retrofit提供的CallAdapter

    CallAdapter Gradle依赖
    guava com.squareup.retrofit2:adapter-guava:2.0.2
    Java8 com.squareup.retrofit2:adapter-java8:2.0.2
    rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2

    如何运行项目

    测试接口服务器在 server 项目下,直接运行 RESTServer.main() 即可启动测试服务器
    接口地址http://localhost:4567/

    当然你也可以自己借助 json-server 或 最新开源的 Parse 搭建一个REST API,不过都需要安装Node.js。

    接口列表:

    地址 请求方法 参数 说明
    /blog GET page={page},sort=asc或desc 分页获取Blog列表,每页10条
    /blog/{id} GET id 获取指定ID的Blog
    /blog POST {"author":"","title":"","content":""} 创建一个新Blog
    /blog/{id} PUT {"author":"","title":"","content":""} 中至少一个 修改Blog
    /blog/{id} DELETE id 删除一个Blog
    /form POST 任意,最终以Json Object形式返回 用于测试Form表单,支持文件上传
    /headers GET showAll=true或false,默认false 返回自定义请求头,all=true是显示全部

    2017-9-8

    附件列表

    • 相关阅读:
      Python 的with关键字
      java解析xml
      Java IO & Serialization
      Java动态编译
      爬虫下载City Scape数据
      Pytorch多GPU训练
      可视化利器Visdom
      GLOG使用Demo
      hyperopt自动调参
      [Redis源码阅读]redis持久化
    • 原文地址:https://www.cnblogs.com/baiqiantao/p/7494850.html
    Copyright © 2011-2022 走看看