我的Android网络框架之旅(四)
来源:互联网 发布:linux系统如何重启 编辑:程序博客网 时间:2024/05/21 10:48
今天打算一口气把网络框架的UI回调和文件缓存写完,上一周没有更新博客是因为忙着加班了,这周同样又是在加班,不过无论如何也要坚持写下去才行啊!
网络异常状态
上次已经说完了网络数据的传输报文的编辑,其实在网络请求过程中,我们不仅要考虑到网络数据的完整性,还要考虑到用户的网络延迟,服务器的状态码异常,传输数据流的关闭,数据流异常抛出等等,比如我们可以像下面这样写:
/** * @param loader 发送的资源信息 * @return * @throws Exception * @date 2015/12/28 13:55 * @author ZhongR * @description 处理附件下载的请求 */ @Override public Document handleDownloadRequest(AbstractLoader loader) throws Exception { Document document = null; //网络请求结果的实体类 HttpURLConnection connect = null;//网络连接池 DataOutputStream dos = null; //数据输入流 InputStream input = null; //数据输出流 try { if (checkNetwork()) { document = new Document(); // 当前操作被取消 if (loader.isCancel()) { return document; } //从父类ProtocolHandler获取Connection connect = getConnection(loader, dos, false); if (connect == null) { throw new NullPointerException("connect is null"); } //获取返回的状态码 int code = connect.getResponseCode(); if (code == 200 || code == 201) { //获取文件总长度 int length = connect.getContentLength(); //将文件下载下来 String path = FileUtils.saveFileFromUrl(connect.getInputStream(), loader, length); //下载完成后,在Document里面编辑路径 document.setMethodId(loader.getMethod()); document.setSavePath(path); } else { document.setError("Access server fail.code " + code); } } } catch (Exception e) { throw e; } finally { try { if (input != null) { input.close(); } if (dos != null) { dos.close(); } } catch (IOException e) { throw e; } } return document; }
在17-19行中,我们判断了当前任务的状态和当前网络情况,若被取消了则返回一个空的Document对象回调给对象。
在24行中,我们抛出了一个connection异常,表示在创建URL连接时出现了异常。
在30行中,我们对收到的响应消息进行了分类处理,若是非200系列的状态码,会调用setError()方法进行异常信息的回调。
在34行中,我们调用了FileUtils进行文件的下载,这个操作同时也是本地缓存操作,在执行重复任务的时候,我们会优先查看本地是否有与之相符的缓存文件,有则无需请求。
文件下载&缓存
说到本地缓存,那么我们应该怎么做呢?这里的思路是根据URL地址截取文件的后缀名得出来的值作为文件名进行保存。这样做的好处就是每个URL地址都会对应一个唯一值,我们使用这个唯一值进行保存的话,在执行重复请求的时候,会在缓存目录下判断是否存在该文件,若是存在则直接返回缓存文件,没有则重新下载。这里有一个值得注意的地方,正在下载过程中的文件,我们用.temp后缀名表示,这样的文件会在下次执行下载操作时删除,当然啦,你也可以使用这样的方式来支持断点续传功能,具体可以自己研究下,代码如下:
/** * @Description 根据文件流,下载文件 * @author ZhongR * @date 2015/12/28 14:57 * @param inputStream * @param loader * @return */ public static String saveFileFromUrl(InputStream inputStream, AbstractLoader loader, int Length) { // 获取本地保存本地 String path = FolderUtil.DIR_FILE; if (loader.getSaveDir() != null) path = loader.getSaveDir(); String url = loader.getUrl(); String name = getFileNameByUrl(url) + ".temp"; // 下载过程中的临时文件后缀名加上.temp File file = new File(path + "/" + name); FileOutputStream outputStream = null; if (!file.exists()) { // 如果没父目录,先创建父目录 File destDir = new File(path); if (!destDir.exists()) { destDir.mkdirs(); } // 创建文件*/ try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } // 创建输出流*/ try { outputStream = new FileOutputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); } int len = 0; byte buffer[] = new byte[1024]; float length = Length; // 转换成浮点型 float total = 0.0f; // 保存当前下载了多少 boolean isRename = false; // 是否改名成功 try { while ((len = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, len); total += len; // 不停的回调onprogress loader.doProgress(total / length); } name = file.getAbsolutePath(); name = name.substring(0, (name.length() - 5));// 将临时文件的后缀去掉 // 文件更名 isRename = file.renameTo(new File(name)); if (outputStream != null) { outputStream.close(); } } catch (IOException e) { e.printStackTrace(); } // 改名成功则返回更改后路径,否则显示原始路径 if (isRename) return name; else return file.getAbsolutePath(); }
哈哈,酱紫就完成了文件的下载操作啦,是不是so esay,我们可以看到在54行下载完成以后,我恢复了文件的原始后缀名。如果有做图片缓存的朋友,这里要注意一点,我们不希望缓存的图片在用户的系统Gallery中展示出来,这个时候不能保存原始的文件名,可以使用MD5加密的方式将URL地址转换成唯一标字串进行存储,其他使用方式都是一样的。关于如何分发本地缓存的策略在第一章已经贴出来了,这里我就不再赘述了。如果你希望能够更好的优化自己的缓存策略,可以看看郭霖大神的博客DiskLruCache完全解析
安全的使用Handler
设计网络框架的时候,无一例外会有一个页面统一的回调接口,像这样:
public interface UICallback { void onSuccess (Document document); void onError(String error); void onProgress(float percent);}
接口会将网络请求的结果通过对象的形式返回给使用者。我们都知道网络请求是一个异步操作,那么接口回调自然是在UI线程进行的。常见的UI线程操作不外乎以下几种:
1.AsynckTask
2.View.Post
3.runOnUiThread
4.Hander().post
好吧实际上他们的实现原理都是使用Handler(呵呵呵),之所以绕这么大一个弯子,我只是想说明,使用Handler很重要,同时使用Handler也很危险!即使是在我等下讲完安全的使用方法之后,你的代码还是依旧会崩溃,因为上面四种方式中的前三种都是由系统API已经实现的Handler,危险无处不在~~~
关于Handler的使用,会造成2个问题:
问题1:如果一个Activity已经Finish,而Handler在MessageQueue里面还有任务没执行,那么非静态内部类会引用Context实例对象造成内存泄漏。
问题2:如果一个Activity已经Finish,而Handler才开始执行IU回调方法,那么会抛出空指针异常。
对于从内存泄漏问题,我们应该将其改成静态内部类,解除他对context的引用。然而由于我们使用了静态内部类,所以对Context使用的是强引用,所以依然没有解决内存泄漏问题。这里我们应该使用弱引用,在下一次GC时,就能够正常回收。当生命周期执行到OnFinish()时,我们还可以使用Cancel这个状态,来取消队列中的任务,从而避免内存泄漏问题。下面这段代码,保证安全绿色无公害~~~
/** * @author ZhongR * @Description 不会内存泄露的Hanlder * @date 2015/12/10 19:03 */ static class SafeHandle extends Handler { WeakReference<Activity> weakReference; SafeHandle(Activity actvity) { super(); weakReference = new WeakReference<Activity >(actvity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case START_LOADING: if (weakReference.get() != null) { //dosomething.... } break; case UPDATE_UI: if (weakReference.get() != null) { //dosomething.... } break; case SHOW_ERRO: if (weakReference.get() != null) { //dosomething.... } break; } } }
同样的AsynckTask也会造成内存泄漏,大家一定要记得安全的使用Handler哦!在onFinish()方法中手动释放对Context的引用也是要养成的好习惯。
断断续续也总算是写完了,借着网络框架这个大话题,我们介绍了网络请求的分发机制,网络缓存的处理,网络传输协议的解析,并借此抛砖引玉,提出了高并发下的性能优化,UI回调的安全写法,文件下载和缓存策略等等,希望能够在你以后的开发中带来多一些启发,多一些思考。
- 我的Android网络框架之旅(四)
- 我的Android网络框架之旅(一)
- 我的Android网络框架之旅(二)
- 我的Android网络框架之旅(三)
- 我的Android之旅(四)--ViewPage(1)
- 我的CTF之旅(四)
- 我的Android进阶之旅------>四种呼叫转移场景
- Android高级之xUtils框架(四):DBUtils的用法
- Android高级之xUtils框架(四):DBUtils的用法
- Android网络框架之请求配置与Response缓存(四)
- 我的Android NDK之旅(四),android串口通信-mac+串口调试工具
- Android 之我的开源框架
- Android之Http网络编程(四)
- Android基础知识(四)之网络编程
- WEB 开发技术系列之四-我的Web框架(hack Struts)
- 超好用的网络抓包框架(Windivert)之四(实例二)
- CocoaAsyncSocket 网络通信使用之RHSocketKit框架(四)
- CocoaAsyncSocket 网络通信使用之RHSocketKit框架(四)
- DB2数据库切换为oracle数据库经验教训总结
- linux文件权限
- postInvalidate和invalidate的区别
- RGB像素明度计算公式
- 集合_1
- 我的Android网络框架之旅(四)
- 集合_2
- java 实现二叉树结构的基本运算详细代码
- iOS地图开发-地图的定位
- 友元函数,一般函数,成员函数
- java中菜单栏的创建
- 关于别克2016君越(Buick Lacroesse)的音频系统
- 集合_3
- gdb调试命令总结