Retrofit2 与 RxJava 用法解析

7,015 阅读4分钟
原文链接: www.cxbiao.com

Retrofit2是square公司出品的一个网络请求库,目前非常流行,特别适合于rest请求。网上也有不少介绍该库的文章,但别人的终究是别人的,还需要转化为自己的才行。正所谓“纸上得来终觉浅,绝知此事要躬行”,本着学习的态度笔者对retroift2的用法进行了下列研究,主要包括以下几个方面

  • get请求
  • post请求(包括key/value,以及body)
  • 文件上传(进度监听)
  • 文件下载
  • 与RxJava整合

单独使用Retrofit2

引入类库

compile 'com.squareup.retrofit2:retrofit:2.0.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'

构造http接口类

Retrofit2可以根据一个服务接口类,利用jdk动态代理生成它的相应实现。

retrofit=new Retrofit.Builder()
              .baseUrl(BASE_URL)
              .addConverterFactory(GsonConverterFactory.create())
              .build();
 myService=retrofit.create(MyService.class);

生成了代理类之后,就可以进行相应请求了

Get请求

@GET("rest/findUserForGet")
Call findUserForGet(@Query("id") int id, @Query("username") String username,@Query("address") String address);
Call userCall=myService.findUserForGet(12,"张明明","北京海淀区");
 userCall.enqueue(new Callback() {
           @Override
           public void onResponse(Call call, Response response) {
               //主线程
               Log.e(TAG,Thread.currentThread().getName());
               Log.e(TAG,response.body().toString());

           }

           @Override
           public void onFailure(Call call, Throwable t) {
               Log.e(TAG,t.getMessage());
           }
   });

需要注意Query注解不能丢,即使形参和请求的key相同也要加上,否则报错;另外回调函数发生在主线程,可以进行UI相关的操作

Post请求(key/value)

@FormUrlEncoded
 @POST("rest/findUserForPost")
 Call findUserForPost(@Field("id") int id, @Field("username") String username,@Field("address") String address);
Call userCall=myService.findUserForPost(9,"陈玄功","恶人谷");
userCall.enqueue(new Callback() {
          @Override
          public void onResponse(Call call, Response response) {
              Log.e(TAG,getResponsString(response.body()));

          }

          @Override
          public void onFailure(Call call, Throwable t) {
              Log.e(TAG,t.getMessage());
          }
  });

Post请求(body体)

 
@POST("rest/postBodyJson")
Call postBodyJson(@Body User user);
User user=new User();
user.setId(2);
user.setUsername("李明");
user.setBirthday("1995-09-06 09-09-08");
user.setSex("1");
Call userCall=myService.postBodyJson(user);
userCall.enqueue(new Callback() {
    @Override
    public void onResponse(Call call, Response response) {
        Log.e(TAG,response.body().toString());
    }

    @Override
    public void onFailure(Call call, Throwable t) {
        Log.e(TAG,t.getMessage());
    }
});

此种方式会默认加上Content-Type: application/json; charset=UTF-8的请求头,即以JSON格式请求,再以JSON格式响应。

单个文件上传


@Multipart
@POST("rest/upload")
Call upload(@Part("username") RequestBody username,@Part("address") RequestBody address,
                          @Part MultipartBody.Part file);

File file=new File(Environment.getExternalStorageDirectory(),"测试01.jpg");

 //普通key/value
 RequestBody username =
         RequestBody.create(
                 MediaType.parse("multipart/form-data"), "jim");

 RequestBody address =
         RequestBody.create(
                 MediaType.parse("multipart/form-data"), "天津市");

 //file
 RequestBody requestFile =
         RequestBody.create(MediaType.parse("multipart/form-data"), file);

 
 //包装RequestBody,在其内部实现上传进度监听
 CountingRequestBody countingRequestBody=new CountingRequestBody(requestFile, new CountingRequestBody.Listener() {
     @Override
     public void onRequestProgress(long bytesWritten, long contentLength) {
         Log.e(TAG,contentLength+":"+bytesWritten);
     }
 });

      
 MultipartBody.Part body =
         MultipartBody.Part.createFormData("file", file.getName(), countingRequestBody);


 Call userCall=myService.upload(username, address, body);
 userCall.enqueue(new Callback() {
     @Override
     public void onResponse(Call call, Response response) {
         Log.e(TAG, getResponsString(response.body()));

     }

     @Override
     public void onFailure(Call call, Throwable t) {
         Log.e(TAG, t.getMessage());
     }
 });

多文件上传


@Multipart
@POST("rest/upload")
Call uploads(@PartMap Map params);
//必须使用LinkedHashMap,保证文件按顺序上传
Map params=new LinkedHashMap<>();
  File file1=new File(Environment.getExternalStorageDirectory(),"测试01.jpg");
  RequestBody filebody1 =RequestBody.create(MediaType.parse("multipart/form-data"), file1);
  //记录文件上传进度
  CountingRequestBody countingRequestBody1=new CountingRequestBody(filebody1, new CountingRequestBody.Listener() {
      @Override
      public void onRequestProgress(long bytesWritten, long contentLength) {
          Log.e(TAG,"file1:"+contentLength+":"+bytesWritten);
      }
  });
  //file代表服务器接收到的key,file1.getName()代表文件名
  params.put("file\";filename=\""+file1.getName(),countingRequestBody1);


  File file2=new File(Environment.getExternalStorageDirectory(),"girl.jpg");
  RequestBody filebody2 =RequestBody.create(MediaType.parse("multipart/form-data"), file2);
  CountingRequestBody countingRequestBody2=new CountingRequestBody(filebody2, new CountingRequestBody.Listener() {
      @Override
      public void onRequestProgress(long bytesWritten, long contentLength) {
          Log.e(TAG,"file2:"+contentLength+":"+bytesWritten);
      }
  });
  params.put("file\";filename=\""+file2.getName(),countingRequestBody2);


  File file3=new File(Environment.getExternalStorageDirectory(),"测试02.jpg");
  RequestBody filebody3 =RequestBody.create(MediaType.parse("multipart/form-data"), file3);
  CountingRequestBody countingRequestBody3=new CountingRequestBody(filebody3, new CountingRequestBody.Listener() {
      @Override
      public void onRequestProgress(long bytesWritten, long contentLength) {
          Log.e(TAG,"file3:"+contentLength+":"+bytesWritten);
      }
  });
  params.put("file\";filename=\""+file3.getName(),countingRequestBody3);

  //普通key/value
  params.put("username",   RequestBody.create(
          MediaType.parse("multipart/form-data"), "jim"));
  params.put("address", RequestBody.create(
          MediaType.parse("multipart/form-data"), "天津市"));

  Call userCall=myService.uploads(params);
  userCall.enqueue(new Callback() {
      @Override
      public void onResponse(Call call, Response response) {
           Log.e(TAG, getResponsString(response.body()));

      }

      @Override
      public void onFailure(Call call, Throwable t) {
          Log.e(TAG, t.getMessage());
      }
  });

文件下载

@Streaming
@GET("image/{filename}")
Call downFile(@Path("filename") String fileName);
Call userCall=myService.downFile(fname);
       userCall.enqueue(new Callback() {
           @Override
           public void onResponse(Call call, Response response) {
               try {

                   String fileName=Environment.getExternalStorageDirectory()+"/"+fname;
                   FileOutputStream fos=new FileOutputStream(fileName);
                   InputStream is=response.body().byteStream();

                   byte[] buf=new byte[1024];
                   int len;
                   while ((len=is.read(buf))!=-1){
                       fos.write(buf,0,len);
                   }
                   is.close();
                   fos.close();

               }catch (Exception ex){
                   Log.e(TAG,ex.getMessage());
               }
               Log.e(TAG,"success");

           }

           @Override
           public void onFailure(Call call, Throwable t) {
               Log.e(TAG,t.getMessage());
           }
       });

开启OKHttp的日志拦截

Retrofit2底层还是使用的OKHttp,可以使用其相关的一些特性,比如开启日志拦截,此时就不能使用Retrofit2默认的OKHttp实例,需要自己单独构造,完整代码如下:


HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);

OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
      
httpClient.addInterceptor(logging);  

retrofit=new Retrofit.Builder()
       .baseUrl(BASE_URL)
       .addConverterFactory(GsonConverterFactory.create())
       .client(httpClient.build())
       .build();

开启日志后,会记录request和response的相关信息


05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: --> POST http://192.168.1.104:8080/mobile/rest/postBodyJson http/1.1
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: Content-Type: application/json; charset=UTF-8
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: Content-Length: 71
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: {"birthday":"1995-09-06 09-09-08","id":2,"sex":"1","username":"李明"}
05-14 03:00:42.128 5212-25519/com.bryan D/OkHttp: --> END POST (71-byte body)
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: <-- 200 OK http://192.168.1.104:8080/mobile/rest/postBodyJson (79ms)
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Server: Apache-Coyote/1.1
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Access-Control-Allow-Origin: *
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Content-Type: application/json;charset=UTF-8
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Transfer-Encoding: chunked
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: Date: Sat, 14 May 2016 07:00:42 GMT
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: OkHttp-Sent-Millis: 1463209242134
05-14 03:00:42.207 5212-25519/com.bryan D/OkHttp: OkHttp-Received-Millis: 1463209242206
05-14 03:00:42.208 5212-25519/com.bryan D/OkHttp: {"id":2,"username":"李明","sex":"1","birthday":"1995-09-06","address":null}
05-14 03:00:42.208 5212-25519/com.bryan D/OkHttp: <-- END HTTP (77-byte body)

Retrofit2与RxJava整合

引入类库

compile 'io.reactivex:rxandroid:1.1.0'
compile 'com.squareup.retrofit2:retrofit:2.0.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'

构造http接口类

Retrofit2可以根据一个服务接口类,利用jdk动态代理生成它的相应实现。

retrofit=new Retrofit.Builder()
              .baseUrl(BASE_URL)
              .addConverterFactory(GsonConverterFactory.create())
              .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
              .client(httpClient.build())
              .build();
 myService=retrofit.create(MyRxService.class);

Get请求


@GET("rest/findUserForGet")
  Observable findUserForGet(@Query("id") int id, @Query("username") String username,@Query("address") String address);
myService.findUserForGet(12,"张明明","北京海淀区")
         .subscribeOn(Schedulers.io())
         .observeOn(AndroidSchedulers.mainThread())
         .subscribe(new Subscriber() {

             @Override
             public void onStart() {
                 super.onStart();
                 Log.e(TAG,"onStart");
             }

             @Override
             public void onCompleted() {
                 Log.e(TAG,"onCompleted");
             }

             @Override
             public void onError(Throwable e) {
                 Log.e(TAG,e.getMessage());
             }

             @Override
             public void onNext(User user) {
                 Log.e(TAG,user.toString());
             }
         });

Post请求(key/value)

@FormUrlEncoded
   @POST("rest/findUserForPost")
   Observable findUserForPost(@Field("id") int id, @Field("username") String username, @Field("address") String address);

实现和Get类似

多文件上传

@Multipart
    @POST("rest/upload")
    Observable uploads(@PartMap Map params);
//params构造部分和单独使用Retrofit2的构造相同
myService.uploads(params)
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe(new Subscriber() {

                   @Override
                   public void onStart() {
                       super.onStart();
                       Log.e(TAG, "onStart");
                   }


                   @Override
                   public void onCompleted() {
                       Log.e(TAG, "onCompleted");
                   }

                   @Override
                   public void onError(Throwable e) {
                       Log.e(TAG, e.getMessage());
                   }

                   @Override
                   public void onNext(ResponseBody body) {
                       Log.e(TAG, getResponsString(body));
                   }
               });

文件下载

@Streaming
@GET("image/{filename}")
Observable downFile(@Path("filename") String fileName);

总结

无论是单独使用Retrofit2还是整合RxJava一起使用,请求体的构造部分并没有多大变化,主要区别是RxJava支持链式写法,可以对response作更为复杂的处理。现实业务中大多数也是两者结合起来使用。

Github Demo

坚持原创技术分享,您的支持将鼓励我继续创作!

查看图片

支付宝打赏