Android学习之——自己搭建Http框架(2)——框架扩展
· 本文主要讲解的是Json指定转化成对象返回,下载进度更新,随时取消Request请求
一、Json指定转化成对象返回
上篇文章主要讲基础的框架搭建起来了,这次需要做一些些的扩展,这里Json转化用到了google的Gson。
上篇文章,我们直接返回了String的字符串,那么如果是请求返回回来的是Json格式的,我们能否在数据返回的时候将数据转化成需要的对象呢。答案当然是可以的。
我们可以在UI线程中创建Callback的时候将预处理的对象放入进去,还是直接代码描述比较清楚:
1. 首先我们需要传递 实体类 的class 进去,该方法我们可以在抽象类 AbstractCallback 中定义:
public AbstractCallback<T> setReturnClass(Class<T> clz) { this.mReturnClass = clz; return this; } public AbstractCallback<T> setReturnType(Type type) { this.mReturnType = type; return this; }2. 使用上述方法,在ui线程代码中,当使用的的时候调用方法如下, 注下面代码中的 new TypeToken<Entity>(){}.getType() 为GSON获取Type的方法:
private void requestJson() { Request request = new Request(UrlHelper.test_json_url, RequestMethod.GET);//UrlHelper.test_json_url是一个json地址 request.setCallback(new JsonCallback<Entity>() { // Entity 为 json 要转化的实体类 ,可以是 ArrayList<Entity>的形式等 @Override public void onFilure(Exception result) { } @Override public void onSuccess(Entity result) { mTestResultLabel.setText(result.weatherinfo + "----"); } }.setReturnType(new TypeToken<Entity>(){}.getType()));//.setReturnClass(Entity.class)); request.execute(); }3. JsonCallback的实现方式如下所示:其中,我们需要将继承的类AbstractCallback改成AbstractCallback<T> 以及对应的接口也改成ICallback<T>,这里就不具体列出修改泛型的代码了
public abstract class JsonCallback<T> extends AbstractCallback<T> { public static Gson gson = new Gson(); @Override protected T bindData(String content) { Log.i("bindData", content); if (TextUtil.isValidate(path)) { content = IOUtilities.readFromFile(path); } if (mReturnClass != null) { return gson.fromJson(content, mReturnClass); } else if (mReturnType != null) { return gson.fromJson(content, mReturnType); } return null; } }至此,转化成Json的对象已经处理完成。可能描述的不是太清楚,其实主要的步骤就是,跟上篇文章实现StringCallback.java一样,在UI线程的request.setcallback中new一个匿名内部类将ICallback以及他的抽象类,抽象子类实现泛型,使之可以传递需要的实体类的 cass,或者 type 进去。其实这个Json的转化并不是非常重要,在我阅读以及使用 android-async-http 框架的时候,直接的做法是,直接将HTTP返回的值预处理成JSON然后再onSuccess的时候进行JSON的解析,效率也并不会有太大的影响。
二、下载进度更新
处理思路:
在 AsyncTask 中doInBackground 里有一个方法publishProgress ,通过 AbstractCallback 的里的写入文件的进度,将进度实时更新到 onProgressUpdate 从而将更新进度更新到 主线程的功能,然后对进度进行相应的处理。如更新进度条等。
1. 首先添加一个监听接口:
public interface IProgressListener { void onProgressUpdate(int curPos,int contentLength); }2. 我们可以通过监听 写入到文件的循环中来进行监听,在AbstractCallback.java 中handle方法的写入文件的代码如下:
while ((read = in.read(b)) != -1) { // TODO update progress fos.write(b, 0, read); }为了节省篇幅,具体的代码可以去上一篇文章阅读。在这里,我们可以通过当前写入的进度和总长度进行比较写入监听的接口方法中,具体代码如下:
a. 修改 ICallback 接口:
Object handle(HttpResponse response, IProgressListener mProgressListener);b. 当然了同时要修改AbstractCallback 中的实现类
@Override public Object handle(HttpResponse response, IProgressListener mProgressListener){.........}c. 修改AbstractCallback 中的handle方法中写入文件的那段代码即上文代码的 //TODO update progress 段,具体代码如下:
byte[] b = new byte[IO_BUFFER_SIZE]; int read; long curPos = 0; long length = entity.getContentLength(); while ((read = in.read(b)) != -1) { checkIfCanceled(); if (mProgressListener != null) { curPos += read; //将当前进度和总进度返回到具体的实现层, //我们在 RequestTask 的 doInBackground 中去实现 IProgressLinstener 接口中的的该方法,将值传到onProgressUpdate中 mProgressListener.onProgressUpdate((int) (curPos / 1024), (int) (length / 1024)); } fos.write(b, 0, read); }
3. 在RequestTask.java 中的doInBackground 中我们来实现上述内容:
@Override protected Object doInBackground(Object... params) { try { HttpResponse response = HttpClientUtil.excute(request); //response 解析代码放到对应的类中,对应handle中的bindData方法 Log.i("doInBackground", response.toString()); if (request.mProgressListener != null) { return request.callback.handle(response, new IProgressListener() { @Override public void onProgressUpdate(int curPos, int contentLength) { //这里的参数类型是 AsyncTask<Object, Integer, Object>中的Integer决定的,在onProgressUpdate中可以得到这个值去更新UI主线程 publishProgress(curPos,contentLength); } }); }else { return request.callback.handle(response, null); } } catch (Exception e) { return e; } }4. 写到这里,突然忘记最重要的一点,我们需要在主线程中设置它的监听才能真正实现监听,在Request.java中我们加入如下方法,使主线程可以调用:
public IProgressListener mProgressListener; public void setProgressListener(IProgressListener iProgressListener) { this.mProgressListener = iProgressListener; }5. 继续上面第3点的话题,在RequestTask.java 中我们实现 AsyncTask 的onProgressUpdate 方法, 将在doInBackground 中调用的 publishProgress(..., ...) 方法得到的值在该方法中中传给IProgressListener的onProgressUpdate。不知道我这样的描述是否准确。表达不是很理想。
@Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); if (request.mProgressListener != null) { request.mProgressListener.onProgressUpdate(values[0], values[1]); } }6. 最后,我们在主线程中设置setProgressListener 并实现匿名内部类 IProgressListener ,通过重写 onProgressUpdate 方法得到当前进度值和总进度值,根据该进度值,进行实时的进度条更新等操作,主线程调用方法如下:
private void requestString() { //设置保存路径 String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mrfu_http.txt"; Request request = new Request(UrlHelper.test_string_url, RequestMethod.GET); request.setCallback(new StringCallback() { @Override public void onSuccess(String result) { mTestResultLabel.setText((String)result); } @Override public void onFilure(Exception result) { result.printStackTrace(); } }.setPath(path)); request.setProgressListener(new IProgressListener() { //在这里实现 IProgressListener 的onProgressUpdate 将子线程中得到的进度值获取到。 //这里,我们只是显示到LogCat中,实际我们可以根据需要实现进度条更新等操作 @Override public void onProgressUpdate(int curPos, int contentLength) { System.err.println("curPost:"+curPos +",contentLength:" + contentLength); } }); request.execute(); }
三、 如何随时取消 Request 请求
1. 我们需要取消 Request 请求,那么,在代码中,我们在哪些地方可以取消请求呢?我们先来分析框架的基本内容:
a. 在主线程我们执行 request.execute(); 在 Request.java 中开启了一个 RequestTask,它继承自 AsyncTask
b. doInBackground 是异步执行的,在这个子线程中 我们执行 HttpResponse response = HttpClientUtil.excute(request); 代码段 正式调用HTTP的get或者set方法,得到类型为 HttpResponse 的返回值 ,然后执行 AbstractCallback 的 handle 方法
c. 在AbstractCallback 的 public T handle(HttpResponse response, IProgressListener mProgressListener) 方法中我们处理返回回来的 HttpResponse 的内容,如果返回的code是200,则成功,那么我们根据是否设置了下载路径选择是否下载,或者是直接返回数值。
d. 根据主线程设置的 StringCallback 或者JsonCallback 或者其他解析类型,通过调用 bindData(....); 去具体的解析内容,并返回到 UI 线程。
2. 设计思路:
在主线程,我们接到了取消请求的需求,通过 调用 Requset 的 cancel() 方法,去调用 callback 中的 cancel(); 方法,将其AbstractCallback 中的取消标志设置为true,如果为true 我们就在checkIfCanceled()方法中抛出异常,结束该次请求。我们可以将 checkIfCanceled() 方法放在handle(..., ...)刚开始的时候,放在while ((read = in.read(b)) != -1){ fos.write(b, 0, read); } 写入文件的时候 以及返回数据放入不同callback中进行处理的时候。下面我们会给出具体的实现方法,还有http请求的 get 和 post 的时候。
3. 实现代码:
a. ICallback 接口中定义如下方法:
void checkIfCanceled() throws AppException; void cancel();
b. 主线程调用cancel请求:
public void testCancel(){ String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "stay_training_http.txt"; final Request request = new Request("http://h.hiphotos.baidu.com/image/w%3D2048/sign=432fca00369b033b2c88fbda21f636d3/a2cc7cd98d1001e9ae04c30bba0e7bec54e797fe.jpg",RequestMethod.GET); request.setCallback(new PathCallback() { @Override public void onSuccess(String result) { } @Override public void onFilure(Exception result) { result.printStackTrace(); } }.setPath(path)); request.setProgressListener(new IProgressListener() { @Override public void onProgressUpdate(int curPos, int contentLength) { System.err.println("curPost:"+curPos +",contentLength:" + contentLength); if (curPos > 8) { request.cancel();//当下载进度为8的时候我们取消了该请求 } } }); request.execute(); } }
c. 在 Request 中实现 cancel() 方法,方法内调用 callback 的 cancel() 方法:
public ICallback callback; public void cancel(){ if (callback != null) { this.callback.cancel(); } }
d. 在 AbstractCallback 中实现ICallback 里定义的 cancel() 的方法,如果主线程调用了cancel方法,我们就将标志设置为 true
protected boolean isCancelled; @Override public void cancel() { isCancelled = true; }
e. 实现 ICallback 中的 checkIfCanceled() 方法,如果 isCancelled 为 true 则抛出异常,即可中断请求操作,代码中出现了 AppException 自定义异常类 这个我们后面再讲
@Override public void checkIfCanceled() throws AppException { if (isCancelled) { throw new AppException(EnumException.CancelException, "request has been cancelled"); } }f. 在 handle 开开始放入 checkIfCanceled() 的判断,在将下载的文件写入到文件的时候我们也做判断,还有在进行数据处理的时候也进行判断
@Override public T handle(HttpResponse response, IProgressListener mProgressListener) throws AppException{ // file, json, xml, image, string checkIfCanceled();//在这里我们调用检查是否取消请求的方法 int statusCode = -1; InputStream in = null; try { HttpEntity entity = response.getEntity(); statusCode = response.getStatusLine().getStatusCode(); switch (statusCode) { case HttpStatus.SC_OK: if (TextUtil.isValidate(path)) { //将服务器返回的数据写入到文件当中 FileOutputStream fos = new FileOutputStream(path); if (entity.getContentEncoding() != null) { String encoding = entity.getContentEncoding().getValue(); if (encoding != null && "gzip".equalsIgnoreCase(encoding)) { in = new GZIPInputStream(entity.getContent()); } if (encoding != null && "deflate".equalsIgnoreCase(encoding)) { in = new InflaterInputStream(entity.getContent()); } } else { in = entity.getContent(); } byte[] b = new byte[IO_BUFFER_SIZE]; int read; long curPos = 0; long length = entity.getContentLength(); while ((read = in.read(b)) != -1) { checkIfCanceled(); //<span style="font-family: Arial, Helvetica, sans-serif;">在这里我们调用检查是否取消请求的方法</span> if (mProgressListener != null) { curPos += read; //将当前进度和总进度返回到具体的实现层, //我们在 RequestTask 的 doInBackground 中去实现 IProgressLinstener 接口中的的该方法,将值传到onProgressUpdate中 mProgressListener.onProgressUpdate((int) (curPos / 1024), (int) (length / 1024)); } fos.write(b, 0, read); } fos.flush(); fos.close(); in.close(); //写入文件之后,再从文件当中将数据读取出来,直接返回对象 return bindData(path); } else { // 需要返回的是对象,而不是数据流,所以需要去解析服务器返回的数据 // 对应StringCallback 中的return content; //2. 调用binData return bindData(EntityUtils.toString(entity)); } default: break; } return null; } catch (ParseException e) { throw new AppException(EnumException.ParseException, e.getMessage()); } catch (IOException e) { throw new AppException(EnumException.IOException, e.getMessage()); } } /** * 数据放入到不同的Callback中处理,StringCallback 等方法中实现了该方法 * @throws AppException */ protected T bindData(String content) throws AppException{ checkIfCanceled();//在这里我们检查是否取消请求的方法 return null; }
g. 我们在 RequestTask 中重写 onCancelled 判断 是否有做了 task.cancel(true); 的操作,当然,我们并没有实现该操作,那是因为,我们需要不管Request 的请求结果如果,我都需要返回到主线程,如果我这个时候取消掉了 AsyncTask ,那么AsyncTask 就永远不继续执行了,也就无法回调回来了。
@Override protected void onCancelled() { super.onCancelled(); if (request.callback != null) { request.callback.cancel(); } }h. HttpClientUtil.java 中的 post 和 get 代码中加入如下代码,具体内容请看注释:
private static HttpResponse get(Request request) throws AppException { try { //如果在代码已经执行到这里的时候,AbstractCallback中的isCancelled被置为了 true //这时我们就要再一次进行检查是否取消。 post方法同理,不再赘述 if (request.callback != null) { request.callback.checkIfCanceled(); } HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(request.url); addHeader(get, request.headers); //返回的结果放到上一层进行处理 HttpResponse response = client.execute(get); return response; } catch (ClientProtocolException e) { throw new AppException(EnumException.ClientProtocolException, e.getMessage()); } catch (IOException e) { throw new AppException(EnumException.IOException, e.getMessage()); } }
上文提到的 AppException 就放到下篇文章再讲吧,还有 预处理返回的对象,即将返回回来的数据解析成对象以后,对该对象进行预处理操作,如写入数据库之类的操作,也一起放入下篇文章讲解,因为这块我也还不是啃的很透,需要再磨练磨练,再看看 stay 老师的视频,
一不小心一点半了,今天就写到这里吧,要写好一篇博客真心难,从晚上 9 点开始一边回顾视频,一边整理思路,一边再重新实现一遍,然后一点点写上来。
ps 本来写了一大段抒情性的话的,等写完了又觉得不太好意思,技术博客就单纯一点吧。
特别感谢 stay 老师在这当中的帮助。让我在框架学习这块实打实的迈出了第一步! 他的个人网站:Stay技术分享技术生活 ,有兴趣的可以去看看他的视频,讲解的相当到位。——纯技术推荐...