一、概述
最近在群里听到各种讨论okhttp的话题,可见okhttp的口碑相当好了。再加上Google貌似在6.0版本里面删除了HttpClient相关API,对于这个行为不做评价。为了更好的在应对网络访问,学习下okhttp还是蛮必要的,本篇博客首先介绍okhttp的简单使用,主要包含:
- 一般的get请求
- 一般的post请求
- 基于Http的文件上传
- 文件下载
- 加载图片
- 支持请求回调,直接返回对象、对象集合
- 支持session的保持
最后会对上述几个功能进行封装,完整的封装类的地址见:https://github.com/hongyangAndroid/okhttp-utils
使用前,对于Android Studio的用户,可以选择添加:
compile 'com.squareup.okhttp:okhttp:2.4.0'
或者Eclipse的用户,可以下载最新的jar okhttp 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(); } });
以上就是发送一个get请求的步骤,首先构造一个Request对象,参数最起码有个url,当然你可以通过Request.Builder设置更多的参数比如:
header
、method
等。然后通过request的对象去构造得到一个Call对象,类似于将你的请求封装成了任务,既然是任务,就会有
execute()
和cancel()
等方法。最后,我们希望以异步的方式去执行请求,所以我们调用的是call.enqueue,将call加入调度队列,然后等待任务执行完成,我们在Callback中即可得到结果。
看到这,你会发现,整体的写法还是比较长的,所以封装肯定是要做的,不然每个请求这么写,得累死。
ok,需要注意几点:
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); } }); }
- 我们这里是异步的方式去执行,当然也支持阻塞的方式,上面我们也说了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做写文件操作,我们这里就不赘述了。
关于用法,也可以参考泡网OkHttp使用教程
接下来我们主要看如何封装上述的代码。
三、封装
由于按照上述的代码,写多个请求肯定包含大量的重复代码,所以我希望封装后的代码调用是这样的:
(一)使用
一般的get请求
<code class="language-java hljs has-numbering"> </code><pre class="java" name="code">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里面直接操作控件。
文件上传且携带参数
我们希望提供一个方法,传入url,params,file,callback即可。
<code class="language-java hljs has-numbering"> </code><pre class="java" name="code">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.
文件下载
对于文件下载,提供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 } });
展示图片
展示图片,我们希望提供一个url和一个imageview,如果下载成功,直接帮我们设置上即可。
<code class="language-java hljs has-numbering"> </code><pre class="java" name="code">OkHttpClientManager.displayImage(mImageView, http://images.youkuaiyun.com/20150817/1.jpg);
内部会自动根据imageview的大小自动对图片进行合适的压缩。虽然,这里可能不适合一次性加载大量图片的场景,但是对于app中偶尔有几个图片的加载,还是可用的。
四、整合Gson
很多人提出项目中使用时,服务端返回的是Json字符串,希望客户端回调可以直接拿到对象,于是整合进入了Gson,完善该功能。
(一)直接回调对象
例如现在有个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"}
客户端可以如下方式调用:
<code class="language-java hljs has-numbering"> </code><pre class="java" name="code">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; } }
源码地址okhttp-utils,大家可以自己下载查看。
参考文章
====================================================
相信大家对OkHttp也是相当的熟悉了,毕竟是Square的东西,对于其种种优点,这里也不再叙说。优秀是优秀,但是毕竟优秀的东西给我们封装了太多,那么问题来了,我们使用OkHttp作为我们的网络层,简单地进行GET/POST请求是毫无问题。近日看了产品的设计稿,毛估估会有文件的上传与下载的需求,如果使用OkHttp作为网络层进行封装,你会惊讶的发现,简直封装的太“完美”了。如果现在有这么一个需求,要求对文件进行上传或下载,但是在上传或者下载前,你需要给用户一个友好的提示,在上传或者下载中,你需要将进度展示给用户,下载或者完成后提示用户下载完成。
但是呢,找啊找,你会发现基本上找不到OkHttp的这种用法,百度是找不到,但是你别忘记了还有谷歌,谷歌一搜,Stackoverflow就全出来了,甚至github上的issue都出来了,可见并不是我们遇到了这么一个问题,还要许许多多的人遇到了这个问题,粗粗看了几个回答,感觉有几个还是比较靠谱的。为了日后的重用,我将其封装为一个OkHttp的扩展库,暂时取名为CoreProgress。
要实现进度的监听,需要使用到OkHttp的依赖包Okio里的两个类,一个是Source,一个是Sink,至于Okio的东西,这里也不多说。
首先我们实现文件下载的进度监听。OkHttp给我们的只是一个回调,里面有Response返回结果,我们需要继承一个类,对结果进行监听,这个类就是ResponseBody,但是如何将它设置到OkHttp中去呢,答案是拦截器。拦截器的部分后面再叙述,这里先实现ResponseBody的子类ProgressResponseBody。
要监听进度,我们必然需要一个监听器,也就是一个接口,在其实现类中完成回调内容的处理,该接口声明如下。
然后会使用到该接口
类似装饰器,我们对原始的ResponseBody 进行了一层包装。并在其读取数据的时候设置了回调,回调的接口由构造函数传入,此外构造函数还传入了原始的ResponseBody,当系统内部调用了ResponseBody 的source方法的时候,返回的便是我们包装后的Source。然后我们还重写了几个方法调用原始的ResponseBody对应的函数返回结果。
同理既然下载是这样,那么上传也应该是这样,我们乘热打铁完成上传的部分,下载是继承ResponseBody ,上传就是继承RequestBody,同时也应该还有一个监听器。
RequestBody的子类实现类比ResponseBody ,基本上复制一下稍加修改即可使用。
内部维护了一个原始的RequestBody 以及一个监听器,同样的也是由构造函数传入。当然也是要重写几个函数调用原始的RequestBody 对应的函数,文件的下载是read函数中进行监听的设置,毫无疑问文件的上传就是write函数了,我们在write函数中进行了类似的操作,并回调了接口中的函数。当系统内部调用了RequestBody 的writeTo函数时,我们对BufferedSink 进行了一层包装,即设置了进度监听,并返回了我们包装的BufferedSink 。于是乎,上传于下载的进度监听就完成了。
还有一个重要的问题就是
如何进行使用呢?
如何进行使用呢?
如何进行使用呢?
重要的事要说三遍。
我们还需要一个Helper类,对上传或者下载进行监听设置。文件的上传其实很简单,将我们的原始RequestBody和监听器 传入,返回我们的包装的ProgressRequestBody ,使用包装后的ProgressRequestBody 进行请求即可,但是文件的下载呢,OkHttp给我们返回的是Response,我们如何将我们包装的ProgressResponseBody设置进去呢,答案之前已经说过了,就是拦截器,具体见代码吧。
对于文件下载的监听器我们为了不影响原来的OkHttpClient 实例,我们调用clone方法进行了克隆,之后对克隆的方法设置了响应拦截,并返回该克隆的实例。而文件的上传则十分简单,直接包装后返回即可。
但是你别忘记了,我们的目的是在UI层进行回调,而OkHttp的所有请求都不在UI层。于是我们还要实现我们写的接口,进行UI操作的回调。由于涉及到消息机制,我们对之前的两个接口回调传的参数进行封装,封装为一个实体类便于传递。
再实现我们的UI回调接口,对于文件的上传,我们需要实现的是ProgressRequestListener接口,文件的下载需要实现的是ProgressResponseListener接口,但是内部的逻辑处理是完全一样的。我们使用抽象类,提供一个抽象方法,该抽象方法用于UI层回调的处理,由具体开发去实现。涉及到消息机制就涉及到Handler类,在Handler的子类中维护一个弱引用指向外部类(用到了static防止内存泄露,但是需要调用外部类的一个非静态函数,所以将外部类引用直接由构造函数传入,在内部通过调用该引用的方法去实现),然后将主线程的Looper传入,调用父类构造函数。在onRequestProgress中发送进度更新的消息,在handleMessage函数中回调我们的抽象方法。我们只需要实现抽象方法,编写对应的UI更新代码即可。具体代码如下。
另一个实现类代码雷同,不做叙述。
最简单的一步就是在我们的程序中使用了。为了方便Android Studio用户使用,我将其发布到了中央库。加入如下依赖即可
注意OkHttp的依赖需要自己手动添加,这个库中OkHttp只是编译时依赖,并没有打包进去。
至于代码,也已经寄托在了github上,如果有bug,欢迎修正。https://github.com/lizhangqu/CoreProgress,具体使用方法github上也已经贴出来了。
如果依赖gradle不能下载的话可以直接从github上下代码。
这里贴一个简单的例子,布局文件,两个进度条用于显示进度
一个上传操作,一个下载操作,分别提供了UI层与非UI层回调的示例。最终代码中使用的监听器都是UI层的,因为我们要更新进度条。
需要特别注意的一点是文件的下载中如果文件大小未知,contentLength 会始终返回-1,对于已知文件大小的情况下,它返回的是实际文件大小。但是done只要在文件下载完成后才会返回true,可以使用两者的结合进行判断文件是否下载完成。
当然,我们进行进度的监听,最终还是会回调内部的接口,如果请求成功会回调onResponse,请求失败则回调onFailure,我们可以在onResponse中处理最终的结果。比如提示用户上传完成或者下载完成等等 。
还有一个细节需要注意就是连接的超时,读取与写入数据的超时时间的设置,在读取或者写入一些大文件的时候如果不设置这个参数可能会报异常,这里就随便设置了一下值,设得有点大,实际情况按需设置。
文件的上传需要服务器的配合,为了证明PHP是世界上最好的语言,服务器的操作使用PHP大法,其实就是简单的输出POST的内容和文件的信息,并将一个文件保存到了服务器当前目录下的file文件夹下。
下面是运行结果,可以看到点击了上传或者下载后,进度条在不断更新。
以及对应的日志输出。
文件的上传,用数据说话
文件的下载,同样是数据
如果这篇文章对你有用,那么请点击下方的顶支持一个吧。
源代码什么的就不再贴了,直接从github上下载吧https://github.com/lizhangqu/CoreProgress
====================================
开源框架com.nostra13.universalimageloader加载图片时抛出OUTOFMEMORY错,有需要的朋友可以参考下。
最近在开发应用的一个游戏模块中需缓存大量的网络图片到本地,为图方便直接使用的com.nostra13.universalimageloader提供的图片缓存方法,可在部分内存容量较小手机运行时ImageLoader.loadImage()偶尔抛出OutOfMemoryerror异常进而导致程序崩溃退出。针对该问题我们可以从如下两方法来解决:
1、尽量减少ImageLoader占用的内存避免出现内存溢出的问题
对DisplayImageOptions类使用如下的配置
new DisplayImageOptions.Builder() .cacheInMemory(false) //设置图片不缓存于内存中 .cacheOnDisc(true) .bitmapConfig(Bitmap.Config.RGB_565) //设置图片的质量 .imageScaleType(ImageScaleType.IN_SAMPLE_INT) //设置图片的缩放类型,该方法可以有效减少内存的占用 .build();
2、对错误进行处理
对图片加载方法添加监听事件,对出现的异常进行特定的处理:
imageLoader.loadImage(uri, options, new ImageLoadingListener() { @Override public void onLoadingStarted(String imageUri, View view) { // TODO Auto-generated method stub } @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { // TODO Auto-generated method stub switch (failReason.getType()) { case IO_ERROR: //handler.sendEmptyMessage(); break; case DECODING_ERROR: break; case NETWORK_DENIED: break; case OUT_OF_MEMORY: break; case UNKNOWN: break; default: break; } } @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { // TODO Auto-generated method stub } @Override public void onLoadingCancelled(String imageUri, View view) { // TODO Auto-generated method stub } });FailReason为该库中的异常类该类封装了枚举变量FailType,该类的定义如下:
public class FailReason { private final FailType type; private final Throwable cause; public FailReason(FailType type, Throwable cause) { this.type = type; this.cause = cause; } /** @return {@linkplain FailType Fail type} */ public FailType getType() { return type; } /** @return Thrown exception/error, can be <b>null</b> */ public Throwable getCause() { return cause; } /** Presents type of fail while image loading */ public static enum FailType { /** Input/output error. Can be caused by network communication fail or error while caching image on file system. */ IO_ERROR, /** * Error while * {@linkplain android.graphics.BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options) * decode image to Bitmap} */ DECODING_ERROR, /** * {@linkplain com.nostra13.universalimageloader.core.ImageLoader#denyNetworkDownloads(boolean) Network * downloads are denied} and requested image wasn't cached in disc cache before. */ NETWORK_DENIED, /** Not enough memory to create needed Bitmap for image */ OUT_OF_MEMORY, /** Unknown error was occurred while loading image */ UNKNOWN } }
Android OkHttp
最新推荐文章于 2018-10-26 14:33:39 发布