首先声明一点: 这里的多线程下载 并不是指的 多个线程下载一个 文件,而是 每个线程 负责一个文件。真正的多线程 希望后面能给大家带来。
------------- 欢迎 爱学习的小伙伴 加群 -------------
-------------android交流群:230274309-------------
-------------一起分享,一起进步! 需要你们--------------
-------------- 期待各位爱学习的小伙伴们 的到来--------------
本文是接着前面http://blog.csdn.net/u011733020/article/details/47016715继续写的。感兴趣的小伙伴 可以看前面的。
界面效果
2015/7/31改进
2015/7/31 日 先说 这次 改进了两点。
第一点 ,前面说过 项目 只适合学习,作为商用的话, 效率不高,是因为 当时 点击暂停 ,在点击下载 继续下载时候,如果文件前面下载部分较大,会比较慢, 因为 java 的 inputstream 的 skip(long size) 跳过字节 这个方法 并不能按照你 想要跳过的字节,而是 跳过的 往往是比较小的,所以 要不断遍历,直到 返回满足 条件 ,比较耗时。打个比方, 文件大小 30M ,你下载了20M,你点了暂停 然后继续点下载,就要跳过这20M,但是你用skip 方法 可能每次跳过
4096 字节,这样 要跳过20M 的时间 就会很长。这样应该好理解。
第二点,原来 项目中, 你这一次下载没有完成,下次在下载是 删除掉原来的 从新 下载,这次 改成继续上次的地方 接着下载。
吐槽下,关于下载,我最近一周 一直在看 开源的download, 但是 无奈水平有限,收获甚微,往往是看到最后 脑袋短路。
这次改的方式比较简单,只改动了 项目中 DownloadManager 这个类。在来看下 DownloadManager 这个类 的run 方法,
@Override public void run() { info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态 notifyDownloadStateChanged(info); File file = new File(info.getPath());// 获取下载文件 HttpResult httpResult = null; InputStream stream = null; if (info.getCurrentSize() == 0 || !file.exists() || file.length() != info.getCurrentSize()) { // 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载 <span> </span>info.setCurrentSize(0); file.delete(); } httpResult = HttpHelper.download(info.getUrl()); if (httpResult == null || (stream = httpResult.getInputStream()) == null) { info.setDownloadState(STATE_ERROR);// 没有下载内容返回,修改为错误状态 notifyDownloadStateChanged(info); } else { try { skipBytesFromStream(stream, info.getCurrentSize()); } catch (Exception e1) { e1.printStackTrace(); } FileOutputStream fos = null; try { fos = new FileOutputStream(file, true); int count = -1; byte[] buffer = new byte[1024]; while (((count = stream.read(buffer)) != -1) && info.getDownloadState() == STATE_DOWNLOADING) { // 每次读取到数据后,都需要判断是否为下载状态,如果不是,下载需要终止,如果是,则刷新进度 fos.write(buffer, 0, count); fos.flush(); info.setCurrentSize(info.getCurrentSize() + count); notifyDownloadProgressed(info);// 刷新进度 } } catch (Exception e) { info.setDownloadState(STATE_ERROR); notifyDownloadStateChanged(info); info.setCurrentSize(0); file.delete(); } finally { IOUtils.close(fos); if (httpResult != null) { httpResult.close(); } } <span> </span>// 判断进度是否和app总长度相等 if (info.getCurrentSize() == info.getAppSize()) { info.setDownloadState(STATE_DOWNLOADED); notifyDownloadStateChanged(info); } else if (info.getDownloadState() == STATE_PAUSED) {// 判断状态 notifyDownloadStateChanged(info); } else { info.setDownloadState(STATE_ERROR); notifyDownloadStateChanged(info); info.setCurrentSize(0);// 错误状态需要删除文件 file.delete(); } } mTaskMap.remove(info.getId()); }
从服务器 返回的数据流 stream 最终是在 HttpHelper 这个类中
HttpResponse response = httpClient.execute(requestBase, httpContext);//访问网络
通过 httpclient 去联网请求的 。
我没有试过 httpclient addHeader("Range", "bytes=" + begin + "-" + end); 可不可以进行继续下载。
而是改成了 通过 httpurlconnection 去请求数据
现在 的run() 方法 是这样的。
@Override public void run() { info.setDownloadState(STATE_DOWNLOADING);// 先改变下载状态 notifyDownloadStateChanged(info); File file = new File(info.getPath());// 获取下载文件 /**********************************************************/ // try { try { URL url = new URL(info.getUrl()); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(30000); conn.setReadTimeout(30000); if (!file.exists()) { info.setCurrentSize(0); file.delete(); } else if (file.length() > info.getAppSize()) { info.setCurrentSize(0); file.delete(); } else if (file.length() == info.getAppSize()) { } else if (file.length() < info.getAppSize()) { info.setCurrentSize(file.length()); } if (info.getCurrentSize() == 0 || !file.exists() || file.length() != info.getCurrentSize()) { // 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载 info.setCurrentSize(0); file.delete(); } else if (file.length() == info.getCurrentSize() && file.length() < info.getAppSize()) { conn.setRequestProperty("Range", "bytes=" + info.getCurrentSize() + "-" + info.getAppSize()); } int code = conn.getResponseCode(); RandomAccessFile raf = new RandomAccessFile(file, "rw"); InputStream is = conn.getInputStream(); byte[] buffer = new byte[1024 * 8]; int len = -1; int total = 0;// 当前线程下载的总的数据的长度 if (code == 200) { } else if (code == 206) { raf.seek(file.length()); } while (((len = is.read(buffer)) != -1) && (info.getDownloadState() == STATE_DOWNLOADING)) { // 下载数据的过程。 raf.write(buffer, 0, len); total += len;// 需要记录当前的数据。 info.setCurrentSize(info.getCurrentSize() + len); notifyDownloadProgressed(info);// 刷新进度 } is.close(); raf.close(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } /*************************对于各种情况,需要删除下载任务,从新下载的 请自己改动代码*****************************/ // 判断进度是否和app总长度相等 // } catch (Exception e) { // System.out.println(e.toString()); // info.setDownloadState(STATE_ERROR); // info.setCurrentSize(0); // file.delete(); // e.printStackTrace(); // } if (info.getCurrentSize() == info.getAppSize()) { info.setDownloadState(STATE_DOWNLOADED); notifyDownloadStateChanged(info); } else if (info.getDownloadState() == STATE_PAUSED) {// 判断状态 notifyDownloadStateChanged(info); } else { info.setDownloadState(STATE_ERROR); notifyDownloadStateChanged(info); info.setCurrentSize(0);// 错误状态需要删除文件 file.delete(); } /**********************************************************/ mTaskMap.remove(info.getId()); }
先判断 文件存不存在,以及大小是否满足条件, 在这里做判断
if (info.getCurrentSize() == 0 || !file.exists() || file.length() != info.getCurrentSize()) { // 如果文件不存在,或者进度为0,或者进度和文件长度不相符,就需要重新下载 info.setCurrentSize(0); file.delete(); } else if (file.length() == info.getCurrentSize() && file.length() < info.getAppSize()) { conn.setRequestProperty("Range", "bytes=" + info.getCurrentSize() + "-" + info.getAppSize()); }
如果 文件当前大小为0,或者文件不存在,或者长度不等于当前长度,则重新下载,否则 设置 Range
下面 判断 code 正常情况下code =200 表示成功,如果 设置了Range 那么 code 返回 206 表示正常。这个时候 我们通过RandomAccessFile
RandomAccessFile 这个 类实现了 RandomAccessFile implements DataInput, DataOutput, 就是一个既可以读 也可以写的类。
RandomAccessFile 这个类来 处理 跳过多少字节, 前面 我说过 inpuStream.skeep() 方法 不准确,但是 RandomAccessFile 这个类是可以的。
RandomAccessFile raf = new RandomAccessFile(file, "rw"); InputStream is = conn.getInputStream(); byte[] buffer = new byte[1024 * 8]; int len = -1; int total = 0;// 当前线程下载的总的数据的长度 if (code == 200) { } else if (code == 206) { raf.seek(file.length()); }
通过 seek 方法 跳过 这些字节。
然后
while (((len = is.read(buffer)) != -1) && (info.getDownloadState() == STATE_DOWNLOADING)) { // 下载数据的过程。 raf.write(buffer, 0, len); total += len;// 需要记录当前的数据。 info.setCurrentSize(info.getCurrentSize() + len); notifyDownloadProgressed(info);// 刷新进度 } is.close(); raf.close();
很普通的代码,把数据写出去。不断刷新当前进度, 最后关闭流。
这样就可以保证 快速的 暂停 继续下载, 并且 本次下载 没有完成,点了暂停, 下次进应用,继续下载的时候 会接着上一次下载,但是断网,或者你自己把网关掉 ,下次在恢复网络,或者 在点下载,我并没有处理,有需要的就自己处理下吧,应该是捕获异常 seckouttimeException,然后保存数据。自己动手试下就知道了。
本次就到这里。 关于多个线程 分段下载一个文件, 等我有时间 在整理。
共勉
版权声明:本文为博主原创文章
版权声明:本文为博主原创文章,未经博主允许不得转载。