我的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回调的安全写法,文件下载和缓存策略等等,希望能够在你以后的开发中带来多一些启发,多一些思考。

0 0