Android多线程下载原理

多线程下载介绍

多线程背景知识

多线程下载任务可以更快完成文件的下载,多线程下载文件之所以快,是因为其抢占的服务器资源多。如:假设服务器同时最多服务100个用户,在服务器中一条线程对应一个用户,100条线程在计算机中并非并发执行,而是由cpu划分时间片轮流执行,如果A应用使用了99条线程下载文件,那么相当于占用了99个用户的资源,假设一秒内cpu分配给每条线程的平均执行时间是10ms,A应用在服务器中就得到了990ms,而其他应用在1秒内只有10ms的执行时间。

多线程下载的实现过程

在客户端进行下载的时候,
第一步,在本地创建一个大小与服务器文件相同大小的零时文件。
第二步,计算机分配几个线程去下载服务器上的资源,知道每个线程下载文件的位置。假设服务器上的资源大小是10byte,编号从0到9,现在开启三个线程去服务器下载文件,那么每个线程下载文件的大小就等于文件的长度除以线程的个数,也就是三个线程。所以线程一下载的起始位置是0到2,线程二下载文件的起始位置3到5,最后一个线程特殊一点,下载文件的起始位置为6到文件末尾位置。由此得到每个线程下载文件起始位置的计算方法:开始位置 =(线程id -1)* 每一块的大小,结束位置 = 线程id*每一块的大小-1
第三步,开启多个线程,本例中开启三个线程。、

多线程下载的代码实现

首先,按照上述描述的步骤,链接服务器,获取文件,获取文件的长度,在本地创建一个大小与服务器一样大的临时文件。

public class Demo{
        public static void main() throws Exception{
            String path ="http://127.0.0.1/post/abc/sleep.gif";
            URL url = new URL(path);
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setRequestMethod("GET");
            int code = conn.getResponseCode();
            if (code == 200){
                int length = conn.getContentLength();
                System.out.println("文件长度"+length);
            }else{
                System.out.println("服务器错误");
            }
        }
    }

通过getContentLength()方法得到文件长度。然后就是分配几个线程去下载服务器上的资源文件。假设本例中使用三个线程,那么需要知道每个线程下载文件的大小以及起始位置。可以通过资源的长度除以线程个数得到每个线程下载文件的大小,每个线程下载文件的位置可以根据上面的公式计算得到,这里需要注意的是最后一个线程的处理,如果文件大小不能被整除,则需要对最后一个线程的终点位置做特殊的处理。代码如下:

 if (code == 200){
     int length = conn.getContentLength();
     System.out.println("文件长度"+length);
     int blockSize = length/threadCount;
     for (int threadId = 1;threadId <= threadCount;threadId++){
          int startIndex = (threadId-1)*blockSize-1;
          int endIndex = threadId*blockSize-1;
          if (threadId == threadCount){
              endIndex = length;
              }
          System.out.println("线程"+threadId+"下载:--"+startIndex+"-->"+endIndex);
          }
  }

至此,完成了多线程下载的第二步,接下来开启多个线程,让每个线程去下载对应的文件,开辟子线程,需要继承Thread类,并实现Thread中的run方法。在线程需要的几个参数:线程id,线程下载的开始位置,线程下载的结束位置,下载路径。在线程中定义以上变量,将参数传递进来,定义构造方法。代码如下:

 public class DownloadThread extends  Thread{
        private int threadId;
        private int startIndex;
        private int endIndex;
        private String path;
        public DownloadThread(String path,int startIndex,int endIndex,int threadId){
            this.path = path;
            this.startIndex = startIndex;
            this.endIndex = endIndex;
            this.threadId = threadId;
        }
        public void run(){
            try{
                URL url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection)url.openConnection();
                conn.setRequestMethod("GET");
                conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
                conn.setConnectTimeout(5000);
                int code = conn.getResponseCode();
                System.out.println(code);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

对于服务返回的状态码,如果是200,则代表从服务器请求全部资源成功,如果是成功从服务器请求部分资源,则服务器返回的状态码为206。在请求服务器资源的时候,如果使用getInputStream()这个方法返回服务器全部的资源,如果只想从服务器下载一部分的资源,需要通过一个特殊的的Http请求头去指定,使用Http的Range头字段指定每条线程从文件的什么位置开始,下载到什么位置为止。为了让不同线程下载的同一文件写到一个位置,这里需要用到java文件中的一个API:RandomAccessFile这个类,该类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中 的一个大型byte数组,存在向该隐含数组的光标和索引,成为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而迁移此指针。如果随机访问文件以随机读取/写入模式创建,则输出操作也是可用的;输出从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。它构造方法中接收的两个参数,一个是文件名,另一个是在随机访问文件在写时候的模式,mode参数指定用以打开文件的访问模式。当我们在主线程中 得到服务器文件的长度后,接下来就可以使用RandomAccessFile这个API去创建这个与服务器上源文件同样大小的临时文件。代码如下:

      int length = conn.getContentLength();
      System.out.println("文件长度"+length);
      //在客户端本地创建一个与服务器端文件大小一样的临时文件
      RandomAccessFile raf = new RandomAccessFile("sleep.gif","rwd");
      //指定创建文件的大小
      raf.setLength(length);
      raf.close();

在子线程中代码:

 InputStream is = conn.getInputStream();
 RandomAccessFile raf = new RandomAccessFile("sleep.gif","rwd");
 raf.seek(startIndex);
 int len = 0;
 byte[] buffer = new byte[1024];
 while ((len =  is.read(buffer))!=-1){
     raf.write(buffer,0,len);
 }
 is.close();
 raf.close();

seek()方法标识随机写文件的时候从哪个位置开始写,多线程下载的时候,第一个线程下载的开始位置是文件开头,第二个线程的下载位置应该是从第二个线程开始的地方存取文件,这样就可以在线程中开辟子线程了。

断点下载

在服务器下载文件的过程中,如果出现断电或者其他意外情况终止下载,希望下次打开下载的时候能从上次未下载完成的位置继续下载,这就需要引入断点下载技术,需要让子线程分别去记住已经下载的长度。可以通过文件读写的方式来下载文件,在开始下载的时候,首先让下载的子线程去读取已经下载的长度,从中来读取已经下载的进度。定义一个文件,用来记录当前线程下载的数据长度。代码如下:

 InputStream is = conn.getInputStream();
 RandomAccessFile raf = new RandomAccessFile("sleep.gif","rwd");
 raf.seek(startIndex);
 int len = 0;
 byte[] buffer = new byte[1024];
 //已经下载的数据的长度
 int total  = 0;
 File file = new File(threadId+".txt");
 while ((len = is.read(buffer))!=-1){
     FileOutputStream fos = new FileOutputStream(file);
     raf.write(buffer,0,len);
     total+=len;
     fos.write(String.valueOf(total).getBytes());
     fos.close();
}

为了记录已经下载的文件长度记录,需要在每个线程开始的地方检查是否存在记录下载长度的文件,如果存在,读取这个文件数据,把它添加到开始下载的位置里面去:
代码如下:

 public void run(){
            try{
                File tempFile = new File(threadId+".txt");
                if (tempFile.exists() && tempFile.length()>0){
                    FileInputStream fis = new FileInputStream(tempFile);
                    byte[] temp = new byte[1024];
                    int leng = fis.read(temp);
                    String downloadLen = new String(temp,0,leng);
                    int downloadLenInt = Integer.parseInt(downloadLen);
                    ///修改下载的真正的位置
                    startIndex = downloadLenInt;
                    fis.close();
                }

文件下载完成后,需要将下载文件清除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值