Rx系列<第三十一篇>:Android开发必须了解的Retrofit

Retrofit是目前主流网络框架之一,往往结合OKHttp一起使用。Retrofit提供网络请求接口,OKhttp专门负责请求网络,因为现在项目主流框架是RxJava+Okhttp+Retrofit,所以我将Retrofit的讲解放在了Rx系列专题。

(1)依赖
//OKHttp依赖
implementation 'com.squareup.okhttp3:okhttp:3.12.2'
//Retrofit依赖
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
//GSON依赖
implementation 'com.google.code.gson:gson:2.8.5'
//GSON转换器依赖
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'

依赖这边有个坑,retrofit的版本要和converter的版本保持一致,否则Json和对象之间的转换可能会报错。

(2)一般用法
  • 声明接口
public interface SearchFileAPIService {

    @FormUrlEncoded
    @POST("file/search?from=upload")
    Call<ResponseBody> fileSearch(@Field("filetype") String filetype, @Field("filename") String filename);

}
  • 请求实现

      Retrofit retrofit = new Retrofit.Builder().baseUrl("http://xxxxxx:82/oa/").build();
      SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
      Call<ResponseBody> call = searchFileAPIService.fileSearch("-1", "1");
      call.enqueue(new Callback<ResponseBody>() {
          @Override
          public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
              if(response != null && response.isSuccessful()){
                  try {
                      Log.d("aaa", response.body().string());
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
    
          @Override
          public void onFailure(Call call, Throwable t) {
              Log.d("aaa", t.getMessage());
          }
      });
    

我们先来看一下retrofit的基本代码,以上代码正是retrofit的post请求。
之所以在文章一开头就贴出基本代码是因为需要说明以下几点:

(1) 本文重点介绍Retrofit,与RxJava结合使用将在下节介绍;
(2) retrofit用于接口的封装,结合OKHttp可以优雅的实现网络请求框架;
(3) retrofit第一步总是声明接口,以上代码声明了SearchFileAPIService接口,我们可以在SearchFileAPIService声明多个方法,以上代码只封装了fileSearch方法,retrofit提供了多种注解,@FormUrlEncoded@POST@Field都是retrofit的注解,当然还有其它注解,文章后面会讲解;
(4) baseUrl有两种,一种不带路径,比如:http://xxxxxx:82,另一种带路径,比如: http://xxxxxx:82/oa/,这里需要重点注意:不带路径的baseUrl结尾可以不带有'/',但是带路径的baseUrl接口必须以'/'结尾,否则会报错,如图:

图片.png

(5) Call后面必须添加泛型,否则会报错,泛型类型不允许是StringInteger或者其他基本数据类型,这里用将ResponseBody作为泛型类型即可,ResponseBody是OKHttp中的类,所以Retrofit有时候需要结合OKHttp使用。

其实需要注意的不止是以上5点,还有很多需要注意的地方,如果过早的指出那就更加不能理解了。

以上声明的5点只是打一个预防针,文章后面会重新说明。

(3)Retrofit的请求方式注解
  • GET请求注解:@GET

GET请求是请求方式的其中一种,下面我来封装一个最简单的GET请求接口

public interface SearchFileAPIService {
    @GET("/")
    Call<ResponseBody> getBaidu();
}


    Retrofit retrofit = new Retrofit.Builder().baseUrl("http://www.baidu.com")
            .build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    Call<ResponseBody> call = searchFileAPIService.getBaidu();
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                try {
                    Log.d("aaa", "onResponse:"+response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.d("aaa", "onFailure:"+t.getMessage());
        }
    });

请求结果如下:

图片.png
  • POST请求注解:@POST

POST请求是请求方式的一种,POST请求上面已经给出例子了。

  • HTTP请求:@HTTP

@HTTP可以使用任何请求,现在就以GET请求为例

public interface SearchFileAPIService {

    @HTTP(method = "GET", path = "user_user", hasBody = false)
    Call<ResponseBody> getBaidu3();

}


    Retrofit retrofit = new Retrofit.Builder().baseUrl("http://www.baidu.com")
            .build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    Call<ResponseBody> call = searchFileAPIService.getBaidu3();
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                try {
                    Log.d("aaa", "onResponse:"+response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.d("aaa", "onFailure:"+t.getMessage());
        }
    });

以上代码请求网络需要跳过证书(怎么跳过证书文章后面会讲到)

请求头报文和上面一样:

图片.png
  • DELETE请求注解:@DELETE

DELETE请求相对GET和POST请求而言用的比较少

public interface SearchFileAPIService {

    @HTTP(method = "DELETE", path = "delete", hasBody = true)
    Call<ResponseBody> deleteSomething();

}

或者

public interface SearchFileAPIService {

    @DELETE("/{delete}")
    Call<ResponseBody> deleteSomething(@Path("delete") String delete);

}


    Retrofit retrofit = new Retrofit.Builder().baseUrl("http://www.baidu.com")
            .build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    Call<ResponseBody> call = searchFileAPIService.deleteSomething("delete_delete");
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                try {
                    Log.d("aaa", "onResponse:"+response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else{
                Log.d("aaa", "onResponse:"+response.message());
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.d("aaa", "onFailure:"+t.getMessage());
        }
    });

以上代码请求网络需要跳过证书(怎么跳过证书文章后面会讲到)

目前当前接口调试是不通的,大家也不要纠结为什么没用了,到目前为止我也没用过这个请求,以后用到的话可以补充。不过查询资料之后得到一句解释:

DELETE请求顾名思义,就是执行相应的删除操作,配合数据库进行相应的删除动作。

  • PUT请求:@PUT

用于更新资源

  • PATCH请求:@PATCH

该请求是对put请求的补充,用于更新局部资源

  • OPTIONS请求:@OPTIONS

(以后补充)

  • head请求:@HEAD

(以后补充)

(4)Retrofit的请求参数注解
  • url 替换:@Path
    我们就基于以上请求百度页面的例子添加@Path,代码如下:
public interface SearchFileAPIService {
    @GET("/{user}")
    Call<ResponseBody> getBaidu2(@Path("user") String user);
}


    Retrofit retrofit = new Retrofit.Builder().baseUrl("http://www.baidu.com")
            .build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    Call<ResponseBody> call = searchFileAPIService.getBaidu2("user_user");
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                try {
                    Log.d("aaa", "onResponse:"+response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.d("aaa", "onFailure:"+t.getMessage());
        }
    });

以上代码请求网络需要跳过证书(怎么跳过证书文章后面会讲到)

请求头如下:

图片.png

通过以上代码演示,@Path的作用已经显而易见了,@GET("/{user}")中的user是一个参数,参数变量用{}修饰。

  • 表单字段:@Filed@FiledMap

@Filed@FiledMap主要用于POST请求表单数据的声明,我们来看一下接口的定义

public interface SearchFileAPIService {

    @FormUrlEncoded
    @POST("file/search?from=upload")
    Call<ResponseBody> fileSearch(@Field("filetype") String filetype, @Field("filename") String filename);
    @FormUrlEncoded
    @POST("file/search?from=upload")
    Call<ResponseBody> fileSearch2(@FieldMap Map<String, String> map)

}

下面贴一下完整的使用

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://xxxxxxxx:82/oa/").build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    Call<ResponseBody> call = searchFileAPIService.fileSearch("-1", "1");
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                try {
                    Log.d("aaa", response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onFailure(Call call, Throwable t) {
            Log.d("aaa", t.getMessage());
        }
    });


    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http:/xxxxxxxxx:82/oa/").build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    Map<String, String> map = new HashMap();
    map.put("filetype", "-1");
    map.put("filename", "1");
    Call<ResponseBody> call = searchFileAPIService.fileSearch2(map);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                try {
                    Log.d("aaa", response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onFailure(Call call, Throwable t) {
            Log.d("aaa", t.getMessage());
        }
    });
  • POST请求非表单数据:@Body

在post请求时,我们可以传递键值对,也可以传递键值对的map集合,使用@Body我们可以传递一个对象,利用json转换器将对象转成json,代码如下:

public interface SearchFileAPIService {

    @POST("file/search?from=upload")
    Call<ResponseBody> fileSearch3(@Body FileBean fileBean);

}


    Retrofit retrofit = new Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create(new Gson()))
            .baseUrl("http://xxxxxxxxxx:82/oa/").build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    FileBean fileBean = new FileBean();
    fileBean.setFiletype("-1");
    fileBean.setFilename("1");
    Call<ResponseBody> call = searchFileAPIService.fileSearch3(fileBean);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                try {
                    Log.d("aaa", response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onFailure(Call call, Throwable t) {
            Log.d("aaa", t.getMessage());
        }
    });

这里使用了addConverterFactory,这是添加转换器,文章后面会讲到。

  • 指定请求路径:@Url

一般情况下,请求路径是在@POST的参数中声明的,一旦使用@Url来指定请求路径,@POST就不能传递传输。代码如下:

    public interface SearchFileAPIService {
    
            @POST()
            Call<ResponseBody> fileSearch4(@Url String url, @Body FileBean fileBean);
    
    }


    Retrofit retrofit = new Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create(new Gson()))
            .baseUrl("http://xin-ikea.cn:82/oa/").build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    FileBean fileBean = new FileBean();
    fileBean.setFiletype("-1");
    fileBean.setFilename("1");
    Call<ResponseBody> call = searchFileAPIService.fileSearch4("file/search?from=upload", fileBean);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                try {
                    Log.d("aaa", response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onFailure(Call call, Throwable t) {
            Log.d("aaa", t.getMessage());
        }
    });
  • Get中指定参数: @Query@QueryMap

我定义了一个接口

    @FormUrlEncoded
    @POST("file/search?from=upload")
    Call<ResponseBody> fileSearch5(@Query("filetype") String filetype, @Query("filename") String filename, @Field("filetype") String filetype1, @Field("filename") String filename1);

抓到的报文如下:

图片.png

我们发现在原本URL的基础上拼接了filetype和filename两个参数,所以 @Query@QueryMap其实主要用于GET请求。接口如下:

    @GET("/")
    Call<ResponseBody> getJianshu1(@Query("name") String name, @Query("age") int age);

    @GET("/")
    Call<ResponseBody> getJianshu2(@QueryMap Map<String, String> map);
  • @Part@PartMap

@Part:用于表单字段,Part和PartMap与Multipart注解结合使用,适合文件上传的情况

@PartMap:用于表单字段,默认接受的类型是Map<String,REquestBody>,可用于实现多文件上传

这两种注解将在文章下面和Multipart注解一起讲解。

(5)Retrofit响应格式注解
  • Form表单数据:@FormUrlEncoded

@FormUrlEncoded@Field@FieldMap一起配合使用,用于POST请求传递表单数据。

    public interface SearchFileAPIService {

            @FormUrlEncoded
            @POST("file/search?from=upload")
            Call<ResponseBody> fileSearch(@Field("filetype") String filetype, @Field("filename") String filename);
            @FormUrlEncoded
            @POST("file/search?from=upload")
            Call<ResponseBody> fileSearch2(@FieldMap Map<String, String> map);

    }
  • 发送multipart数据:@Multipart

一般和@Part@PartMap一起配合使用,支持单文件上传,多文件上传,也支持表单和文件一起上传。

代码如下:

    public interface SearchFileAPIService {

            @Multipart
            @POST("FileUpload/FileUploadServlet")
            Call<ResponseBody> uploadFlie2(@Part MultipartBody.Part file);
    }


    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://192.168.0.105:8080").build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);

    File file = new File(Environment.getExternalStorageDirectory() + File.separator + "iam.jpg");

    // 创建 RequestBody
    //application/otcet-stream
    RequestBody requestFile = RequestBody.create(MediaType.parse("application/otcet-stream"), file);

    //新建Part
    MultipartBody.Part body = MultipartBody.Part.createFormData("photo1", file.getName(), requestFile);

    // 添加描述
    String descriptionString = "hello, 这是文件描述";
    RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);
    Call<ResponseBody> call = searchFileAPIService.uploadFlie2(body);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                try {
                    Log.d("aaa", response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onFailure(Call call, Throwable t) {
            Log.d("aaa", t.getMessage());
        }
    });

我们查看一下报文:

图片.png
  • 响应用字节流的形式返回:@Streaming

该注解可以用于下载文件。
代码如下:

    public interface SearchFileAPIService {
            @Streaming
            @GET("/1.apk")
            Call<ResponseBody> downloadFile();
    }


    Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.0.105:8080").build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    Call<ResponseBody> call = searchFileAPIService.downloadFile();
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                boolean writtenToDisk = writeResponseBodyToDisk(response.body());
                Log.d("aaa", "下载成功:"+writtenToDisk);
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.d("aaa", "下载失败");
        }
    });


//写入到磁盘根目录
private boolean writeResponseBodyToDisk(ResponseBody body) {
    try {
        File futureStudioIconFile = new File(Environment.getExternalStorageDirectory() + File.separator + "myapk.apk");
        InputStream inputStream = null;
        OutputStream outputStream = null;

        try {
            byte[] fileReader = new byte[4096];

            final long fileSize = body.contentLength();
            long fileSizeDownloaded = 0;

            inputStream = body.byteStream();
            outputStream = new FileOutputStream(futureStudioIconFile);
            while (true) {
                int read = inputStream.read(fileReader);

                if (read == -1) {
                    break;
                }

                outputStream.write(fileReader, 0, read);

                fileSizeDownloaded += read;


                final long finalFileSizeDownloaded = fileSizeDownloaded;
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        bt_1.setText("file download: " + finalFileSizeDownloaded + " of " + fileSize);
                    }
                });
            }

            outputStream.flush();

            return true;
        } catch (IOException e) {
            return false;
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }

            if (outputStream != null) {
                outputStream.close();
            }
        }
    } catch (IOException e) {
        return false;
    }
}

报文如下:

图片.png
(6)Retrofit的请求头注解
  • @Headers

用于添加固定请求头,可以同时添加多个。通过该注解添加的请求头不会相互覆盖,而是共同存在。

    @Headers({"User-Agent:okhttp/3.14.1","User-Agent:okhttp/3.14.2","Content-Type: text/html"})
    @GET("/")
    Call<ResponseBody> getJianshu2();

请求报文如下:

图片.png
  • @Header

作为方法的参数传入,用于添加不固定值的Header,该注解会更新已有的请求头

    @GET("/")
    Call<ResponseBody> getJianshu2(@Header("Content-Type") String contentType);


    Retrofit retrofit = new Retrofit.Builder().baseUrl("http://www.jianshu.com").build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    Call<ResponseBody> call = searchFileAPIService.getJianshu2("text/html");
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                Log.d("aaa", "11111111111111111");
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.d("aaa", "2222222222222222");
        }
    });

报文如下:

图片.png
(7)怎么跳出SSL证书校验?

有时候,我们想要请求https开头的链接,有些服务器会做限制,不让别人访问自己的服务器,这个时候我们需要跳出证书校验了,Retrofit的client方法可以重新封装OkHttpClient,使hostnameVerifier返回true即可跳过证书校验。

就以访问“https://www.jianshu.com”为例,代码如下:

public class SslClient {

    private SslClient(){}

    static class SingleHolder{
        public static SslClient instance = new SslClient();
    }

    public static SslClient getInstance(){
        return SingleHolder.instance;
    }

    public OkHttpClient getOkHttpClient(){
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        X509TrustManager x509TrustManager = new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };

        TrustManager[] trustManager = new TrustManager[]{
                x509TrustManager
        };
        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, trustManager, new SecureRandom());
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
            builder.sslSocketFactory(sslSocketFactory,x509TrustManager);
            builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return hostname.contains("www.jianshu.com");
                }
            });

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return builder.build();
    }

}


    public interface SearchFileAPIService {

            @GET("/")
            Call<ResponseBody> getJianshu();

    }


    Retrofit retrofit = new Retrofit.Builder().baseUrl("https://www.jianshu.com")
            .client(SslClient.getInstance().getOkHttpClient())
            .build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    Call<ResponseBody> call = searchFileAPIService.getJianshu();
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                try {
                    Log.d("aaa", "onResponse:"+response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else{
                Log.d("aaa", "onResponse:"+response.message());
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            Log.d("aaa", "onFailure:"+t.getMessage());
        }
    });
(8)转换器

我们先来搜索一下有多少转换器依赖吧

图片.png

这里就以gson转换器为例,其它转换器不做讲解。

一般情况下,我们的post请求代码是这样写的

    Retrofit retrofit = new Retrofit.Builder().baseUrl("http://123.124.175.239:7001").build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    Call<ResponseBody> call = searchFileAPIService.getCarList("868256021322879", "862679", "c907d5df8b26550eaf2841ed9bcf51bb44b9ae1b", "d3974829a20747579c90e2004a5ed1c5");
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if(response != null && response.isSuccessful()){
                try {
                    Log.d("aaa", response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {

        }
    });

当我们获取返回结果时,发现返回结果时一个json或者json数组,这个时候我们可以利用转换器将json直接转成对象,改造之后的代码如下:

    public interface SearchFileAPIService {

            @FormUrlEncoded
            @POST("/xxx/interface/carList")
            Call<ArrayList<Car>> getCarList(@Field("IMEI") String IMEI, @Field("appid") String appid, @Field("appkey") String appkey, @Field("userId") String userId);

    }


    Retrofit retrofit = new Retrofit.Builder().baseUrl("http://123.124.175.239:7001")
            .addConverterFactory(GsonConverterFactory.create(new Gson()))
            .build();
    SearchFileAPIService searchFileAPIService = retrofit.create(SearchFileAPIService.class);
    Call<ArrayList<Car>> call = searchFileAPIService.getCarList("868256021322879", "862679", "c907d5df8b26550eaf2841ed9bcf51bb44b9ae1b", "d3974829a20747579c90e2004a5ed1c5");
    call.enqueue(new Callback<ArrayList<Car>>() {
        @Override
        public void onResponse(Call<ArrayList<Car>> call, Response<ArrayList<Car>> response) {
            if(response != null && response.isSuccessful()){
                ArrayList<Car> carlist = response.body();
                for(Car car : carlist){
                    Log.d("aaa", car.getCarNumber());
                }
            }
        }

        @Override
        public void onFailure(Call<ArrayList<Car>> call, Throwable t) {

        }
    });

获取的结果是一个数组:

图片.png
(9)日志拦截器

当我们网络请求时,要想看到报文,有时候必须依赖于抓包工具,本人常用的抓包工具是Fiddler,除了抓包工具之外,OkHttp提供了日志拦截器,可以打印报文日志。

通过下图代码,可以添加拦截求

图片.png

创建日志拦截器有两种方式:

  • 自定义的方式
/**
 * 创建日志拦截器
 *
 * @return
 */
public class LogInterceptor implements Interceptor {
    @Override
    public okhttp3.Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Log.d("bbb", "请求数据:");
        Log.d("bbb", "请求URL:"+String.valueOf(request.url()));
        Log.d("bbb", "请求头:"+String.valueOf(request.headers()));
        Log.d("bbb", "请求体:"+request.toString());

        okhttp3.Response response = chain.proceed(request);

        Log.d("bbb", "响应数据:");
        Log.d("bbb", "返回码:"+String.valueOf(response.code()));
        Log.d("bbb", "响应头:"+String.valueOf(response.headers()));
        Log.d("bbb", "响应体:"+String.valueOf(response.body().string()));

        return response;
    }
}

打印日志如下:

图片.png
  • 使用日志拦截器依赖
implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'


/**
 * 创建日志拦截器
 *
 * @return
 */
public static HttpLoggingInterceptor getHttpLoggingInterceptor() {
    HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(
            new HttpLoggingInterceptor.Logger() {

                @Override
                public void log(String message) {
                    Log.e("bbb", "log = " + message);
                }

            });
    loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
    return loggingInterceptor;
}

捕获到的日志如下:

图片.png
(10)自定义CookieJar
            OkHttpClient client = new OkHttpClient.Builder()
                    .addInterceptor(getHttpLoggingInterceptor())
                    .cookieJar(new CookieJar() {
                        final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();

                        @Override
                        public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
                            cookieStore.put(url, cookies);//保存cookie
                            //也可以使用SP保存
                        }

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

推荐阅读更多精彩内容