网络请求框架OkHttp3全解系列:OkHttp的基本使用
Posted 胡飞洋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络请求框架OkHttp3全解系列:OkHttp的基本使用相关的知识,希望对你有一定的参考价值。
预计okhttp系列有4篇,使用方法、流程分析、缓存和连接池、使用扩展及封装。
这是第一篇,按照惯例,就介绍基本的使用方法,比较简单。
好了,闲话少叙,开始!所需 预备知识:
HTTP协议详解
HTTP请求报文和响应报文
现在android中的网络请求框架,基本都是okhttp和Retrofit一统天下,而Retrofit又是以okhttp为基础,所以系统学习okhttp的使用和原理就很有必要了。
它有以下默认特性:
使用连接池减少请求延时
透明的GZIP压缩减少响应数据的大小
缓存响应内容,避免一些完全重复的请求
一、引入
gradle引入依赖即可。
1 implementation 'com.squareup.okhttp3:okhttp:3.14.7'
2 implementation 'com.squareup.okio:okio:1.17.5'
3.14.x版本及以前的版本,采用Java语言编写,4.0.0以后采用kotlin语言;本系列文章中源码引自3.14.x版本,以Java语言讲解。
其中Okio库 是对Java.io和java.nio的补充,以便能够更加方便,快速的访问、存储和处理你的数据。OkHttp的底层使用该库作为支持。
另外,别忘了申请网络请求权限,如果还使用网络请求的缓存功能,那么还要申请读写外存的权限:
1 <uses-permission android:name="android.permission.INTERNET" />
2 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
3 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
二、使用方式
基本使用步骤如下
构建客户端对象OkHttpClient
构建请求Request
生成Call对象
Call发起请求(同步/异步)
下面跟着具体使用实例,详细介绍。
2.1 get请求
以百度主页为例,进行Get请求:
1 OkHttpClient httpClient = new OkHttpClient();
2
3 String url = "https://www.baidu.com/";
4 Request getRequest = new Request.Builder()
5 .url(url)
6 .get()
7 .build();
8
9 Call call = httpClient.newCall(getRequest);
10
11 new Thread(new Runnable() {
12 @Override
13 public void run() {
14 try {
15 //同步请求,要放到子线程执行
16 Response response = call.execute();
17 Log.i(TAG, "okHttpGet run: response:"+ response.body().string());
18 } catch (IOException e) {
19 e.printStackTrace();
20 }
21 }
22 }).start();
首先,创建了OkHttpClient实例,接着用Request.Builder构建了Request实例并传入了百度主页的url,然后httpClient.newCall方法传入Request实例生成call,最后在子线程调用call.execute()执行请求获得结果response。
所以,使用OkHttp进行get请求,是比较简单的,只要在构建Request实例时更换url就可以了。
异步请求
有个问题,你可能注意到了,这里是放在子线程执行请求的,这是因为call.execute()是同步方法。想要在主线程直接使用而不用手动创建子线程可以嘛?当然可以,使用call.enqueue(callback)即可:
1 call.enqueue(new Callback() {
2 @Override
3 public void onFailure(Call call, IOException e) {
4 }
5
6 @Override
7 public void onResponse(Call call, Response response) throws IOException {
8 Log.i(TAG, "okHttpGet enqueue: onResponse:"+ response.body().string());
9
10 ResponseBody body = response.body();
11 String string = body.string();
12 byte[] bytes = body.bytes();
13 InputStream inputStream = body.byteStream();
14 }
15 });
call.enqueue会异步执行,需要注意的是,两个回调方法onFailure、onResponse是执行在子线程的,所以如果想要执行UI操作,需要使用切换到UI线程。
取消请求
每一个Call只能执行一次(原因会在下篇流程分析中说明)。如果想要取消正在执行的请求,可以使用call.cancel(),通常在离开页面时都要取消执行的请求的。
结果处理
请求回调的两个方法是指 传输层 的失败和成功。onFailure通常是connection连接失败或读写超时;onResponse是指,成功的从服务器获取到了结果,但是这个结果的响应码可能是404、500等,也可能就是200(response.code()的取值)。
如果response.code()是200,表示应用层请求成功了。此时我们可以获取Response的ResponseBody,这是响应体。从面看到,可以从ResponseBody获取string、byte[]、InputStream,这样就可以对结果进行很多操作了,比如UI上展示string(要用Handler切换到UI线程)、通过InputStream写入文件等等。
上面异步请求执行后 结果打印如下:
12020-05-04 21:52:56.446 32681-3631/com.hfy.androidlearning I/OkHttpTestActivity: okHttpGet run: response:<!DOCTYPE html>
2 <!--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&tpl=mn&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>');
3 </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>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a> 京ICP证030173号 <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对象,用它来携带我们要提交的数据,其他都是一样的。示例如下:
1 OkHttpClient httpClient = new OkHttpClient();
2
3 MediaType contentType = MediaType.parse("text/x-markdown; charset=utf-8");
4 String content = "hello!";
5 RequestBody body = RequestBody.create(contentType, content);
6
7 Request getRequest = new Request.Builder()
8 .url("https://api.github.com/markdown/raw")
9 .post(body)
10 .build();
11
12 Call call = httpClient.newCall(getRequest);
13
14 call.enqueue(new Callback() {
15 @Override
16 public void onFailure(Call call, IOException e) {
17 }
18
19 @Override
20 public void onResponse(Call call, Response response) throws IOException {
21 Log.i(TAG, "okHttpPost enqueue: \n onResponse:"+ response.toString() +"\n body:" +response.body().string());
22 }
23 });
对比get请求,把构建Request时的get()改成post(body),并传入RequestBody实例。RequestBody实例是通过create方法创建,需要指定请求体内容类型、请求体内容。这里是传入了一个指定为markdown格式的文本。
结果打印如下:
12020-05-05 13:18:26.445 13301-13542/com.hfy.androidlearning I/OkHttpTestActivity: okHttpPost enqueue:
2 onResponse:Response{protocol=http/1.1, code=200, message=OK, url=https://api.github.com/markdown/raw}
3 body:<p>hello!</p>
请求成功并把请求体内容又返回来了。
传入RequestBody的 MediaType 还可以是其他类型,如客户端要给后台发送json字符串、发送一张图片,那么可以定义为:
1// RequestBody:jsonBody,json字符串
2String json = "jsonString";
3RequestBody jsonBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
4
5//RequestBody:fileBody, 上传文件
6File file = new File(Environment.getExternalStorageDirectory(), "1.png");
7RequestBody fileBody = RequestBody.create(MediaType.parse("image/png"), file);
MediaType更多类型信息可以查看 RFC 2045:https://tools.ietf.org/html/rfc2045。
2.2.2 post请求提交表单
构建RequestBody除了上面的方式,还有它的子类FormBody,FormBody用于提交表单键值对,这种能满足平常开发大部分的需求。
1//RequestBody:FormBody,表单键值对
2RequestBody formBody = new FormBody.Builder()
3 .add("username", "hfy")
4 .add("password", "qaz")
5 .build();
FormBody是通过FormBody.Builder用构建者模式创建,add键值对即可。它的contentType在内部已经指定了。
1 private static final MediaType CONTENT_TYPE = MediaType.get("application/x-www-form-urlencoded");
2.2.2 post请求提交复杂请求体
RequestBody另一个子类MultipartBody,用于post请求提交复杂类型的请求体。复杂请求体可以同时包含多种类型的的请求体数据。
1 OkHttpClient httpClient = new OkHttpClient();
2
3// MediaType contentType = MediaType.parse("text/x-markdown; charset=utf-8");
4// String content = "hello!";
5// RequestBody body = RequestBody.create(contentType, content);
6
7 //RequestBody:fileBody,上传文件
8 File file = drawableToFile(this, R.mipmap.bigpic, new File("00.jpg"));
9 RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file);
10
11
12 //RequestBody:multipartBody, 多类型 (用户名、密码、头像)
13 MultipartBody multipartBody = new MultipartBody.Builder()
14 .setType(MultipartBody.FORM)
15 .addFormDataPart("username", "hufeiyang")
16 .addFormDataPart("phone", "123456")
17 .addFormDataPart("touxiang", "00.png", fileBody)
18 .build();
19
20
21 Request getRequest = new Request.Builder()
22 .url("http://yun918.cn/study/public/file_upload.php")
23 .post(multipartBody)
24 .build();
25
26 Call call = httpClient.newCall(getRequest);
27
28 call.enqueue(new Callback() {
29 @Override
30 public void onFailure(Call call, IOException e) {
31
32 Log.i(TAG, "okHttpPost enqueue: \n onFailure:"+ call.request().toString() +"\n body:" +call.request().body().contentType()
33 +"\n IOException:"+e.getMessage());
34 }
35
36 @Override
37 public void onResponse(Call call, Response response) throws IOException {
38 Log.i(TAG, "okHttpPost enqueue: \n onResponse:"+ response.toString() +"\n body:" +response.body().string());
39 }
40 });
请求抓包结果:
可见请求体重确实包含了姓名、电话、头像,并且注意到Content-Type值是 multipart/form-data。响应是200,说明请求成功了。
其他请求方式像put、header、delete,主要在构建Request时把get()或post()换成put()、header()、delete()就可以了,但一般在Android端很少用到。
2.4 请求配置项
先看几个问题:
如何全局设置超时时长?
缓存位置、最大缓存大小 呢?
考虑有这样一个需求,我要监控App通过 OkHttp 发出的 所有 原始请求,以及整个请求所耗费的时间,如何做?
这些问题,在OkHttp这里很简单。把OkHttpClient实例的创建,换成以下方式即可:
1 OkHttpClient client = new OkHttpClient.Builder()
2 .connectTimeout(15, TimeUnit.SECONDS)
3 .readTimeout(10, TimeUnit.SECONDS)
4 .writeTimeout(10, TimeUnit.SECONDS)
5 .cache(new Cache(getExternalCacheDir(),500 * 1024 * 1024))
6 .addInterceptor(new Interceptor() {
7 @Override
8 public Response intercept(Chain chain) throws IOException {
9 Request request = chain.request();
10 String url = request.url().toString();
11 Log.i(TAG, "intercept: proceed start: url"+ url+ ", at "+System.currentTimeMillis());
12 Response response = chain.proceed(request);
13 ResponseBody body = response.body();
14 Log.i(TAG, "intercept: proceed end: url"+ url+ ", at "+System.currentTimeMillis());
15 return response;
16 }
17 })
18 .build();
这里通过OkHttpClient.Builder通过构建者模式设置了连接、读取、写入的超时时长,用cache()方法传入了由缓存目录、缓存大小构成的Cache实例,这样就解决了前两个问题。
还注意到,使用addInterceptor()方法添加了Interceptor实例,且重写了intercept方法。Interceptor意为拦截器,intercept()方法会在开始执行请求时调用。其中chain.proceed(request)内部是真正请求的过程,是阻塞操作,执行完后会就会得到请求结果ResponseBody,所以chain.proceed(request)的前后取当前时间,那么就知道整个请求所耗费的时间。上面chain.proceed(request)的前后分别打印的日志和时间,这样第三个问题也解决了。
具体Interceptor是如何工作,会在下一篇流程分析中介绍。
另外,通常OkHttpClient实例是全局唯一的,这样这些基本配置就是统一,且内部维护的连接池也可以有效复用(会在下一篇流程分析中介绍)。
全局配置的有了,单个请求的也可以有一些单独的配置。
1 Request getRequest = new Request.Builder()
2 .url("http://yun918.cn/study/public/file_upload.php")
3 .post(multipartBody)
4 .addHeader("key","value")
5 .cacheControl(CacheControl.FORCE_NETWORK)
6 .build();
这个Request实例,
使用addHeader()方法添加了请求头。
使用cacheControl(CacheControl.FORCE_NETWORK)设置此次请求是能使用网络,不用缓存。(还可以设置只用缓存FORCE_CACHE。)
好了,okhttp的使用就讲这里了,总体还是比较简单的。下篇是okhttp的工作流程分析,敬请期待~
推荐阅读:
点个在看吧
以上是关于网络请求框架OkHttp3全解系列:OkHttp的基本使用的主要内容,如果未能解决你的问题,请参考以下文章