OKHttp3的使用和详解

一、概述

OKHttp是处理网络请求的开源框架,Andorid当前最火热的网络框架,Retrofit的底层也是OKHttp,用于替换HttpUrlConnection和Apache HttpClient(API23 6.0已经移除)。

概况起来说OKHttp是一款优秀HTTP框架,它支持GET和POST请求,支持Http的文件上传和下载,支持加载图片,支持下载文件透明的GZIP压缩,支持响应缓存避免重复的网络请求,支持使用连接池来降低响应延迟的问题。

OKHttp的优点:

1.支持HTTP2/SPDY,这使得对同一个主机发出的所有请求都可以共享相同的套接字连接。

2.如果HTTP2/SPDY不可用OkHttp,会使用连接池来复用连接以提高效率。

3.提供了对 GZIP 的默认支持来降低传输内容的大小

4.提供了对 HTTP 响应的缓存机制,可以避免不必要的网络请求

5.当网络出现问题时,OkHttp会自动重试一个主机的多个 IP 地址

二、基本的使用

2.1、配置工程

(1)首先在清单文件AndroidManifest.xml中添加网络权限

 
 
 

(2)在build.gradle文件中添加okhttp依赖:

implementation 'com.squareup.okhttp3:okhttp:4.2.0'

2.2、使用步骤:

(1)创建OkHttpClient实例

(2)通过Builder辅助类构建Request请求对象

(3)OkHttpClient实例回调请求,得到Call对象

(4)同步/异步执行请求,获取Response对象

2.3、方法详解

(1)创建OkHttpClient实例

//方式一:创建OkHttpClient实例,使用默认构造函数,创建默认配置OkHttpClient(官方建议全局只有一个实例)
OkHttpClient okHttpClient = new OkHttpClient();
 
//方式二:通过new OkHttpClient.Builder() 一步步配置一个OkHttpClient实例
OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(13, TimeUnit.SECONDS).build();
 
//方式三:如果要求使用现有的实例,可以通过newBuilder().build()方法进行构造
OkHttpClient client = okHttpClient.newBuilder().build();

使用默认构造函数创建OkHttpClient实例,创建默认配置OkHttpClient(官方建议全局只有一个实例)。也可以通过new OkHttpClient.Builder() 一步步配置一个OkHttpClient实例,如果要求使用现有的实例,可以通过newBuilder().build()方法进行构造。

(2)超时设置

我们在创建OkHttpClient实例的时候也会设置相关的属性,通过.Builder().build()的形式设置,比如超时时间设置:

    //1.构建OkHttpClient实例
    final OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(2, TimeUnit.SECONDS)//链接超时为2秒,单位为秒
            .writeTimeout(2, TimeUnit.SECONDS)//写入超时
            .readTimeout(2, TimeUnit.SECONDS)//读取超时
            .build();
 
    //2.通过Builder辅助类构建请求对象
    final Request request = new Request.Builder()
            .url("http://httpbin.org/delay/10")//URL地址
            .build();//构建
 
    //创建线程,在子线程中运行
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                //3.通过mOkHttpClient调用请求得到Call
                final Call call = okHttpClient.newCall(request);
                //4.执行同步请求,获取响应体Response对象
                Response response = call.execute();
                Log.e(TAG, "请求(超时)==" + response);
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, "请求(超时)==" + e.toString());
            }
        }
    }).start();

我这里设置了请求10秒后才请求成功,请求超时时间为2秒,读写超时时间为2秒,网络请求是耗时的请求操作,需要另外开子线程运行,抛出了超时异常,测试一下效果:

这里引出了http请求的几个重要的角色,Request是OKHttp的访问请求,Builder是访问辅助类,Response是OKHttp的请求响应

Request(请求):每一个HTTP请求中,都应该包含一个URL,一个GET或POST方法,以及Header和其他参数,还可以包含特定内容类型的数据流。

Responses(响应):响应则包含一个回复代码(200代表成功,404代表未找到),Header和定制可选的body。
另外可以根据response.code()获取返回的状态码。

OKHttp:简单来说,通过OkHttpClient可以发送一个http请求,并且可以读取该请求的响应,它是一个生产Call的工厂。收益于一个共享的响应缓存/线程池/复用的链接等因素,绝大多数应用使用一个OKHttpClient实例,便可满足整个应用的Http请求

(3)HTTP头部的设置和读取

HTTP头部的数据结构是Map>类型,也就是说,对于每个HTTP的头,可能有多个值,但是大部分的HTTP头只有一个值,只有少部分HTTP头允许多个值,至于name的取值说明请参考:HTTP头部参数说明

OKHTTP的处理方式是:

  • header(name, value)            来设置HTTP头的唯一值, 如果请求中已经存在响应的信息那么直接替换掉;
  • addHeader(name, value)     来补充新值,如果请求头中已经存在name的name-value, 那么还会继续添加,请求头中便会存在多个name相同value不同的“键值对”;
  • header(name, value)            读取唯一值或多个值的最后一个值;
  • headers(name)                     获取所有值。
    Request request = new Request.Builder()
            .url("https://api.github.com/repos/square/okhttp/issues")
            .header("User-Agent", "OkHttp Headers.java")//设置唯一值
            .addHeader("Server", "application/json; q=0.5")//设置新值
            .addHeader("Server", "application/vnd.github.v3+json")//设置新值
            .build();
 
    mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求(HTTP头)异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            Log.e(TAG, "header:Date==" + response.header("Date"));
            Log.e(TAG, "header:User-Agent==" + response.header("User-Agent"));
            Log.e(TAG, "headers:Server==" + response.headers("Server"));
            Log.e(TAG, "headers:Vary==" + response.headers("Vary"));
 
            Log.e(TAG, "Post请求(HTTP头)异步响应Success==" + response.body().string());
        }
    });

Request请求中通过.Builder().build()的形式设置url(请求地址),也可以设置该请求的头部信息,response的body有很多种输出方法,string()只是其中之一,注意是string()不是toString()。如果是下载文件就是response.body().bytes()。我们来看看打印出来的结果:

(4)GET请求(同步)

new Thread(new Runnable() {
        @Override
        public void run() {
            //通过Builder辅助类构建请求对象
            Request request = new Request.Builder()
                    .get()//get请求
                    .url("https://www.baidu.com")//请求地址
                    .build();//构建
 
            try {
                //通过mOkHttpClient调用请求得到Call
                final Call call = mOkHttpClient.newCall(request);
                //执行同步请求,获取Response对象
                Response response = call.execute();
 
                if (response.isSuccessful()) {//如果请求成功
                    String string = response.body().string();
                    Log.e(TAG, "get同步请求success==" + string);
                    //响应体的string()对于小文档来说十分方便高效,但是如果响应体太大(超过1M),应避免使用string()方法,
                    //因为它会把整个文档加载到内存中,对用超多1M的响应body,应该使用流的方式来处理。
 
                    //response.body().bytes();//字节数组类型
                    //response.body().byteStream();//字节流类型
                    //response.body().charStream();//字符流类型
 
                    printHeads(response.headers());
                } else {
                    Log.e(TAG, "get同步请求failure==");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
 
   /**
     * 打印请求头信息
     *
     * @param headers 请求头集合
     */
    private void printHeads(Headers headers) {
        if (headers == null) return;
        for (int i = 0; i < headers.size(); i++) {
            Log.e(TAG, "请求头==" + headers.name(i) + ":" + headers.value(i));
        }
    }

在请求Request中声明为GET请求,设置url请求地址,调用实例mOkHttpClient.new Call(request)调用请求,返回Call,通过同步方法call.execute()同步执行,这里需要开启一个子线程运行。需要手动通过response.isSuccessful()判断请求是否成功,同时通过响应体得到Heander打印了请求头的相关信息,看看得出的结果:

(5)GET请求(异步)

同步和异步不一样的地方是Call对象调用的方法,call.enqueue(callback)实现函数回调成功和失败两方法,异步方法可以不用开启子线程执行。

注意:同步是阻塞式的,串联执行,同步发生在当前线程内。异步是并发式的,会再次创建子线程处理耗时操作。

    //通过Builder辅助类构建Request对象,链式编程
    Request request = new Request.Builder()
            .url("https://www.baidu.com")
            .get()
            .build();
    //异步
    mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            e.printStackTrace();
            Log.e(TAG, "get异步响应失败==" + e.toString());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            //主线程中更新UI
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //TODO 在主线程中更新UI的操作
                }
            });
 
            Log.e(TAG, "get异步当前线程,线程id==" + Thread.currentThread().getId());
            String result = response.body().string();
            Log.e(TAG, "get异步响应成功==" + result);
            printHeads(response.headers());
        }
    });
 
    Log.e(TAG, "主线程,线程id==" + Thread.currentThread().getId());

打印log如下:

onFailure()onResponse()分别是在请求失败和成功时会调用的方法。这里有个要注意的地方,onFailure()onResponse()是在异步线程里执行的,所以如果你在Android把更新UI的操作写在这两个方法里面是会报错的,这个时候可以用runOnUiThread这个方法进行更新UI操作。

(6)POST提交String请求(同步)

post请求创建request和get是一样的,只是post请求需要提交一个表单,就是RequestBody。表单的格式有好多种,post请求提交参数需要构建RequestBody对象,post提交的过程需要将提交的内容封装到一个RequestBody中,同时需要设置类型MediaType,MediaType用于描述Http请求和响应体的内容类型,也就是Content-Type,通过.Builder().build()的形式设置URL(请求地址),RequestBody(参数容器)。

    //构建RequestBody对象,post提交的过程需要将提交的内容封装到一个RequestBody中
    //MediaType用于描述Http请求和响应体的内容类型,也就是Content-Type
    MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
    RequestBody requestBody = RequestBody.create("提交的内容", mediaType);
    final Request request = new Request.Builder()
            .post(requestBody)
            .url("https://api.github.com/markdown/raw")
            .build();
 
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Response response = mOkHttpClient.newCall(request).execute();
                if (response.isSuccessful()) {
                    Log.e(TAG, "Post请求String同步响应success==" + response.body().string());
                } else {
                    Log.e(TAG, "Post请求String同步响应failure==" + response.body().string());
                }
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, "Post请求String同步响应failure==" + e.getMessage());
            }
        }
    }).start();

请求结果如下:

RequestBody的数据格式都要指定Content-Type,常见的有三种:

  • application/x-www-form-urlencoded 数据是个普通表单;
  • multipart/form-data                           数据里有文件;
  • application/json                                 数据是个json。
 MediaType mediaType = MediaType.parse("image/png");
 RequestBody requestBody = RequestBody.create(xxx.png, mediaType);

改变MediaType 中的内容即可设置不同的内容类型,比如image/png表示便携式网络图形(Portable Network Graphics,PNG)是一种无损压缩的位图图形格式,支持索引、灰度、RGB三种颜色方案以及Alpha通道等特性。

(7)POST提交String请求(异步)

    RequestBody requestBody = RequestBody.create("提交内容", MediaType.parse("text/plain; charset=utf-8"));
    Request request = new Request.Builder()
            .url("https://api.github.com/markdown/raw")
            .post(requestBody)
            .build();
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求String异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String string = response.body().string();
            Log.e(TAG, "Post请求String异步响应success==" + string);
        }
    });

异步请求主要是有回调方法,和其他部分和同步差不多,我们来看看效果:

(8)POST提交键值对请求(异步)

请求参数提交键值对需要用到FormBody,FormBody继承自RequestBody,通过.add("key", "value")形式添加:

    //提交键值对需要用到FormBody,FormBody继承自RequestBody
    FormBody formBody = new FormBody.Builder()
            //添加键值对(通多Key-value的形式添加键值对参数)
            .add("key", "value")
            .build();
    final Request request = new Request.Builder()
            .post(formBody)
            .url("url")
            .build();
 
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求(键值对)异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post请求(键值对)异步响应Success==" + result);
        }
    });

(9)POST提交文件请求(异步)

post提交文件,将文件传入RequestBody中即可:

    RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), new File("text.txt"));
    Request request = new Request.Builder()
            .post(requestBody)
            .url("https://api.github.com/markdown/raw")
            .build();
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求(文件)异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post请求(文件)异步响应Success==" + result);
        }

请求效果如下:源码中有例子

(10)POST提交表单请求(异步)

 使用FormEncodingBuilder来构建和HTML标签相同效果的请求体,键值对将使用一种HTML兼容形式的URL编码来进行编码

   //使用FormEncodingBuilder来构建和HTML标签相同效果的请求体,键值对将使用一种HTML兼容形式的URL编码来进行编码
    FormBody formBody = new FormBody.Builder()
            .add("search", "Jurassic Park")
            .build();
    Request request = new Request.Builder()
            .url("https://en.wikipedia.org/w/index.php")
            .post(formBody)
            .build();
 
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求(表单)异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post请求(表单)异步响应Success==" + result);
        }
    });

(11)POST提交流请求(同步)

以流的方式Post提交请求体,请求体的内容由流写入产生,这里是流直接写入OKIO的BufferedSink。你的程序可能会使用OutputStream, 你可以使用BufferedSink.outputStream()来获取,这里需要重写RequestBody中的几个方法,将本地数据放入到Http协议的请求体中,然后发送到服务器。

    //以流的方式Post提交请求体,请求体的内容由流写入产生,这里是流直接写入OKIO的BufferedSink。
    // 你的程序可能会使用OutputStream, 你可以使用BufferedSink.outputStream()来获取
    final MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8");
    //重写RequestBody中的几个方法,将本地数据放入到Http协议的请求体中,然后发送到服务器
    final RequestBody requestBody = new RequestBody() {
        @Nullable
        @Override
        public MediaType contentType() {
            //返回内容类型
            return mediaType;
        }
 
        @Override
        public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {
            //输入数据头
            bufferedSink.writeUtf8("Numbers\\n");
            bufferedSink.writeUtf8("-------\\n");
 
            //构造数据
            for (int i = 2; i < 997; i++) {
                bufferedSink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
            }
        }
    };
 
    //构建请求
    final Request request = new Request.Builder().
            url("https://api.github.com/markdown/raw")
            .post(requestBody)
            .build();
        //开启线程
        new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Response response = mOkHttpClient.newCall(request).execute();
                if (response.isSuccessful()) {
                    String result = response.body().toString();
                    Log.e(TAG, "Post请求(流)异步响应Success==" + result);
                } else {
                    Log.e(TAG, "Post请求(流)异步响应failure==" + response);
                    throw new IOException("Unexpected code " + response);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
 
    private String factor(int n) {
        for (int i = 2; i < n; i++) {
            int x = n / i;
            if (x * i == n) {
                return factor(x) + "x" + i;
            }
        }
        return Integer.toString(n);
    }

请求结果如下:

(12)POST提交分块请求(异步)

MultipartBuilder可以构建复杂的请求体,与HTML文件上传形式兼容,多块请求头中的每个请求体都是一个亲求体,可以定义自己的请求体,这些请求体可以用来描述这块请求,例如他的Content-Disposition,如果Content-Length和Content-Type可用的话,他们会被自动添加到请求头中。

    MediaType mediaType = MediaType.parse("image/png");
    String IMGUR_CLIENT_ID = "...";
    //构建body
    MultipartBody multipartBody = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("title", "Square Logo")
            .addFormDataPart("image", "logo-square.png", RequestBody.create(mediaType,
                    new File("website/static/logo-square.png")))
            .build();
    
    //构建请求
    Request request = new Request.Builder()
            .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
            .url("https://api.imgur.com/3/image")
            .post(multipartBody)
            .build();
        
    //执行请求
        mOkHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            Log.e(TAG, "Post请求(分块)异步响应failure==" + e.getMessage());
        }
 
        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            String result = response.body().string();
            Log.e(TAG, "Post请求(分块)异步响应Success==" + result);
        }
    });

请求结果如下:

如果你上传一个文件不是一张图片,但是MediaType.parse("image/png")里的"image/png"不知道该填什么,可以参考下这个页面。

源码地址


相关文章:

Retrofit2详解和简单使用

  • Retrofit2的介绍和简单使用

OKHttp3的使用和详解

  • OKHttp3的用法介绍和解析

OKHttp3源码详解

  • 从源码角度解释OKHttp3的关键流程和重要操作

转载:https://blog.csdn.net/m0_37796683/article/details/101029208

你可能感兴趣的:(Android,okhttp)