Retrofit(一)

Android知识总结

一、简介

1、Retrofit是什么?

准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。
原因:网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装

我们先来看看下面这个图:


上图说明了如下几点:

    1. App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作。
    1. 在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析。

所以,网络请求的本质仍旧是OkHttp完成的,retrofit只是帮使用者来进行工作简化的,比如配置网络,处理数据等工作,提高这一系列操作的复用性。这也就是网上流行的一个不太准确的总结:okhttp是瑞士军刀,retrofit则是讲这边瑞士军刀包装从了一个非常好用的指甲钳。

2、Retrofit用到的 8 大设计模式

  • 1)Retrofit 实例使用建造者模式通过Builder类构建。
    当构造函数的参数大于4个,
    且存在可选参数的时候既可以使用 建造者设计模式

  • 2)Retrofit 创建时的callFactory,使用工厂方法设计模式,但是似乎并不打算支持其他的工厂。

  • 3)整个retrofit 采用的时外观模式。统一的调用创建网络请求接口实例和网络请求参数配置的方法。

  • 4)retrofit里面使用了动态代理来创建网络请求接口实例,这个是retrofit对用户使用来说最大的复用,其它的代码都是为了支撑这个动态代理给用户带来便捷性的。

  • 5)使用了策略模式对serviceMethod对象进行网络请求参数配置,即通过解析网络请求接口方法的参数、返回值和注解类型,从Retrofit对象中获取对应的网络的url地址、网络请求执行器、网络请求适配器和数据转换器。

  • 6)ExecuteCallBack 使用装饰者模式来封装callbackExecutor,用于完成线程的切换。

  • 7)ExecutorCallbackCall 使用静态代理(委托) 代理了Call进行网络请求,真正的网络请求由okhttpCall执行,然而okHttpCall不是自己执行,它是okhttp 提供call给 外界(retrofit)使用的唯一门户,其实这个地方就是门面模式

  • 8)ExecutorCallbackCall 的被初始化是在ExecutorCallAdapterFactory里面通过适配器模式被创建的。
    CallAdapter采用了适配器模式 为创建访问Call接口提供服务。默认不添加Rxjava则使用默认的ExecutorCallAdapterFactory 将okhttp3.call转变成为 retroift中的call,如果有Rxjava则将okhttp3.call转化为abservable。

3、依赖库

    implementation  'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'

4、注解类型

二、请求方法类

2.1、序号 1 ~ 7

  • 分别对应 HTTP 的请求方法;
  • 接收一个字符串表示接口 path ,与 baseUrl 组成完整的 Url;
  • 可以不指定,结合 @Url 注解使用;
  • url 中可以使用变量,如 {id} ,并使用 @Path(“id”) 注解为 {id} 提供值
@GET("project/tree/json")
Call getProject1();
//简单的get请求(URL中带有两个参数)
  @GET("news/{userId}")
  Call getItem(@Path("userId") String userId,@Path("type") String type);

2.2、序号 8

  • 可用于替代以上 7 个,及其他扩展方法;
  • 有 3 个属性:method、path、hasBody、 举个例子
@HTTP(method = "get", path = "project/tree/json",hasBody = false)
 Call getProject2();

三、标记类

3.1、FormUrlEncoded

登录页面使用: Content-Type:application/x-www-form-urlencoded

  • 用于修饰Field注解和FieldMap注解
  • 使用该注解,表示请求正文将使用表单网址编码。字段应该声明为参数,并用@Field注释或FieldMap注释。使用FormUrlEncoded注解的请求将具application/ x-www-form-urlencodedMIME类型。字段名称和值将先进行UTF-8进行编码,再根据RFC-3986进行URI编码.
//普通参数
@FormUrlEncoded
@POST("/")
Call example(@Field("name") String name,@Field("occupation") String occupation);

//固定或可变数组
@FormUrlEncoded
@POST("/list")
Call example(@Field("name") String... names);

3.1、Multipart

上传文件使用: Content-Type:multipart/form-data
使用该注解,表示请求体是多部分的,每个部分作为一个参数,且用PartPartMap注解声明。

  • 上传单张图片
///////上传单张图片//////
  /**
  * Multipart:表示请求实体是一个支持文件上传的Form表单,需要配合使用@Part,适用于
有文件 上传的场景
  * Part:用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况
  * PartMap:用于表单字段,默认接受的类型是Map,可用于实现多
文件上传
  * Part 后面支持三种类型,{@link RequestBody}、{@link
okhttp3.MultipartBody.Part} 、任意类型;
  *
  * @param file 服务器指定的上传图片的key值
  * @return
  */
@Multipart
@POST("project/upload")
Call upload1(@Part("file" + "\";filename=\"" + "test.png") RequestBody file);

@Multipart
@POST("project/xxx")
Call upload2(@Part MultipartBody.Part file);

////////请求///////////
//上传单个图片1
File file = new File("");
RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);
wanAndroidApi.upload1(requestBody).execute();

//上传单个图片2
MultipartBody.Part imagePart = MultipartBody.Part.createFormData("上传的key", file.getName(), requestBody);
wanAndroidApi.upload2(imagePart)
      .enqueue(new Callback() {
      @Override
      public void onResponse(Call call, Response response) { }
        @Override
        public void onFailure(Call call, Throwablet) { }
       });
  • 上传多张图片
///////上传多张图片//////
@Multipart
@POST("project/upload")
Call upload3(@PartMap Map map);

@Multipart
@POST("project/xxx")
Call upload4(@PartMap Map map);

////////使用//////////
//上传多张图片1
//图片集合
List files = new ArrayList<>();
Map map = new HashMap<>();
for (int i = 0; i < files.size(); i++) {
   RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), files.get(i));
   map.put("file" + i + "\";filename=\"" + files.get(i).getName(), requestBody);
}
wanAndroidApi.upload3(map).execute();


//上传多张图片2
Map map1 = new HashMap<>();

File file1 = new File("");
RequestBody requestBody1 = RequestBody.create(MediaType.parse("image/png"), file1);
MultipartBody.Part part1 = MultipartBody.Part.createFormData("上传的key1", file1.getName(), requestBody1);
map1.put("上传的key1", part1);

File file2 = new File("");
RequestBody requestBody2 = RequestBody.create(MediaType.parse("image/png"), file2);
MultipartBody.Part part2 = MultipartBody.Part.createFormData("上传的key2", file2.getName(), requestBody2);
map1.put("上传的key2", part2);

wanAndroidApi.upload4(map1).execute();
  • 图文混传
//////图文混传/////
  /**
  * @param params
  * @param files
  * @return
  */
  @Multipart
  @POST("upload/upload")
  Call upload5(@FieldMap() Map params, @PartMap() Map files);

  /**
  * Part 后面支持三种类型,{@link RequestBody}、{@link
okhttp3.MultipartBody.Part} 、任意类型;
  *
  * @param userName
  * @param passWord
  * @param file
  * @return
  */
  @Multipart
  @POST("project/xxx")
  Call upload6(@Part("username") RequestBody userName,
               @Part("password") RequestBody passWord,
               @Part MultipartBody.Part file);


    //////使用///////
    MediaType textType = MediaType.parse("text/plain");
    RequestBody name = RequestBody.create(textType, "zero");
    RequestBody password = RequestBody.create(textType, "123456");
    File file = new File("");
    RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);
    MultipartBody.Part part = MultipartBody.Part.createFormData("上传的key", file.getName(), requestBody);

    wanAndroidApi
       .upload6(name, password, part)
       .enqueue(new Callback() {
          @Override
          public void onResponse(Call call, Response response) {
         }
          @Override
          public void onFailure(Call call, Throwable t) {
         }
       });

3.1、Streaming

未使用@Straming 注解,默认会把数据全部载入内存,之后通过流获取数据也是读取内存中数据,所以返回数据较大时,需要使用该注解。
处理返回Response的方法的响应体,用于下载大文件。

/**
  * Streaming注解:表示响应体的数据用流的方式返回,
  * 适用于返回的数据比较大,该注解在在下载大文件的特别有用
  */
  @Streaming
  @GET
  Call downloadFile(@Url String fileUrl);

四、参数注解

注意:

  • 1、Map用来组合复杂的参数,并且对于FieldMap,HeaderMap,PartMap,QueryMap这四种作用方法的注解,其参数类型必须为Map实例,且key的类型必须为String类型,否则抛出异常。
  • 2、Query、QueryMap与Field、FieldMap功能一样,生成的数据形式一样;Query、QueryMap的数据体现在Url上;Field、FieldMap的数据是请求体
  • 3、{占位符}和PATH尽量只用在URL的path部分,url的参数使用Query、QueryMap代替,保证接口的简洁
  • 4、Query、Field、Part支持数据和实现了iterable接口的类型,如List、Set等,方便向后台传递数组,代码如下:
  • 5、以上部分注解真正的实现在ParameterHandler类中,每个注解的真正实现都是ParameterHandler类中的一个final类型的内部类,每个内部类都对各个注解的使用要求做了限制,比如参数是否可空、键和值是否可空等。
  • 6、@FormUrlEncoded 注解和@Multipart 注解不能同时使用,否则会抛出methodError(“Only one encoding annotation is allowed.”),可在ServiceMethod类中parseMethodAnnotation()方法中找到不能同时使用的具体原因。
  • 7、@Path 与@Url 注解不能同时使用,否则会抛出parameterError(p, "@Path parameters may not be used with @Url."),可在ServcieMethod类中parseParameterAnnotation()方法中找到不能同时使用的具体代码。其实原因也是很好理解:Path注解用于替换url中的参数,这就要求在使用path注解时,必须已经存在请求路径。不然没法替换路径中指定的参数。而@Url 注解是在参数中指定了请求路径的,这时候情定请求路径已经晚,path注解找不到请求路径,更别提更换请求路径了中的参数了。
  • 8 使用@Body 注解的参数不能使用form 或multi-part编码,即如果为方法使用了FormUrlEncodedMultipart注解,则方法的参数中不能使用@Body 注解,否则会抛出异常parameterError(p, “@Body parameters cannot be used with form or multi-part encoding.”)

4.1、Headers

使用 @Headers 注解设置固定的请求头,所有请求头不会相互覆盖,即使名字相同

@Headers("Cache-Control: max-age=640000")
@GET("project/list")
Call getMsg1();

@Headers({ "Accept: application/vnd.github.v3.full+json","User-Agent:
Retrofit-Sample-App"})
@GET("project/{username}")
Call getMsg2(@Path("username") String username);

4.2、Header

使用 @Header 注解动态更新请求头,匹配的参数必须提供给 @Header ,若参数值为 null ,这个头会被省略,否则,会使用参数值的 toString 方法的返回值。
具有相同名称的请求头不会相互覆盖,而是照样添加到请求头中。

@GET("project")
Call getProject3(@Header("Authorization") String authorization);

4.3、Body

使用 @Body 注解,指定一个对象作为 request body,使用@Body 注解定义的参数不能为null。
当你发送一个post或put请求,但是又不想作为请求参数或表单的方式发送请求时,使用该注解定义的参数可以直接传入一个实体类,retrofit会通过convert把该实体序列化并将序列化的结果直接作为请求体发送出去。

   public class ProjectBean {
    private String owner;
    private String name;
    ProjectBean (String owner, String name) {
      this.owner = owner;
      this.name = name;
    }
  }

@POST("project/new")
Call createProject(@Body ProjectBean user);

4.4、Field

  • 作用于方法的参数
  • 用于发送一个表单请求
  • 用String.valueOf()把参数值转换为String,然后进行URL编码,当参数值为null值时,会自动忽略,如果传入的是一个List或array,则为每一个非空的item拼接一个键值对,每一个键值对中的键是相同的,值就是非空item的值,如:name=张三&name=李四&name=王五,另外,如果item的值有空格,在拼接时会自动忽略,例如某个item的值为:张三,则拼接后为name=张三。
//固定或可变数组
@FormUrlEncoded
@POST("/list")
Call example(@Field("name") String... names);

4.5、FieldMap

  • 作用于方法的参数
  • 用于发送一个表单请求
  • map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
FormUrlEncoded
@POST("/examples")
Call example(@FieldMap Map fields);

4.6、Part

  • 作用于方法的参数,用于定义Multipart请求的每个part
  • 使用该注解定义的参数,参数值可以为空,为空时,则忽略
  • 使用该注解定义的参数类型有以下3种方式可选:
    1、如果类型是okhttp3.MultipartBody.Part,内容将被直接使用。省略part中的名称,即 @Part MultipartBody.Part part
    2、如果类型是RequestBody,那么该值将直接与其内容类型一起使用。在注释中提供part名称(例如,@Part(“foo”)RequestBody foo)
    3、其他对象类型将通过使用转换器转换为适当的格式。 在注释中提供part名称(例如,@Part(“foo”)Image photo
@Multipart
@POST("/")
Call example(
       @Part("description") String description,
       @Part(value = "image", encoding = "8-bit") RequestBody image);

//上传一张图片
@Multipart
@POST("you methd url upload/")
Call uploadFile(@Part("avatar\\\\"; filename=\\\\"avatar.jpg") RequestBody file);

//上传图片数量不定
@Multipart
@POST("{url}")
Observable uploadFiles(
        @Path("url") String url,
        @Part("filename") String description,
        @PartMap() Map maps);

//上传图片数量不定方法2
@Multipart
@POST("{url}")
Observable uploads(
        @Path("url") String url,
        @Part("description") RequestBody description,
        @Part("filekey") MultipartBody.Part file);

4.7、PartMap

  • 作用于方法的参数,以map的方式定义Multipart请求的每个part
  • map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
  • 使用该注解定义的参数类型有以下2种方式可选:
    1、如果类型是RequestBody,那么该值将直接与其内容类型一起使用
    2、其他对象类型将通过使用转换器转换为适当的格式
@Multipart
@POST("upload/upload")
Call upload5(@FieldMap() Map params,
               @PartMap() Map files,
               @Part("file") RequestBody file,
               @PartMap Map maps)

4.8、HeaderMap

  • 作用于方法的参数,用于添加请求头
  • 以map的方式添加多个请求头,map中的key为请求头的名称,value为请求头的值,且value使用 String.valueOf() 统一转换为String类型,
  • map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常
@GET("/example1")
Call example1(@HeaderMap Map headers);

/////使用///////
Map headers = new HashMap<>();
headers.put("Accept","text/plain");
headers.put("Accept-Charset", "utf-8");
wanAndroidApi.example1(headers)
       .enqueue(new Callback() {
          @Override
          public void onResponse(Call call, Response response) { }
          @Override
          public void onFailure(Call call, Throwablet) { }
       });

4.9、Path

请求 URL 可以替换模块来动态改变,替换模块是 {}包含的字母数字字符串,替换的参数必须使用 @Path 注解的相同字符串

@GET("example5/{id}")
Call example5(@Path("id") int id);

4.10、Query

  • 作用于方法的参数
  • 用于添加查询参数,即请求参数
  • 参数值通过String.valueOf()转换为String并进行URL编码
  • 使用该注解定义的参数,参数值可以为空,为空时,忽略该值,当传入一个List或array时,为每个非空item拼接请求键值对,所有的键是统一的,如:name=张三&name=李四&name=王五
@GET("/list")
Call list(@Query("page") int page);
@GET("/list")
Call list(@Query("category") String category);
//传入一个数组
@GET("/list")
Call list(@Query("category") String... categories);
//不进行URL编码
@GET("/search")
Call list(@Query(value="foo", encoded=true) String foo);

4.11、QueryMap

复杂的查询参数

//使用默认URL编码
@GET("/search")
Call list(@QueryMap Map filters);
//不使用默认URL编码
@GET("/search")
Call list(@QueryMap(encoded=true) Map filters);

4.12、Url

  • 作用于方法参数
  • 用于添加请求的接口地址
@GET
Call example4(@Url String url);

五、Retrfit文件上传

1.创建描述网络请求的接口

 public interface FileUploadService {  
 @Multipart //表示发送form-encoded的数据(适用于 有文件 上传的场景)
 @POST("upload")
 Call upload(@Part("description") RequestBody description,
                          @Part MultipartBody.Part file);
}

2.具体使用

// 创建 RequestBody,用于封装 请求RequestBody
RequestBody requestFile =
        RequestBody.create(MediaType.parse("multipart/form-data"), file);

// MultipartBody.Part  和后端约定好Key,这里的partName是用image
MultipartBody.Part body =
        MultipartBody.Part.createFormData("image", file.getName(), requestFile);

// 添加描述
String descriptionString = "hello, 这是文件描述";
RequestBody description =
        RequestBody.create(
                MediaType.parse("multipart/form-data"), descriptionString);

// 执行请求
Call call = service.upload(description, body);
call.enqueue(new Callback() {
    @Override
    public void onResponse(Call call,
                           Response response) {
        Log.v("Upload", "success");
    }

    @Override
    public void onFailure(Call call, Throwable t) {
        Log.e("Upload error:", t.getMessage());
    }
});

    /**
     * 文字参数
     * @param value
     * @return
     */
    public static RequestBody toRequestBodyOfText (String value) {
        RequestBody body = RequestBody.create(MediaType.parse("text/plain"), value);
        return body ;
    }

    /**
     * 图片文件参数
     * @param pFile
     * @return
     */
    public static RequestBody toRequestBodyOfImage(File pFile){
        RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), pFile);
        return fileBody;
    }

    /**
     * 加工图片参数
     * @param params
     * @param file 图片路径
     * @param key 和后台约定
     * @return
     */
    public static HashMap toPutImgFileParams(HashMap params,String key,File file){
        if (params == null) {
            throw new RuntimeException("params is not allow null");
        }

        if (key == null) {
            throw new RuntimeException("file key is not allow null");
        }

        String fileName = "yyt_image"+ (Math.random()*9+1)*10000 +".png";
        params.put(""+key+"\"; filename=\""+fileName+"",toRequestBodyOfImage(file));
        return params;
    }

你可能感兴趣的:(Retrofit(一))