Android之OkHttp详解
文章大纲
一、okhttp简介
二、okhttp简单使用
三、okhttp封装
四、项目源码下载
一、okhttp简介
1. 什么是okhttp
一般在java平台上,我们会使用apache httpclient作为http客户端,用于发送 http 请求,并对响应进行处理。比如可以使用http客户端与第三方服务(如sso服务)进行集成,当然还可以爬取网上的数据等。okhttp与httpclient类似,也是一个http客户端,提供了对 http/2 和 spdy 的支持,并提供了连接池,gzip 压缩和 http 响应缓存功能。
2. okhttp优点
(1)支持http2/spdy(spdy是google开发的基于tcp的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验)
(2)socket自动选择最好路线,并支持自动重连,拥有自动维护的socket连接池,减少握手次数,减少了请求延迟,共享socket,减少对服务器的请求次数
(3)基于headers的缓存策略减少重复的网络请求
(4)拥有interceptors轻松处理请求与响应(自动处理gzip压缩)
3. okhttp功能
(1)一般的get请求 (2)一般的post请求 (3)基于http的文件上传 (4)文件下载 (5)上传下载的进度回调 (6)加载图片 (7)支持请求回调,直接返回对象、对象集合 (8)支持session的保持 (9)支持自签名网站https的访问,提供方法设置下证书就行 (10)支持取消某个请求
3. okhttp使用步骤
(1)get请求的步骤,首先构造一个request对象,参数最起码有个url,当然你可以通过request.builder设置更多的参数比如:header、method等。
(2)然后通过request的对象去构造得到一个call对象,类似于将你的请求封装成了任务,既然是任务,就会有execute()和cancel()等方法。
(3)最后,我们希望以异步的方式去执行请求,所以我们调用的是call.enqueue,将call加入调度队列,然后等待任务执行完成,我们在callback中即可得到结果。
(4)onresponse回调的参数是response,一般情况下,比如我们希望获得返回的字符串,
可以通过response.body().string()获取;如果希望获得返回的二进制字节数组,则调用response.body().bytes();如果你想拿到返回的inputstream,则调用response.body().bytestream()
(5)看到这,你可能会奇怪,竟然还能拿到返回的inputstream,看到这个最起码能意识到一点,这里支持大文件下载,有inputstream我们就可以通过io的方式写文件。不过也说明一个问题,这个onresponse执行的线程并不是ui线程。的确是的,如果你希望操作控件,还是需要使用handler等
(6)okhttp还支持gjson的处理方式
(7)okhttp支持同步请求和异步请求,call call = client.newcall(request);为同步请求,发送请求后,就会进入阻塞状态,知道收到响应call.enqueue(new callback()为异步请求
(8)在okhttp3.callback的回调方法里面有个参数是call 这个call可以单独取消相应的请求,随便在onfailure或者onresponse方法内部执行call.cancel()都可以。如果想取消所有的请求,则可以okhttpclient.dispatcher().cancelall();
二、okhttp简单使用
1. 进行get请求
/** * 原始的get请求 * * @author 吴晓畅 * */ public class okhttpget { public void get() { //1.okhttpclient对象 okhttpclient okhttpclient = new okhttpclient.builder(). //在这里,还可以设置数据缓存等 //设置超时时间 connecttimeout(15, timeunit.seconds). readtimeout(20, timeunit.seconds). writetimeout(20, timeunit.seconds). //错误重连 retryonconnectionfailure(true). build(); //2构造request, //builder.get()代表的是get请求,url方法里面放的参数是一个网络地址 request.builder builder = new request.builder(); request request = builder.get().url("http://www.baidu.com/").build(); //3将request封装成call call call = okhttpclient.newcall(request); //4,执行call,这个方法是异步请求数据 call.enqueue(new callback() { @override public void onfailure(call arg0, ioexception arg1) { //失败调用 } @override //由于okhttp在解析response的时候依靠的是response头信息当中的content-type字段来判断解码方式 //okhttp会使用默认的utf-8编码方式来解码 //这里使用的是异步加载,如果需要使用控件,则在主线程中调用 public void onresponse(call arg0, response arg1) throws ioexception { //成功调用 } }); } }
2. 进行post请求
/** * 使用okhttp进行post请求 * * @author 吴晓畅 * */ public class okhttppost { public void initpost() { //1.okhttpclient对象 okhttpclient okhttpclient = new okhttpclient.builder(). //在这里,还可以设置数据缓存等 //设置超时时间 connecttimeout(15, timeunit.seconds). readtimeout(20, timeunit.seconds). writetimeout(20, timeunit.seconds). //错误重连 retryonconnectionfailure(true). build(); requestbody requestbodypost = new formbody.builder() .add("page", "1") .add("code", "news") .add("pagesize", "20") .add("parentid", "0") .add("type", "1") .build(); request requestpost = new request.builder() .url("www.baidu.com") .post(requestbodypost) .build(); okhttpclient.newcall(requestpost).enqueue(new callback() { @override public void onfailure(call arg0, ioexception arg1) { // todo auto-generated method stub } @override public void onresponse(call arg0, response arg1) throws ioexception { //okhttp还支持gjson的处理方式 //在这里可以进行list<bean>和bean处理 } }); } }
3. 进行图片上传和下载
/** * 使用okhttp进行图片上传和下载 * * @author 吴晓畅 * */ public class okhttppicture { public void getpicture() { //1.创建一个okhttpclient对象 okhttpclient okhttpclient = new okhttpclient(); //2.创建request.builder对象,设置参数,请求方式如果是get,就不用设置,默认就是get request request = new request.builder() .url("www.baidu.com") .build(); //3.创建一个call对象,参数是request对象,发送请求 call call = okhttpclient.newcall(request); //4.异步请求,请求加入调度 call.enqueue(new callback() { @override public void onfailure(call arg0, ioexception arg1) { // todo auto-generated method stub } @override public void onresponse(call arg0, response arg1) throws ioexception { // //得到从网上获取资源,转换成我们想要的类型 // byte[] picture_bt = response.body().bytes(); // //通过handler更新ui // message message = handler.obtainmessage(); // message.obj = picture_bt; // message.what = success; // handler.sendmessage(message); } }); } public void shangchuanpicture() { okhttpclient mokhttpclent = new okhttpclient(); //获取sd卡中的文件 file file = new file(environment.getexternalstoragedirectory()+"/headportrait.jpg"); multipartbody.builder builder = new multipartbody.builder() //设置类型 .settype(multipartbody.form) //设置正文内容 .addformdatapart("img", "headportrait.jpg", requestbody.create(mediatype.parse("image/png"), file)); requestbody requestbody = builder.build(); request request = new request.builder() .url("www.baidu.com") .post(requestbody) .build(); call call = mokhttpclent.newcall(request); } }
3. 拦截器使用
什么是拦截器
首先我们需要了解什么事拦截器。打个比方,镖局押着一箱元宝在行走在一个山间小路上,突然从山上下来一群山贼拦住了镖局的去路,将镖局身上值钱的东西搜刮干净后将其放行。其中山贼相当于拦截器,镖局相当于一个正在执行任务的网络请求,请求中的参数就是镖局携带的元宝。拦截器可以将网络请求携带的参数进行修改验证,然后放行。这里面其实设计了aop编程的思想(面向切面编程)。
在介绍拦截器的作用和好处之前,我们还是要回到山贼这个角色上,如果让你做一次山贼,你会在什么地方埋伏?肯定是在镖局必经之路上埋伏。也就是说,拦截器就是在所有的网络请求的必经之地上进行拦截。
(1)拦截器可以一次性对所有的请求和返回值进行修改。
(2)拦截器可以一次性对请求的参数和返回的结果进行编码,比如统一设置为utf-8.
(3)拦截器可以对所有的请求做统一的日志记录,不需要在每个请求开始或者结束的位置都添加一个日志操作。
(4)其他需要对请求和返回进行统一处理的需求….
okhttp中拦截器分类
okhttp中的拦截器分2个:app层面的拦截器(application interception)、网络请求层面的拦截器(network interception)
(1)application interceptor是在请求执行刚开始,还没有执行okhttp的核心代码前进行拦截,application拦截器的作用:
1)不需要担心是否影响okhttp的请求策略和请求速度。
2)即使是从缓存中取数据,也会执行application拦截器。
3)允许重试,即chain.proceed()可以执行多次。(当然请不要盲目执行多次,需要加入你的逻辑判断)
(2)network interception是在连接网络之前
1)可以修改okhttp框架自动添加的一些属性(当然最好不要修改)。
2)可以观察最终完整的请求参数(也就是最终服务器接收到的请求数据和熟悉)
使用注意点
如果对拦截器不是很熟的同学,开发过程中,建议使用application interception。这样避免对okhttp请求策略的破坏。
常见实际场景
(1)对请求参数进行统一加密处理。
(2)拦截不符合规则的url。
(3)对请求或者返回参数设置统一的编码方式
(4)其它…。
代码实操
public class okhttplanjieqi { /** * 应用拦截器 */ interceptor appinterceptor = new interceptor() { @override public response intercept(chain chain) throws ioexception { request request = chain.request(); //———请求之前要做的事情———— httpurl url = request.url(); string s = url.url().tostring(); response response = chain.proceed(request); //———请求之后要做事情———— log.d("aa","app interceptor:begin"); return response; } }; /** * 网络拦截器 */ interceptor networkinterceptor = new interceptor() { @override public response intercept(chain chain) throws ioexception { request request = chain.request(); //———请求之前要做的事情———— response response = chain.proceed(request); //———请求之后要做事情———— return response; } }; /** * 进行get请求,并配置拦截器 */ public void initget() { okhttpclient okhttpclient = new okhttpclient .builder() .addinterceptor(appinterceptor)//application拦截器 .addnetworkinterceptor(networkinterceptor)//network拦截器 .build(); //2构造request, //builder.get()代表的是get请求,url方法里面放的参数是一个网络地址 request.builder builder = new request.builder(); request request = builder.get().url("http://www.baidu.com/").build(); //3将request封装成call call call = okhttpclient.newcall(request); //4,执行call,这个方法是异步请求数据 call.enqueue(new callback() { @override public void onfailure(call arg0, ioexception arg1) { //失败调用 } @override //由于okhttp在解析response的时候依靠的是response头信息当中的content-type字段来判断解码方式 //okhttp会使用默认的utf-8编码方式来解码 //这里使用的是异步加载,如果需要使用控件,则在主线程中调用 public void onresponse(call arg0, response arg1) throws ioexception { //成功调用 } }); } }
三、okhttp封装
1. 自行简单封装
/** * okhttp操作进行封装 * * @author 吴晓畅 * */ public class okhttp { public void get(string url, callback callback) { //1.okhttpclient对象 okhttpclient okhttpclient = new okhttpclient.builder(). //在这里,还可以设置数据缓存等 //设置超时时间 connecttimeout(15, timeunit.seconds). readtimeout(20, timeunit.seconds). writetimeout(20, timeunit.seconds). addinterceptor(appinterceptor).//application拦截器 //错误重连 retryonconnectionfailure(true). build(); //2构造request, //builder.get()代表的是get请求,url方法里面放的参数是一个网络地址 request.builder builder = new request.builder(); request request = builder.get().url(url).build(); //3将request封装成call call call = okhttpclient.newcall(request); //4,执行call,这个方法是异步请求数据 call.enqueue(callback); } public void post(string url, list<string> list, callback callback, requestbody requestbody) { //1.okhttpclient对象 okhttpclient okhttpclient = new okhttpclient.builder(). //在这里,还可以设置数据缓存等 //设置超时时间 connecttimeout(15, timeunit.seconds). addinterceptor(appinterceptor).//application拦截器 readtimeout(20, timeunit.seconds). writetimeout(20, timeunit.seconds). //错误重连 retryonconnectionfailure(true). build(); requestbody requestbodypost = requestbody; request requestpost = new request.builder() .url(url) .post(requestbodypost) .build(); okhttpclient.newcall(requestpost).enqueue(callback); } /** * 应用拦截器 */ interceptor appinterceptor = new interceptor() { @override public response intercept(chain chain) throws ioexception { request request = chain.request(); //———请求之前要做的事情———— response response = chain.proceed(request); //———请求之后要做事情———— return response; } }; }
2. android--okhttputils框架封装
简介
okhttputils:一个专注于让网络请求更简单的网络请求框架,对于任何形式的网络请求只需要一行代码。它是okhttp的一次二次封装,封装的目的是让网络请求更加方便。
okhttputils优势
(1)性能高,使用主流的okhttp的进行封装
okhttp我们知道它支持http2和socket的重连。自动选择最好的路线,拥有自己维护socket维护的连接池。可以减少tcp的握手次数,同时它拥有队列线程池可以轻松的并发请求。
(2)特有的网络缓存模式
okhttputils是大多数网络框架不具备的,比如我们公司的网络老板要求不仅在有网的情况下,进行展示网络数据,在无网的情况下使用缓存数据。这时候我们使用普通网络请求,就需要大量的判断。当前是否有网和无网状态,根据不同的状态保存不同的数据。然后再决定是否使用缓存。但是这是一个通用的写法。于是okhttputils使用自动网络缓存模式。让用户只关注数据处理。
(3)方便易用的扩展接口
可以添加全局的公共参数、全局的拦截器、全局的超时时间,更可以对单个请求定制拦截器。请求参数修改等等。
(4)强大的cookie的保存策略
在客户端对cookie的获取不是一个特别简单的事情,cookie全程自动管理,并且提供了额外的cookie管理方法,引入额外的自动管理中,添加任何你想创建的cookie。
依赖包导入
compile 'com.zhy:okhttputils:2.0.0'
进行get请求
private string get(string url) throws ioexception { request request = new request.builder() .url(url)//传url .build();//创建 //把request传进client //execute()执行线程 response response = client.newcall(request).execute(); return response.body().string(); }
进行post请求
private string post(string url, string json) throws ioexception { requestbody body = requestbody.create(json, json); request request = new request.builder() .url(url) .post(body) .build(); response response = client.newcall(request).execute(); return response.body().string(); }
使用okhttp-utils请求单张图片
public void getimage() { tv_result.settext(""); string url = "http://images.csdn.net/20150817/1.jpg"; okhttputils .get()// .url(url)// .tag(this)// .build()// .conntimeout(20000)//链接超时 .readtimeout(20000)//读取超时 .writetimeout(20000)//写入超时 .execute(new bitmapcallback() { @override public void onerror(call call, exception e, int id) { tv_result.settext("onerror:" + e.getmessage()); } @override public void onresponse(bitmap bitmap, int id) { log.e("tag", "onresponse:complete"); iv_icon.setimagebitmap(bitmap); } }); }
使用okhttp-utils上传多个或者单个文件
/** * 使用okhttp-utils上传多个或者单个文件 */ public void multifileupload() { //fileuploadservlet string mbaseurl = "http://192.168.3.27:8080/fileupload/fileuploadservlet"; file file = new file(environment.getexternalstoragedirectory(), "tupian.jpg"); file file2 = new file(environment.getexternalstoragedirectory(), "zanghao.jpg"); if (!file.exists()) { toast.maketext(okhttpactivity.this, "文件不存在,请修改文件路径", toast.length_short).show(); return; } // map<string, string> params = new hashmap<string, string>(); // params.put("username", "黄敏莹"); // params.put("password", "123"); string url = mbaseurl; okhttputils.post()// .addfile("mfile", "server_tupian.jpg", file)// .addfile("mfile", "server_zanghao.jpg", file2)//两个addfile就是多文件上传,注释掉一个就是单文件上传 .url(url) // .params(params)// .build()// .execute(new mystringcallback());//回调 }
回调处理
/** * 用于回调 * @author mloong * */ private class mystringcallback extends stringcallback{ @override public void onbefore(request request, int id) { // todo auto-generated method stub super.onbefore(request, id); settitle("loading..."); } @override public void onafter(int id) { // todo auto-generated method stub super.onafter(id); settitle("sample-okhttp"); } //出错 @override public void onerror(call arg0, exception e, int arg2) { e.printstacktrace(); tv_result.settext("onerror:"+e.getmessage()); } //成功后回调 @override public void onresponse(string response, int id) { //显示文本信息 tv_result.settext("onresponse:"+ response); switch (id) { case 100: toast.maketext(okhttpactivity.this, "http", toast.length_long).show(); break; case 101: toast.maketext(okhttpactivity.this, "https", toast.length_long).show(); break; default: break; } } @override public void inprogress(float progress, long total, int id) { log.e(tag, "inprogress:"+progress); mprogressbar.setprogress((int) (100*progress)); } }