今天我们来接触一下多线程下载,当然也包括断点续传,我们可以看到

很多下载器,当开通会员的时候下载东西的速度就变得快了许多,这是为什么呢?这就是跟今天讲的多线程有关系了,其实就是多开了几个线程一起下载罢了。当然真正的多线程下载要比这个复杂,要考虑很多问题。

效果图如下:

这里下载的是本地服务器上的文件,也可以下载网络上的一些文件。

先来看看多线程下载的原理吧:

通常服务器同时与多个用户连接,用户之间共享带宽。如果N个用户的优先级都相同,那么每个用户连接到该服务器上的实际带宽就是服务器带宽的N分之一。可以想象,如果用户数目较多,则每个用户只能占有可怜的一点带宽,下载将会是个漫长的过程。

假设服务器的带宽为20M/s,服务器上有很多电视剧资源,现在有三位同学都想要下载 ,现在三位同学都在下载,所以每位同学的速度应该为1/3 * 20M/s = 6.7M/s ,但是 欢乐颂这部电视剧有好多集,大家都想下最后一集,这时王五同学可能有点赶时间,等不及,下的这么慢,所以他就使用他所学的多线程的知识多开了几个线程,结果他最先下完。

这次可以看到分给每个线程的带宽为1/5 * 20M/s = 4M/s,但是后面三个线程都是王五同学的,这时王五同学的带宽其实为 12M/s ,没错,王五同学成功运用多线程知识解决了下载慢的问题。(神不知鬼不觉)

看到这里我们可以知道,影响用户带宽的因素:

①服务器的带宽

②线程数

好的,那让我们来看下具体如何实现:

要实现这个,需要解决以下几个问题:

问题1:怎么在一个文件里面写数据的时候按照指定的位置写(因为每个线程的下载区间需要不一样,不然数据会覆盖,导致文件下不全)

问题2:如何去获取要下载的文件大小(因为怕下载中途需要下载其他东西,导致本次需要下载的文件内存不足,所以需要先预留一个和要下载的文件大小一样大的空间)

问题3:计算每个子线程的下载区间(因为每个线程的下载区间肯定不一样,不然怎么加快速度呢)

第一个问题的解决办法:

借助RandomAccessFile 随机文件访问类的 seek(long offset)方法,这个方法可以把文件的写入位置移动至offset。

第二个问题的解决办法:

我们可以使用HttpURLConnection 对象的 getContentLength() 方法得到你当前请求文件的大小。

第三个问题的解决办法:

假设下载的文件大小为10B(0-9,数组下标从0开始),线程数为3,那么

线程0的下载区间应该是: 0—2

线程1的下载区间应该是: 3—5

线程2的下载区间应该是: 6—9

每个线程下载文件的大小 = 文件长度 / 线程数 (最后一个线程除外,因为可能不能均分)

那么i线程的下载开始位置: i*每个线程下载文件的大小

i线程的下载结束位置: (i+1)*每个线程下载文件的大小 - 1

最后一个线程的结束位置为:文件长度 - 1

搞清楚以上问题就可以多线程下载了,接下里就是断点续传了。

因为有时我们在下载下到一半的时候突然停电了,等来电时我们应该接到上次下载的地方继续下载。如何实现呢??

我们可以把每个线程下载的进度都存在一个文件里,等来电时我们先去检索有没有进度文件,如果有,说明上次下载过,但没下完,就将次进度取出来继续下载。不过线程下载的开始位置应该是 原来的开始位置+上次的进度,为了用户体验,我们应该在线程全部下载完成之后将保存的下载进度文件删除(因为这个文件对用户也没什么用)。

下面我们来理一下思路:

①请求网络得到需要下载的文件的大小,并生成一个和原文件一样大小的文件(先占空间)(响应码为200)

②确定每个线程的下载区间(最后一个线程的结束位置应该单独考虑)

③先查看有没有进度文件,有则从上次进度开始下载,没有则请求网络获取需要下载区间的数据,并生成下载进度文件以便断点续传。(记住请求的数据不是所有数据,而是各个线程它需要下载的那部分区间,响应码为206)

注:不是所有的服务器都支持断点续传,这取决于服务器那边。

④待各个线程全部下载完成,将进度文件删掉。

⑤开启线程下载

核心代码:

布局文件:

MainActivity:

public class MainActivity extends Activity {

//进度条

private ProgressBar pb;

//显示进度(百分比)

private TextView tv;

//记录当前进度条的下载进度

private int currentProgress;

//进行下载的线程数量

public static final int THREADCOUNT = 3;

//下载完成的线程数量

public int finishedThread = 0;

//下载完成生成的文件名

public String fileName = "download.txt";

//请求的文件下载地址(本地文件)

public String path = "https://192.168.102.2:8090/" + fileName;

private Handler mHandler = new Handler(){

public void handleMessage(android.os.Message msg) {

if (msg.what == 0x1) {

System.out.println(pb.getProgress());

System.out.println("最大:"+pb.getMax());

tv.setText(pb.getProgress()*100/pb.getMax() + "%");

}

};

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

initView();

}

/**

* 初始化组件

*/

private void initView() {

pb = (ProgressBar) findViewById(R.id.pb);

tv = (TextView) findViewById(R.id.tv);

}

/**

* 点击下载的事件

* @param view

*/

public void download(View view) {

new Thread() {

public void run() {

try {

URL url = new URL(path);

HttpURLConnection conn = (HttpURLConnection) url

.openConnection();

conn.setRequestMethod("GET");

conn.setConnectTimeout(3000);

conn.setReadTimeout(8000);

//请求成功时的响应码为200(注意响应码为200)

if (conn.getResponseCode() == 200) {

// 拿到需要下载的文件的大小

int length = conn.getContentLength();

// 先占个位置,生成临时文件

File file = new File(Environment.getExternalStorageDirectory(),fileName);

RandomAccessFile raf = new RandomAccessFile(file, "rwd");

raf.setLength(length);

//设置进度条的最大进度为文件的长度

pb.setMax(length);

raf.close();

//每个线程应该下载的长度(最后一个线程除外,因为不一定能够平分)

int size = length / THREADCOUNT;

for (int i = 0; i < THREADCOUNT; i++) {

// 1.确定每个线程的下载区间

// 2.开启对应的子线程

int startIndex = i * size; //开始位置

int endIndex = (i + 1) * size - 1; //结束位置

// 最后一个线程

if (i == THREADCOUNT - 1) {

endIndex = length - 1;

}

System.out.println("第" + (i + 1) + "个线程的下载区间为:"

+ startIndex + "-" + endIndex);

new DownloadThread(startIndex, endIndex, path, i)

.start();

}

}

} catch (MalformedURLException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

};

}.start();

}

class DownloadThread extends Thread{

private int lastProgress;

private int startIndex,endIndex,threadId;

private String path;

public DownloadThread(int startIndex,int endIndex,String path,int threadId) {

this.startIndex = startIndex;

this.endIndex = endIndex;

this.path = path;

this.threadId = threadId;

}

@Override

public void run() {

try {

//建立进度临时文件,其实这时还没有创建。当往文件里写东西的时候才创建。

File progressFile = new File(Environment.getExternalStorageDirectory(), threadId+".txt");

lastProgress=0;

//判断临时文件是否存在,存在表示已下载过,没下完而已

if (progressFile.exists()) {

FileInputStream fis = new FileInputStream(progressFile);

BufferedReader br = new BufferedReader(new InputStreamReader(fis));

//从进度临时文件中读取出上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置

lastProgress = Integer.parseInt(br.readLine());

startIndex += lastProgress;

//断点续传,更新上次下载的进度条

currentProgress += lastProgress;

pb.setProgress(currentProgress);

Message msg = Message.obtain();

msg.what = 0x1;

mHandler.sendMessage(msg);

br.close();

fis.close();

}

//真正请求数据

URL url = new URL(path);

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setRequestMethod("GET");

conn.setConnectTimeout(3000);

conn.setReadTimeout(8000);

//设置本次http请求所请求的数据的区间(这是需要服务器那边支持断点),格式需要这样写,不能写错

conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);

//请求部分数据,响应码是206(注意响应码是206)

if (conn.getResponseCode() == 206) {

//此时流中只有1/3原数据

InputStream is = conn.getInputStream();

File file = new File(Environment.getExternalStorageDirectory(),fileName);

RandomAccessFile raf = new RandomAccessFile(file, "rwd");

//把文件的写入位置移动至startIndex

raf.seek(startIndex);

byte[] b = new byte[10240];

int len = 0;

int total = lastProgress;

while ((len = is.read(b)) != -1) {

raf.write(b, 0, len);

total += len;

System.out.println("线程" + threadId + "下载了" + total);

//生成一个专门用来记录下载进度的临时文件

RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");

//每次读取流里数据之后,同步把当前线程下载的总进度写入进度临时文件中

progressRaf.write((total + "").getBytes());

progressRaf.close();

//下载时更新进度条

currentProgress += len;

pb.setProgress(currentProgress);

Message msg = Message.obtain();

msg.what = 0x1;

mHandler.sendMessage(msg);

}

System.out.println("线程" + threadId + "下载完成");

raf.close();

//每完成一个线程就+1

finishedThread ++;

//等标志位等于线程数的时候就说明线程全部完成了

if (finishedThread == THREADCOUNT) {

for (int i = 0; i < finishedThread; i++) {

//将生成的进度临时文件删除

File f = new File(Environment.getExternalStorageDirectory(),i + ".txt");

f.delete();

}

}

}

} catch (MalformedURLException e) {

e.printStackTrace();

} catch (ProtocolException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

注意:测试的文件不要太大也不要太小~

把文件放在Tomcat的这个目录下apache-tomcat-7.0.37webappsROOT

android线程池断点续传,Android之多线程下载及断点续传相关推荐

  1. android 多线程下载,断点续传,线程池

    android 多线程下载,断点续传,线程池 你可以在这里看到这个demo的源码: https://github.com/onlynight/MultiThreadDownloader 效果图 这张效 ...

  2. Android 线程池概念及使用

    一:使用线程池的原因 在android开发中经常会使用多线程异步来处理相关任务,而如果用传统的newThread来创建一个子线程进行处理,会造成一些严重的问题: 在任务众多的情况下,系统要为每一个任务 ...

  3. android 线程池

    为什么用线程池 创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率 例如: 记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3 如果T1+T3>T2 ...

  4. Android 线程池的使用

    线程池优点 提到线程池就必须先说一下线程池的优点,线程池的优点可以概括为以下四点: * 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销: * 线程池旨在线程的复用,就避免了创建线程和销毁 ...

  5. 线程池与Android的日日夜夜

    线程池与Android的日日夜夜 假如你Java中研究到了线程池的话,一般来说,你已经对线程的原理颇有研究了,或者说,你意识到了线程的某些瓶颈或者缺点.你说,要有光,所以,天降线程池. 正儿八经的说, ...

  6. Android 线程池管理工具类

    转自Android 线程池 public class AppExecutors {private static final String TAG = "AppExecutors"; ...

  7. Android 多线程下载以及断点续传

    多线程下载 在日常开发中,我们不可避免的会接到类似这样的需求,下载一个比较大的素材文件或者安装包文件,以此实现APP的自动更新,APP内的素材替换等.由于一般此类文件都比较大,一般会在50M以上,如果 ...

  8. 【Android】多线程下载加断点续传

    http://blog.csdn.net/smbroe/article/details/42270573 文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大 ...

  9. android多线程下载程序卡死,android 多线程下载与断点续传

    多线程下载: 下载速度更快,服务器对每个线程平分资源,故线程越多,得到的资源越多,下载速度越快. 断点续传: 下载中断,再次下载时从上一次下载结束的位置开始下载,防止重复下载 下载结束后 代码: pa ...

最新文章

  1. Tomcat下项目调整Log4J的console输出级别,减少输出信息
  2. 《计算机应用》实践考核,《管理系统中计算机应用》实践性环节考核方案
  3. mysql 安全问题_浅谈MySQL数据库的Web安全问题
  4. 解决 asp.net 伪静态 IIS设置后 真正的HTML无法显示的问题
  5. 基于Java Bean Validation对Request参数进行校验的设计思路
  6. Cocos2dx---------------- TinyXml 解析 XML
  7. bitlife设置中文_bitlife下载-bitlife中文版 v1.1.3下载-6188手游网
  8. 【c】正负数二进制表示
  9. fckeditor for java_配置FCKeditor(FCKeditor for java)
  10. html5一个可拖动的图片大小,HTML5画布中的可拖动和可调整大小元素
  11. 关于计算机的网络小说,“80后”从维熙:熟练电脑写作 关注网络文学
  12. 玉伯:从前端到体验,如何把格局做大
  13. 微信小程序学习第8天——自定义组件的数据监听器Observer小案例
  14. TOMCAT 中间件安全加固
  15. VS2015远程白屏的解决办法——亲测可用
  16. 你见过的最全面的Python重点知识总结
  17. 文献管理软件Zotero配置及使用
  18. Deepin UOS 20安装(附带镜像文件)
  19. 大学物理:CH3-振动
  20. 基于Labview的信号发生器的设计

热门文章

  1. 整理最全的PDF分页工具
  2. 苹果怎么换字体_苹果手机电池容量还剩80,什么时候换很省钱,听卖手机小哥怎么说...
  3. page builder odata model /UI2/PB_MODEL, first config, then cust and pers
  4. “机器鼠”出动!北理工团队X光片精度还原老鼠脊柱灵活度,可用于管道检测...
  5. Unity官方Animator经典学习范例MecanimGDC2013学习笔记
  6. stm32毕设分享 人体定位智能调速风扇系统
  7. 静态时序分析-OCV和time derate
  8. 微信跳一跳怎么修改java_微信跳一跳怎么用抓包修改分数_改数据[多图]
  9. 口腔数字化时代:AI牙医的防御基建与攻坚
  10. 只有一方签字的电子合同,有法律效应吗?