网络请求框架OkHttp3全解系列 - (一)OkHttp的基本使用

预备知识: HTTP协议详解 HTTP请求报文和响应报文php

OkHttp3是由square公司开发,Android中公认最好用的网络请求框架,在接口封装上作的简单易用,GitHub地址css

它有如下默认特性:html

  • 支持HTTP/2,容许全部同一个主机地址的请求共享同一个socket链接
  • 使用链接池减小请求延时
  • 透明的GZIP压缩减小响应数据的大小
  • 缓存响应内容,避免一些彻底重复的请求

当网络出现问题的时候OkHttp 会自动恢复通常的链接问题,若是你的服务有多个IP地址,当第一个IP请求失败时,OkHttp会交替尝试你配置的其余IP。java

1、引入

gradle引入依赖便可。android

implementation 'com.squareup.okhttp3:okhttp:3.14.7'
    implementation 'com.squareup.okio:okio:1.17.5'
复制代码

3.14.x版本及之前的版本,采用Java语言编写,4.0.0之后采用kotlin语言;本系列文章中源码引自3.14.x版本,以Java语言讲解。git

其中Okio库 是对Java.io和java.nio的补充,以便可以更加方便,快速的访问、存储和处理你的数据。OkHttp的底层使用该库做为支持。github

另外,别忘了申请网络请求权限,若是还使用网络请求的缓存功能,那么还要申请读写外存的权限:json

<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
复制代码

2、使用方式

基本使用步骤以下api

  • 构建客户端对象OkHttpClient
  • 构建请求Request
  • 生成Call对象
  • Call发起请求(同步/异步)

下面跟着具体使用实例,详细介绍。缓存

2.1 get请求

以百度主页为例,进行Get请求:

OkHttpClient httpClient = new OkHttpClient();

        String url = "https://www.baidu.com/";
        Request getRequest = new Request.Builder()
                .url(url)
                .get()
                .build();

        Call call = httpClient.newCall(getRequest);

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //同步请求,要放到子线程执行
                    Response response = call.execute();
                    Log.i(TAG, "okHttpGet run: response:"+ response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
复制代码

首先,建立了OkHttpClient实例,接着用Request.Builder构建了Request实例并传入了百度主页的url,而后httpClient.newCall方法传入Request实例生成call,最后在子线程调用call.execute()执行请求得到结果response。

因此,使用OkHttp进行get请求,是比较简单的,只要在构建Request实例时更换url就能够了。

有个问题,你可能注意到了,这里是放在子线程执行请求的,这是由于call.execute()是同步方法。想要在主线程直接使用而不用手动建立子线程能够嘛?固然能够,使用call.enqueue(callback)便可:

call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "okHttpGet enqueue: onResponse:"+ response.body().string());
            }
        });
复制代码

call.enqueue会异步执行,须要注意的是,两个回调方法onFailure、onResponse是执行在子线程的,因此若是想要执行UI操做,须要使用Handler切换到UI线程。

另外,注意每个Call只能执行一次(缘由会在下篇流程分析中说明)。

执行后结果打印以下:

2020-05-04 21:52:56.446 32681-3631/com.hfy.androidlearning I/OkHttpTestActivity: okHttpGet run: response:<!DOCTYPE html>
    <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登陆</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登陆</a>');
                    </script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

复制代码

可见百度首页的get请求成功响应了。

2.2 post请求

2.2.1 post请求提交String、文件

post请求与get请求的区别是 在构造Request对象时,须要多构造一个RequestBody对象,用它来携带咱们要提交的数据。示例以下:

OkHttpClient httpClient = new OkHttpClient();

        MediaType contentType = MediaType.parse("text/x-markdown; charset=utf-8");
        String content = "hello!";
        RequestBody body = RequestBody.create(contentType, content);

        Request getRequest = new Request.Builder()
                .url("https://api.github.com/markdown/raw")
                .post(body)
                .build();

        Call call = httpClient.newCall(getRequest);

        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "okHttpPost enqueue: \n onResponse:"+ response.toString() +"\n body:" +response.body().string());
            }
        });
复制代码

对比get请求,把构建Request时的get()改为post(body),并传入RequestBody实例。RequestBody实例是经过create方法建立,须要指定请求体内容类型、请求体内容。这里是传入了一个指定为markdown格式的文本。

结果打印以下:

2020-05-05 13:18:26.445 13301-13542/com.hfy.androidlearning I/OkHttpTestActivity: okHttpPost enqueue: 
     onResponse:Response{protocol=http/1.1, code=200, message=OK, url=https://api.github.com/markdown/raw}
     body:<p>hello!</p>
复制代码

请求成功并把请求体内容又返回来了。

传入RequestBody的 MediaType 还能够是其余类型,如客户端要给后台发送json字符串、发送一张图片,那么能够定义为:

// RequestBody:jsonBody,json字符串
String json = "jsonString";
RequestBody jsonBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);

//RequestBody:fileBody, 上传文件
File file = new File(Environment.getExternalStorageDirectory(), "1.png");
RequestBody fileBody = RequestBody.create(MediaType.parse("image/png"), file);
复制代码

MediaType更多类型信息能够查看 RFC 2045

2.2.2 post请求提交表单

构建RequestBody除了上面的方式,还有它的子类FormBody,FormBody用于提交表单键值对,这种能知足日常开发大部分的需求。

//RequestBody:FormBody,表单键值对
RequestBody formBody = new FormBody.Builder()
        .add("username", "hfy")
        .add("password", "qaz")
        .build();
复制代码

FormBody是经过FormBody.Builder用构建者模式建立,add键值对便可。它的contentType在内部已经指定了。

private static final MediaType CONTENT_TYPE = MediaType.get("application/x-www-form-urlencoded");
复制代码

2.2.2 post请求提交复杂请求体

RequestBody另外一个子类MultipartBody,用于post请求提交复杂类型的请求体。复杂请求体能够同时包含多种类型的的请求体数据。

上面介绍的 post请求 string、文件、表单,只有单一类型。考虑一种场景--注册场景,用户填写完姓名、电话,同时要上传头像图片,这时注册接口的请求体就须要 接受 表单键值对 以及文件了,那么前面讲的的post就没法知足了。那么就要用到MultipartBody了。 完整代码以下:

OkHttpClient httpClient = new OkHttpClient();

// MediaType contentType = MediaType.parse("text/x-markdown; charset=utf-8");
// String content = "hello!";
// RequestBody body = RequestBody.create(contentType, content);

        //RequestBody:fileBody,上传文件
        File file = drawableToFile(this, R.mipmap.bigpic, new File("00.jpg"));
        RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file);


        //RequestBody:multipartBody, 多类型 (用户名、密码、头像)
        MultipartBody multipartBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("username", "hufeiyang")
                .addFormDataPart("phone", "123456")
                .addFormDataPart("touxiang", "00.png", fileBody)
                .build();


        Request getRequest = new Request.Builder()
                .url("http://yun918.cn/study/public/file_upload.php")
                .post(multipartBody)
                .build();

        Call call = httpClient.newCall(getRequest);

        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

                Log.i(TAG, "okHttpPost enqueue: \n onFailure:"+ call.request().toString() +"\n body:" +call.request().body().contentType()
                +"\n IOException:"+e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "okHttpPost enqueue: \n onResponse:"+ response.toString() +"\n body:" +response.body().string());
            }
        });
复制代码

可见,在构建RequestBody时是使用MultipartBody.Builder构建了MultipartBody实例,经过addFormDataPart方法传入了姓名、电话的键值对,也经过addFormDataPart("touxiang", "00.png", fileBody)传入了头像图片,其中"touxiang"是key值, "00.png"是文件名,fileBody是要以上传的图片建立的RequestBody。 由于全部数据都是以键值对的表单形式提交,因此要设置setType(MultipartBody.FORM)。

请求抓包结果:

在这里插入图片描述
可见请求体重确实包含了姓名、电话、头像,而且注意到Content-Type值是 multipart/form-data。响应是200,说明请求成功了。

其余请求方式像put、header、delete,主要在构建Request时把get()或post()换成put()、header()、delete()就能够了,但通常在Android端不多用到。

2.4 请求配置项

先看几个问题:

  1. 如何全局设置超时时长?
  2. 缓存位置、最大缓存大小 呢?
  3. 考虑有这样一个需求,我要监控App经过 OkHttp 发出的 全部 原始请求,以及整个请求所耗费的时间,如何作?

这些问题,在OkHttp这里很简单。把OkHttpClient实例的建立,换成如下方式便可:

OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .cache(new Cache(getExternalCacheDir(),500 * 1024 * 1024))
                .addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Request request = chain.request();
                        String url = request.url().toString();
                        Log.i(TAG, "intercept: proceed start: url"+ url+ ", at "+System.currentTimeMillis());
                        Response response = chain.proceed(request);
                        ResponseBody body = response.body();
                        Log.i(TAG, "intercept: proceed end: url"+ url+ ", at "+System.currentTimeMillis());
                        return response;
                    }
                })
                .build();
复制代码

这里经过OkHttpClient.Builder经过构建者模式设置了链接、读取、写入的超时时长,用cache()方法传入了由缓存目录、缓存大小构成的Cache实例,这样就解决了前两个问题。

还注意到,使用addInterceptor()方法添加了Interceptor实例,且重写了intercept方法。Interceptor意为拦截器,intercept()方法会在开始执行请求时调用。其中chain.proceed(request)内部是真正请求的过程,是阻塞操做,执行完后会就会获得请求结果ResponseBody,因此chain.proceed(request)的先后取当前时间,那么就知道整个请求所耗费的时间。上面chain.proceed(request)的先后分别打印的日志和时间,这样第三个问题也解决了。

具体Interceptor是如何工做,会在下一篇流程分析中介绍。

另外,一般OkHttpClient实例是全局惟一的,这样这些基本配置就是统一,且内部维护的链接池也能够有效复用(会在下一篇流程分析中介绍)。

全局配置的有了,单个请求的也能够有一些单独的配置。

Request getRequest = new Request.Builder()
                .url("http://yun918.cn/study/public/file_upload.php")
                .post(multipartBody)
                .addHeader("key","value")
                .cacheControl(CacheControl.FORCE_NETWORK)
                .build();
复制代码

这个Request实例,

  • 使用addHeader()方法添加了请求头。
  • 使用cacheControl(CacheControl.FORCE_NETWORK)设置这次请求是能使用网络,不用缓存。(还能够设置只用缓存FORCE_CACHE。)

感谢 Okhttp3基本使用 OkHttp使用详解

相关文章
相关标签/搜索