服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - PC版与Android手机版带断点续传的多线程下载

PC版与Android手机版带断点续传的多线程下载

2021-04-02 16:48Android开发网 Android

这篇文章主要介绍了PC版与Android手机版带断点续传的多线程下载的相关资料,需要的朋友可以参考下

一、多线程下载

        多线程下载就是抢占服务器资源

        原理:服务器CPU 分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源。

      1、设置开启线程数,发送http请求到下载地址,获取下载文件的总长度
          然后创建一个长度一致的临时文件,避免下载到一半存储空间不够了,并计算每个线程下载多少数据      
       2、计算每个线程下载数据的开始和结束位置
          再次发送请求,用 Range 头请求开始位置和结束位置的数据
       3、将下载到的数据,存放至临时文件中
       4、带断点续传的多线程下载

          定义一个int变量,记录每条线程下载的数据总长度,然后加上该线程的下载开始位置,得到的结果就是下次下载时,该线程的开始位置,把得到的结果存入缓存文件,当文件下载完成,删除临时进度文件。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
public class MultiDownload {
   static int ThreadCount = ;
   static int finishedThread = ;
   //确定下载地址
   static String filename = "EditPlus.exe";
   static String path = "http://...:/"+filename;
   public static void main(String[] args) {
     //、发送get请求,去获得下载文件的长度
    try {
      URL url = new URL(path);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setRequestMethod("GET");
      conn.setConnectTimeout();
      conn.setReadTimeout();
      if (conn.getResponseCode()==) {
        //如果请求成功,拿到所请求资源文件的长度
        int length = conn.getContentLength();
        //、生成一个与原文件同样的大小的临时文件,以免下载一半存储空间不够了
        File file = new File(filename);//演示,所以将保存的文件目录放在工程的同目录
        //使用RandomAccessFile 生成临时文件,可以用指针定位文件的任意位置,
        //而且能够实时写到硬件底层设备,略过缓存,这对下载文件是突然断电等意外是有好处的
        RandomAccessFile raf = new RandomAccessFile(file, "rwd");//rwd, 实时写到底层设备
        //设置临时文件的大小
        raf.setLength(length);
        raf.close();
        //、计算出每个线程应该下载多少个字节
        int size = length/ThreadCount;//如果有余数,负责最后一部分的线程负责下砸
        //开启多线程
        for (int threadId = ; threadId < ThreadCount; threadId++) {
          //计算每个线程下载的开始位置和结束位置
          int startIndex = threadId*size; // 开始 = 线程id * size
          int endIndex = (threadId+)*size - ; //结束 = (线程id + )*size -
          //如果是最后一个线程,那么结束位置写死为文件结束位置
          if (threadId == ThreadCount - ) {
            endIndex = length - ;
          }
          //System.out.println("线程"+threadId+"的下载区间是: "+startIndex+"----"+endIndex);
          new DownloadThread(startIndex,endIndex,threadId).start();
        }
      }
    } catch (MalformedURLException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}
class DownloadThread extends Thread{
  private int startIndex;
  private int endIndex;
  private int threadId;
  public DownloadThread(int startIndex, int endIndex, int threadId) {
    super();
    this.startIndex = startIndex;
    this.endIndex = endIndex;
    this.threadId = threadId;
  }
   public void run() {
     //每个线程再次发送http请求,下载自己对应的那部分数据
    try {
      File progressFile = new File(threadId+".txt");
      //判断进度文件是否存在,如果存在,则接着断点继续下载,如果不存在,则从头下载
      if (progressFile.exists()) {
        FileInputStream fis = new FileInputStream(progressFile);
        BufferedReader br = new BufferedReader(new InputStreamReader(fis));
        //从进度文件中度取出上一次下载的总进度,然后与原本的开始进度相加,得到新的开始进度
        startIndex += Integer.parseInt(br.readLine());
        fis.close();
      }
      System.out.println("线程"+threadId+"的下载区间是:"+startIndex+"----"+endIndex);
      //、每个线程发送http请求自己的数据
      URL url = new URL(MultiDownload.path);
      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      conn.setRequestMethod("GET");
      conn.setConnectTimeout();
      conn.setReadTimeout();
      //设置本次http请求所请求的数据的区间
      conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
      //请求部分数据,响应码是
      if (conn.getResponseCode()==) {
        //此时,流里只有ThreadCount分之一的原文件数据
        InputStream is = conn.getInputStream();
        byte[] b = new byte[];
        int len = ;
        int total = ;//total 用于保存断点续传的断点
        //拿到临时文件的输出流
        File file = new File(MultiDownload.filename);
        RandomAccessFile raf = new RandomAccessFile(file, "rwd");
        //把文件的写入位置移动至 startIndex
        raf.seek(startIndex);
        while ((len = is.read(b))!=-) {
          //每次读取流里数据之后,同步把数据写入临时文件
          raf.write(b, , len);
          total += len;
          //System.out.println("线程" + threadId + "下载了" + total);
          //生成一个一个专门用来记录下载进度的临时文件
          RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");
          progressRaf.write((total+"").getBytes());
          progressRaf.close();
        }
        System.out.println("线程"+threadId+"下载完了---------------------");
        raf.close();
        //当所有的线程下载完之后,将进度文件删除
        MultiDownload.finishedThread++;
        synchronized (MultiDownload.path) {//所有线程使用同一个锁
          if (MultiDownload.finishedThread==MultiDownload.ThreadCount) {
            for (int i = ; i < MultiDownload.ThreadCount; i++) {
              File f = new File(i+".txt");
              f.delete();
            }
            MultiDownload.finishedThread=;
          }
        }
      
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

二、Android手机版带断点续传的多线程下载

     Android手机版的带断点续传的多线程下载逻辑与PC版的几乎一样,只不过在Android手机中耗时操作不能放在主线程,网络下载属于耗时操作,所以多线程下载要在Android中开启一个子线程执行。并使用消息队列机制刷新文本进度条。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
public class MainActivity extends Activity {
  static int ThreadCount = ;
  static int FinishedThread = ;
  int currentProgess;
  static String Filename = "QQPlayer.exe";
  static String Path = "http://...:/"+Filename;
  static MainActivity ma;
  static ProgressBar pb;
  static TextView tv;
  static Handler handler = new Handler(){
    public void handleMessage(android.os.Message msg){
      tv.setText((long)pb.getProgress()* /pb.getMax() +"%");
    };
  };
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ma = this;
    pb = (ProgressBar) findViewById(R.id.pb);
    tv = (TextView) findViewById(R.id.tv);
  }
  public void download(View v){
    Thread t = new Thread(){
      public void run() {
        //发送http请求获取文件的长度,创建临时文件
        try {
          URL url= new URL(Path);
          HttpURLConnection conn = (HttpURLConnection) url.openConnection();
          conn.setRequestMethod("GET");
          conn.setConnectTimeout();
          conn.setReadTimeout();
          if (conn.getResponseCode()==) {
            int length = conn.getContentLength();
            //设置进度条的最大值就是原文件的总长度
            pb.setMax(length);
            //生成一个与原文件相同大小的临时文件
            File file = new File(Environment.getExternalStorageDirectory(),Filename);
            RandomAccessFile raf = new RandomAccessFile(file, "rwd");
            raf.setLength(length);
            raf.close();
            //计算每个线程需要下载的数据大小
            int size = length/ThreadCount;
            //开启多线程
            for (int threadId = ; threadId < ThreadCount; threadId++) {
              int startIndex = threadId*size;
              int endIndex = (threadId + )*size - ;
              if (threadId==ThreadCount - ) {
                endIndex = length - ;
              }
              new DownloadThread(startIndex, endIndex, threadId).start();
            }
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    };
    t.start();
  }
  class DownloadThread extends Thread{
    private int startIndex;
    private int endIndex;
    private int threadId;
    public DownloadThread(int startIndex, int endIndex, int threadId) {
      super();
      this.startIndex = startIndex;
      this.endIndex = endIndex;
      this.threadId = threadId;
    }
    public void run() {
      // 每个线程发送http请求自己的数据
      try{
        //先判断是不是断点续传
        File progessFile = new File(Environment.getExternalStorageDirectory(),threadId+".txt");
        if (progessFile.exists()) {
          FileReader fr = new FileReader(progessFile);
          BufferedReader br = new BufferedReader(fr);
          int lastProgess = Integer.parseInt(br.readLine());
          startIndex += lastProgess;
          //把上次下载的进度显示至进度条
          currentProgess +=lastProgess;
          pb.setProgress(currentProgess);
          //发消息,让主线程刷新文本进度
          handler.sendEmptyMessage();
          br.close();
          fr.close();
        }
        URL url = new URL(Path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout();
        conn.setReadTimeout();
        conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
        if (conn.getResponseCode()==) {
          InputStream is = conn.getInputStream();
          byte[] buffer = new byte[];
          int len = ;
          int total = ;
          File file = new File(Environment.getExternalStorageDirectory(),Filename);
          RandomAccessFile raf = new RandomAccessFile(file, "rwd");
          raf.seek(startIndex);
          while ((len = is.read(buffer))!= -) {
            raf.write(buffer, , len);
            total += len;
            //每次读取流里数据之后,把本次读取的数据的长度显示至进度条
            currentProgess += len;
            pb.setProgress(currentProgess);
            //发消息,让主线程刷新文本进度
            handler.sendEmptyMessage();
            //生成临时文件保存下载进度,用于断点续传,在所有线程现在完毕后删除临时文件
            RandomAccessFile progressRaf = new RandomAccessFile(progessFile, "rwd");
            progressRaf.write((total+"").getBytes());
            progressRaf.close();
          }
          raf.close();
          System.out.println("线程"+threadId+"下载完了");
          //当所有线程都下在完了之后,删除临时进度文件
          FinishedThread++;
          synchronized (Path) {
            if (FinishedThread==ThreadCount) {
              for (int i = ; i < ThreadCount; i++) {
                File f = new File(Environment.getExternalStorageDirectory(),i+".txt");
                f.delete();
              }
              FinishedThread=;
            }
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}

以上内容是小编跟大家分享的PC版与Android手机版带断点续传的多线程下载,希望大家喜欢。

延伸 · 阅读

精彩推荐