详解Android OkHttp完全解析
一、概述
最近在群里听到各种讨论okhttp的话题,可见okhttp的口碑相当好了。再加上google貌似在6.0版本里面删除了httpclient相关api,对于这个行为不做评价。为了更好的在应对网络访问,学习下okhttp还是蛮必要的,本篇博客首先介绍okhttp的简单使用,主要包含:
- 一般的get请求
- 一般的post请求
- 基于http的文件上传
- 文件下载
- 加载图片
- 支持请求回调,直接返回对象、对象集合
- 支持session的保持
最后会对上述几个功能进行封装,完整的封装类的地址见:
使用前,对于android studio的用户,可以选择添加:
compile 'com.squareup.okhttp:okhttp:2.4.0'
或者eclipse的用户,可以下载最新的jarokhttp he latest jar ,添加依赖就可以用了。
注意:okhttp内部依赖okio,别忘了同时导入okio:
gradle: compile 'com.squareup.okio:okio:1.5.0'
最新的jar地址:okio the latest jar
二、使用教程
(一)http get
对了网络加载库,那么最常见的肯定就是http get请求了,比如获取一个网页的内容。
//创建okhttpclient对象 okhttpclient mokhttpclient = new okhttpclient(); //创建一个request final request request = new request.builder() .url("https://github.com/hongyangandroid") .build(); //new call call call = mokhttpclient.newcall(request); //请求加入调度 call.enqueue(new callback() { @override public void onfailure(request request, ioexception e) { } @override public void onresponse(final response response) throws ioexception { //string htmlstr = response.body().string(); } });
1.以上就是发送一个get请求的步骤,首先构造一个request对象,参数最起码有个url,当然你可以通过request.builder设置更多的参数比如:header、method等。
2.然后通过request的对象去构造得到一个call对象,类似于将你的请求封装成了任务,既然是任务,就会有execute()和cancel()等方法。
3.最后,我们希望以异步的方式去执行请求,所以我们调用的是call.enqueue,将call加入调度队列,然后等待任务执行完成,我们在callback中即可得到结果。
看到这,你会发现,整体的写法还是比较长的,所以封装肯定是要做的,不然每个请求这么写,得累死。
ok,需要注意几点:
1、onresponse回调的参数是response,一般情况下,比如我们希望获得返回的字符串,可以通过response.body().string()获取;如果希望获得返回的二进制字节数组,则调用response.body().bytes();如果你想拿到返回的inputstream,则调用response.body().bytestream()
看到这,你可能会奇怪,竟然还能拿到返回的inputstream,看到这个最起码能意识到一点,这里支持大文件下载,有inputstream我们就可以通过io的方式写文件。不过也说明一个问题,这个onresponse执行的线程并不是ui线程。的确是的,如果你希望操作控件,还是需要使用handler等,例如:
@override public void onresponse(final response response) throws ioexception { final string res = response.body().string(); runonuithread(new runnable() { @override public void run() { mtv.settext(res); } }); }
2、我们这里是异步的方式去执行,当然也支持阻塞的方式,上面我们也说了call有一个execute()方法,你也可以直接调用call.execute()通过返回一个response。
--------------------------------------------------------------------------------
(二) http post 携带参数
看来上面的简单的get请求,基本上整个的用法也就掌握了,比如post携带参数,也仅仅是request的构造的不同。
request request = buildmultipartformrequest( url, new file[]{file}, new string[]{filekey}, null); formencodingbuilder builder = new formencodingbuilder(); builder.add("username","张鸿洋"); request request = new request.builder() .url(url) .post(builder.build()) .build(); mokhttpclient.newcall(request).enqueue(new callback(){});
大家都清楚,post的时候,参数是包含在请求体中的;所以我们通过formencodingbuilder。添加多个string键值对,然后去构造requestbody,最后完成我们request的构造。
后面的就和上面一样了。
--------------------------------------------------------------------------------
(三)基于http的文件上传
接下来我们在介绍一个可以构造requestbody的builder,叫做multipartbuilder。当我们需要做类似于表单上传的时候,就可以使用它来构造我们的requestbody。
file file = new file(environment.getexternalstoragedirectory(), "balabala.mp4"); requestbody filebody = requestbody.create(mediatype.parse("application/octet-stream"), file); requestbody requestbody = new multipartbuilder() .type(multipartbuilder.form) .addpart(headers.of( "content-disposition", "form-data; name=\"username\""), requestbody.create(null, "张鸿洋")) .addpart(headers.of( "content-disposition", "form-data; name=\"mfile\"; filename=\"wjd.mp4\""), filebody) .build(); request request = new request.builder() .url("http://192.168.1.103:8080/okhttpserver/fileupload") .post(requestbody) .build(); call call = mokhttpclient.newcall(request); call.enqueue(new callback() { //... });
上述代码向服务器传递了一个键值对username:张鸿洋和一个文件。我们通过multipartbuilder的addpart方法可以添加键值对或者文件。
其实类似于我们拼接模拟浏览器行为的方式,如果你对这块不了解,可以参考:从原理角度解析android (java) http 文件上传
ok,对于我们最开始的目录还剩下图片下载,文件下载;这两个一个是通过回调的response拿到byte[]然后decode成图片;文件下载,就是拿到inputstream做写文件操作,我们这里就不赘述了。
接下来我们主要看如何封装上述的代码。
--------------------------------------------------------------------------------
三、封装
由于按照上述的代码,写多个请求肯定包含大量的重复代码,所以我希望封装后的代码调用是这样的:
(一)使用
1.一般的get请求
okhttpclientmanager.getasyn("https://www.baidu.com", new okhttpclientmanager.resultcallback<string>() { @override public void onerror(request request, exception e) { e.printstacktrace(); } @override public void onresponse(string u) { mtv.settext(u);//注意这里是ui线程 } });
对于一般的请求,我们希望给个url,然后callback里面直接操作控件。
2.文件上传且携带参数
我们希望提供一个方法,传入url,params,file,callback即可。
okhttpclientmanager.postasyn("http://192.168.1.103:8080/okhttpserver/fileupload",// new okhttpclientmanager.resultcallback<string>() { @override public void onerror(request request, ioexception e) { e.printstacktrace(); } @override public void onresponse(string result) { } },// file,// "mfile",// new okhttpclientmanager.param[]{ new okhttpclientmanager.param("username", "zhy"), new okhttpclientmanager.param("password", "123")} );
键值对没什么说的,参数3为file,参数4为file对应的name,这个name不是文件的名字;
对应于http中的
<input type="file" name="mfile" >
对应的是name后面的值,即mfile.
3.文件下载
对于文件下载,提供url,目标dir,callback即可。
okhttpclientmanager.downloadasyn( "http://192.168.1.103:8080/okhttpserver/files/messenger_01.png", environment.getexternalstoragedirectory().getabsolutepath(), new okhttpclientmanager.resultcallback<string>() { @override public void onerror(request request, ioexception e) { } @override public void onresponse(string response) { //文件下载成功,这里回调的reponse为文件的absolutepath } });
4.展示图片
展示图片,我们希望提供一个url和一个imageview,如果下载成功,直接帮我们设置上即可。
okhttpclientmanager.displayimage(mimageview, http://images.csdn.net/20150817/1.jpg);
内部会自动根据imageview的大小自动对图片进行合适的压缩。虽然,这里可能不适合一次性加载大量图片的场景,但是对于app中偶尔有几个图片的加载,还是可用的。
四、整合json
很多人提出项目中使用时,服务端返回的是json字符串,希望客户端回调可以直接拿到对象,于是整合进入了json,完善该功能。
(一)直接回调对象
例如现在有个user实体类:
package com.zhy.utils.http.okhttp; public class user { public string username ; public string password ; public user() { // todo auto-generated constructor stub } public user(string username, string password) { this.username = username; this.password = password; } @override public string tostring() { return "user{" + "username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
服务端返回:
{"username":"zhy","password":"123"}
客户端可以如下方式调用:
okhttpclientmanager.getasyn("http://192.168.56.1:8080/okhttpserver/user!getuser", new okhttpclientmanager.resultcallback<user>() { @override public void onerror(request request, exception e) { e.printstacktrace(); } @override public void onresponse(user user) { mtv.settext(u.tostring());//ui线程 } });
我们传入泛型user,在onresponse里面直接回调user对象。
这里特别要注意的事,如果在json字符串->实体对象过程中发生错误,程序不会崩溃,onerror方法会被回调。
注意:这里做了少许的更新,接口命名从stringcallback修改为resultcallback。接口中的onfailure方法修改为onerror。
(二) 回调对象集合
依然是上述的user类,服务端返回
[{"username":"zhy","password":"123"},{"username":"lmj","password":"12345"}]
则客户端可以如下调用:
okhttpclientmanager.getasyn("http://192.168.56.1:8080/okhttpserver/user!getusers", new okhttpclientmanager.resultcallback<list<user>>() { @override public void onerror(request request, exception e) { e.printstacktrace(); } @override public void onresponse(list<user> us) { log.e("tag", us.size() + ""); mtv.settext(us.get(1).tostring()); } });
唯一的区别,就是泛型变为list<user> ,ok , 如果发现bug或者有任何意见欢迎留言。
--------------------------------------------------------------------------------
源码
ok,基本介绍完了,对于封装的代码其实也很简单,我就直接贴出来了,因为也没什么好介绍的,如果你看完上面的用法,肯定可以看懂:
package com.zhy.utils.http.okhttp; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.os.handler; import android.os.looper; import android.widget.imageview; import com.google.gson.gson; import com.google.gson.internal.$gson$types; import com.squareup.okhttp.call; import com.squareup.okhttp.callback; import com.squareup.okhttp.formencodingbuilder; import com.squareup.okhttp.headers; import com.squareup.okhttp.mediatype; import com.squareup.okhttp.multipartbuilder; import com.squareup.okhttp.okhttpclient; import com.squareup.okhttp.request; import com.squareup.okhttp.requestbody; import com.squareup.okhttp.response; import java.io.file; import java.io.fileoutputstream; import java.io.ioexception; import java.io.inputstream; import java.lang.reflect.parameterizedtype; import java.lang.reflect.type; import java.net.cookiemanager; import java.net.cookiepolicy; import java.net.filenamemap; import java.net.urlconnection; import java.util.hashmap; import java.util.map; import java.util.set; /** * created by zhy on 15/8/17. */ public class okhttpclientmanager { private static okhttpclientmanager minstance; private okhttpclient mokhttpclient; private handler mdelivery; private gson mgson; private static final string tag = "okhttpclientmanager"; private okhttpclientmanager() { mokhttpclient = new okhttpclient(); //cookie enabled mokhttpclient.setcookiehandler(new cookiemanager(null, cookiepolicy.accept_original_server)); mdelivery = new handler(looper.getmainlooper()); mgson = new gson(); } public static okhttpclientmanager getinstance() { if (minstance == null) { synchronized (okhttpclientmanager.class) { if (minstance == null) { minstance = new okhttpclientmanager(); } } } return minstance; } /** * 同步的get请求 * * @param url * @return response */ private response _getasyn(string url) throws ioexception { final request request = new request.builder() .url(url) .build(); call call = mokhttpclient.newcall(request); response execute = call.execute(); return execute; } /** * 同步的get请求 * * @param url * @return 字符串 */ private string _getasstring(string url) throws ioexception { response execute = _getasyn(url); return execute.body().string(); } /** * 异步的get请求 * * @param url * @param callback */ private void _getasyn(string url, final resultcallback callback) { final request request = new request.builder() .url(url) .build(); deliveryresult(callback, request); } /** * 同步的post请求 * * @param url * @param params post的参数 * @return */ private response _post(string url, param... params) throws ioexception { request request = buildpostrequest(url, params); response response = mokhttpclient.newcall(request).execute(); return response; } /** * 同步的post请求 * * @param url * @param params post的参数 * @return 字符串 */ private string _postasstring(string url, param... params) throws ioexception { response response = _post(url, params); return response.body().string(); } /** * 异步的post请求 * * @param url * @param callback * @param params */ private void _postasyn(string url, final resultcallback callback, param... params) { request request = buildpostrequest(url, params); deliveryresult(callback, request); } /** * 异步的post请求 * * @param url * @param callback * @param params */ private void _postasyn(string url, final resultcallback callback, map<string, string> params) { param[] paramsarr = map2params(params); request request = buildpostrequest(url, paramsarr); deliveryresult(callback, request); } /** * 同步基于post的文件上传 * * @param params * @return */ private response _post(string url, file[] files, string[] filekeys, param... params) throws ioexception { request request = buildmultipartformrequest(url, files, filekeys, params); return mokhttpclient.newcall(request).execute(); } private response _post(string url, file file, string filekey) throws ioexception { request request = buildmultipartformrequest(url, new file[]{file}, new string[]{filekey}, null); return mokhttpclient.newcall(request).execute(); } private response _post(string url, file file, string filekey, param... params) throws ioexception { request request = buildmultipartformrequest(url, new file[]{file}, new string[]{filekey}, params); return mokhttpclient.newcall(request).execute(); } /** * 异步基于post的文件上传 * * @param url * @param callback * @param files * @param filekeys * @throws ioexception */ private void _postasyn(string url, resultcallback callback, file[] files, string[] filekeys, param... params) throws ioexception { request request = buildmultipartformrequest(url, files, filekeys, params); deliveryresult(callback, request); } /** * 异步基于post的文件上传,单文件不带参数上传 * * @param url * @param callback * @param file * @param filekey * @throws ioexception */ private void _postasyn(string url, resultcallback callback, file file, string filekey) throws ioexception { request request = buildmultipartformrequest(url, new file[]{file}, new string[]{filekey}, null); deliveryresult(callback, request); } /** * 异步基于post的文件上传,单文件且携带其他form参数上传 * * @param url * @param callback * @param file * @param filekey * @param params * @throws ioexception */ private void _postasyn(string url, resultcallback callback, file file, string filekey, param... params) throws ioexception { request request = buildmultipartformrequest(url, new file[]{file}, new string[]{filekey}, params); deliveryresult(callback, request); } /** * 异步下载文件 * * @param url * @param destfiledir 本地文件存储的文件夹 * @param callback */ private void _downloadasyn(final string url, final string destfiledir, final resultcallback callback) { final request request = new request.builder() .url(url) .build(); final call call = mokhttpclient.newcall(request); call.enqueue(new callback() { @override public void onfailure(final request request, final ioexception e) { sendfailedstringcallback(request, e, callback); } @override public void onresponse(response response) { inputstream is = null; byte[] buf = new byte[2048]; int len = 0; fileoutputstream fos = null; try { is = response.body().bytestream(); file file = new file(destfiledir, getfilename(url)); fos = new fileoutputstream(file); while ((len = is.read(buf)) != -1) { fos.write(buf, 0, len); } fos.flush(); //如果下载文件成功,第一个参数为文件的绝对路径 sendsuccessresultcallback(file.getabsolutepath(), callback); } catch (ioexception e) { sendfailedstringcallback(response.request(), e, callback); } finally { try { if (is != null) is.close(); } catch (ioexception e) { } try { if (fos != null) fos.close(); } catch (ioexception e) { } } } }); } private string getfilename(string path) { int separatorindex = path.lastindexof("/"); return (separatorindex < 0) ? path : path.substring(separatorindex + 1, path.length()); } /** * 加载图片 * * @param view * @param url * @throws ioexception */ private void _displayimage(final imageview view, final string url, final int errorresid) { final request request = new request.builder() .url(url) .build(); call call = mokhttpclient.newcall(request); call.enqueue(new callback() { @override public void onfailure(request request, ioexception e) { seterrorresid(view, errorresid); } @override public void onresponse(response response) { inputstream is = null; try { is = response.body().bytestream(); imageutils.imagesize actualimagesize = imageutils.getimagesize(is); imageutils.imagesize imageviewsize = imageutils.getimageviewsize(view); int insamplesize = imageutils.calculateinsamplesize(actualimagesize, imageviewsize); try { is.reset(); } catch (ioexception e) { response = _getasyn(url); is = response.body().bytestream(); } bitmapfactory.options ops = new bitmapfactory.options(); ops.injustdecodebounds = false; ops.insamplesize = insamplesize; final bitmap bm = bitmapfactory.decodestream(is, null, ops); mdelivery.post(new runnable() { @override public void run() { view.setimagebitmap(bm); } }); } catch (exception e) { seterrorresid(view, errorresid); } finally { if (is != null) try { is.close(); } catch (ioexception e) { e.printstacktrace(); } } } }); } private void seterrorresid(final imageview view, final int errorresid) { mdelivery.post(new runnable() { @override public void run() { view.setimageresource(errorresid); } }); } //*************对外公布的方法************ public static response getasyn(string url) throws ioexception { return getinstance()._getasyn(url); } public static string getasstring(string url) throws ioexception { return getinstance()._getasstring(url); } public static void getasyn(string url, resultcallback callback) { getinstance()._getasyn(url, callback); } public static response post(string url, param... params) throws ioexception { return getinstance()._post(url, params); } public static string postasstring(string url, param... params) throws ioexception { return getinstance()._postasstring(url, params); } public static void postasyn(string url, final resultcallback callback, param... params) { getinstance()._postasyn(url, callback, params); } public static void postasyn(string url, final resultcallback callback, map<string, string> params) { getinstance()._postasyn(url, callback, params); } public static response post(string url, file[] files, string[] filekeys, param... params) throws ioexception { return getinstance()._post(url, files, filekeys, params); } public static response post(string url, file file, string filekey) throws ioexception { return getinstance()._post(url, file, filekey); } public static response post(string url, file file, string filekey, param... params) throws ioexception { return getinstance()._post(url, file, filekey, params); } public static void postasyn(string url, resultcallback callback, file[] files, string[] filekeys, param... params) throws ioexception { getinstance()._postasyn(url, callback, files, filekeys, params); } public static void postasyn(string url, resultcallback callback, file file, string filekey) throws ioexception { getinstance()._postasyn(url, callback, file, filekey); } public static void postasyn(string url, resultcallback callback, file file, string filekey, param... params) throws ioexception { getinstance()._postasyn(url, callback, file, filekey, params); } public static void displayimage(final imageview view, string url, int errorresid) throws ioexception { getinstance()._displayimage(view, url, errorresid); } public static void displayimage(final imageview view, string url) { getinstance()._displayimage(view, url, -1); } public static void downloadasyn(string url, string destdir, resultcallback callback) { getinstance()._downloadasyn(url, destdir, callback); } //**************************** private request buildmultipartformrequest(string url, file[] files, string[] filekeys, param[] params) { params = validateparam(params); multipartbuilder builder = new multipartbuilder() .type(multipartbuilder.form); for (param param : params) { builder.addpart(headers.of("content-disposition", "form-data; name=\"" + param.key + "\""), requestbody.create(null, param.value)); } if (files != null) { requestbody filebody = null; for (int i = 0; i < files.length; i++) { file file = files[i]; string filename = file.getname(); filebody = requestbody.create(mediatype.parse(guessmimetype(filename)), file); //todo 根据文件名设置contenttype builder.addpart(headers.of("content-disposition", "form-data; name=\"" + filekeys[i] + "\"; filename=\"" + filename + "\""), filebody); } } requestbody requestbody = builder.build(); return new request.builder() .url(url) .post(requestbody) .build(); } private string guessmimetype(string path) { filenamemap filenamemap = urlconnection.getfilenamemap(); string contenttypefor = filenamemap.getcontenttypefor(path); if (contenttypefor == null) { contenttypefor = "application/octet-stream"; } return contenttypefor; } private param[] validateparam(param[] params) { if (params == null) return new param[0]; else return params; } private param[] map2params(map<string, string> params) { if (params == null) return new param[0]; int size = params.size(); param[] res = new param[size]; set<map.entry<string, string>> entries = params.entryset(); int i = 0; for (map.entry<string, string> entry : entries) { res[i++] = new param(entry.getkey(), entry.getvalue()); } return res; } private static final string session_key = "set-cookie"; private static final string msessionkey = "jsessionid"; private map<string, string> msessions = new hashmap<string, string>(); private void deliveryresult(final resultcallback callback, request request) { mokhttpclient.newcall(request).enqueue(new callback() { @override public void onfailure(final request request, final ioexception e) { sendfailedstringcallback(request, e, callback); } @override public void onresponse(final response response) { try { final string string = response.body().string(); if (callback.mtype == string.class) { sendsuccessresultcallback(string, callback); } else { object o = mgson.fromjson(string, callback.mtype); sendsuccessresultcallback(o, callback); } } catch (ioexception e) { sendfailedstringcallback(response.request(), e, callback); } catch (com.google.gson.jsonparseexception e)//json解析的错误 { sendfailedstringcallback(response.request(), e, callback); } } }); } private void sendfailedstringcallback(final request request, final exception e, final resultcallback callback) { mdelivery.post(new runnable() { @override public void run() { if (callback != null) callback.onerror(request, e); } }); } private void sendsuccessresultcallback(final object object, final resultcallback callback) { mdelivery.post(new runnable() { @override public void run() { if (callback != null) { callback.onresponse(object); } } }); } private request buildpostrequest(string url, param[] params) { if (params == null) { params = new param[0]; } formencodingbuilder builder = new formencodingbuilder(); for (param param : params) { builder.add(param.key, param.value); } requestbody requestbody = builder.build(); return new request.builder() .url(url) .post(requestbody) .build(); } public static abstract class resultcallback<t> { type mtype; public resultcallback() { mtype = getsuperclasstypeparameter(getclass()); } static type getsuperclasstypeparameter(class<?> subclass) { type superclass = subclass.getgenericsuperclass(); if (superclass instanceof class) { throw new runtimeexception("missing type parameter."); } parameterizedtype parameterized = (parameterizedtype) superclass; return $gson$types.canonicalize(parameterized.getactualtypearguments()[0]); } public abstract void onerror(request request, exception e); public abstract void onresponse(t response); } public static class param { public param() { } public param(string key, string value) { this.key = key; this.value = value; } string key; string value; } }
源码地址:demo下载
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: Spring Boot 自定义starter的示例代码
下一篇: python实现上传下载文件功能
推荐阅读
-
Android开发:微信授权登录与微信分享完全解析
-
android中Activity详解(生命周期、以各种方式启动Activity、状态保存,完全退出等)
-
Android中Fragment 真正的完全解析(上)
-
Android创建与解析XML(三)——详解Sax方式
-
Android 创建与解析XML(四)——详解Pull方式
-
Android中Fragment的解析和使用详解
-
Android 创建与解析XML(五)——详解Dom4j方式
-
Android创建与解析XML(二)——详解Dom方式
-
Android AsyncTask完全解析 带你从源码的角度彻底理解
-
Android中的Intent对象完全解析