且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Android Day05-网络编程之文件下载之多线程断点续传技术

更新时间:2022-08-21 21:08:51

文件下载之多线程断点续传技术底层实现

 通过HttpURLConnection连接

 断点续传核心步骤:      

  1.UI设计

  2.获取服务器文件的大小,通过连接的一个方法getContentLength()来得到。

  3.在客户端创建一个和将要下载的文件的同样大小的同名文件。

  4.计算每个线程的起始位置和结束位置

  5.开启多个线程,采用RandomAccessFile类,根据计算得到的起始位置和结束位置来随机的读取服务

   器资源的输出流,并且实时的采用RandomAccessFile类保存线程读取到的字节位置。

  6.判断线程结束和文件上传成功。

  断点续传具体实现:  

  1.UI设计

    下载主界面设计

   Android Day05-网络编程之文件下载之多线程断点续传技术

     进度条单独存放在一个布局文件中,注意进度条要采用以下的样式的进度条。

     Android Day05-网络编程之文件下载之多线程断点续传技术    

  2.代码逻辑

     多线程断点续传的技术可以说是目前学Android课程以来最难、代码最长的一个案例,那么如何

   有章有序的将代码写出来呢?

     思路整理

    1)网络访问权限和SD卡访问权限的添加

     2)初始化动作及下载点击事件的书写Android Day05-网络编程之文件下载之多线程断点续传技术 

     3)随机读取文件线程类的定义

      Android Day05-网络编程之文件下载之多线程断点续传技术  

     具体代码:   

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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
    // 注册点击事件
    public void click(View v) {
        path = et_path.getText().toString().trim();
        String threadcount_str = et_threadcount.getText().toString().trim();
        final int threadcount = Integer.parseInt(threadcount_str);
 
         
        if (TextUtils.isEmpty(path) || threadcount < 0) {
            Toast.makeText(getApplicationContext(), "输入错误"0).show();
            return;
        }
 
        list_pbBars = new ArrayList<ProgressBar>();
        // 先清空进度条布局
        ll_layout.removeAllViews();
        // 添加进度条
        for (int i = 0; i < threadcount; i++) {
            //得到进度条的View对象
            View item_pb_View = View.inflate(getApplicationContext(),R.layout.item_progress, null);
            //通过View再得到进度条
            ProgressBar pb_bar = (ProgressBar) item_pb_View.findViewById(R.id.item_pb);
            list_pbBars.add(pb_bar);
            ll_layout.addView(item_pb_View);
        }
        try {
            //将服务器文件路径用File包装一下,以便获得它的文件名。
            sourceFile = new File(path);
             
            //访问URL得到文件的大小 ,通过Content-Length得到。
            new Thread(){
                public void run()
                {
                    System.out.println("xxx"+Thread.currentThread().getName());
                    try {
                        //创建HttpURLConnection
                        URL url = new URL(path);
                         
                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                         
                        conn.setRequestMethod("GET");
                        //获取状态码
                        int responseCode = conn.getResponseCode();
                        if(responseCode == 200)
                        {
                            //获取资源文件的长度
                            sourceFileLength = conn.getContentLength();
                             
                             
                            sourceFileName = sourceFile.getName();
                            clientFile = new RandomAccessFile(Environment.getExternalStorageDirectory() + "/"
                                            + sourceFileName, "rw");
                            clientFile.setLength(sourceFileLength);
                             
                             
                            //正在运行的线程
                            runningThreadCount = threadcount;
                            // 第3步: 计算每个线程读取文件的起始和结束位置
                            int blockSize = sourceFileLength / threadcount;
                            for (int i = 0; i < threadcount; i++) {
                                //得到每个线程读取的起始和结束位置
                                int start_index = i * blockSize;
                                int end_index = (i + 1) * blockSize - 1;
                                int last_index = -1;
                                if(i == threadcount - 1)
                                {
                                    end_index = sourceFileLength -1;
                                }
                                //------如果采用了断点续传,线程的起始位置就是上次记录位置。
                                 
                                File recordIndexFile = new File(Environment.getExternalStorageDirectory().getPath() + "/" +i + ".txt");
                                 
                                //如果有断点文件,就得到上次记录的读取位置。
                                if(recordIndexFile.exists() && recordIndexFile.length() > 0)
                                {
                                    BufferedReader bufr = new BufferedReader(new FileReader(recordIndexFile));
                                    last_index = Integer.parseInt(bufr.readLine());
//                                  last_index++;
                                    bufr.close();
                                }
                                else  //否则上次读取位置就是开始位置。
                                {
                                    last_index = start_index;
                                }
                                //设置进度条的总长度
                                list_pbBars.get(i).setMax((int)(end_index - start_index));
                                 
                                System.out.println("线程"+i +"::"+ last_index + "----" + end_index);
                                // 第4步:启动线程
                                new DownloadThread(i, start_index, end_index,last_index).start();
                            }
                             
                             
                             
                        }
                        else
                        {
                            showToast("请求不到资源 ");
                            return;
                        }
                    catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }.start();
             
             
             
             
                 
            // 第5步:判断线程结束
 
        catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    // 定义下载的线程类
    class DownloadThread extends Thread {
        private int threadID;
        private int  start_index, end_index,last_index;
        private int total = 0;      //记录已经读写的总的字节数
        public DownloadThread(int threadID, int start_index, int end_index,int last_index) {
            this.threadID = threadID;
            this.start_index = start_index;
            this.end_index = end_index;
            this.last_index = last_index;
            this.setName("线程" + threadID);
        }
 
        public void run() {
            try {
                //创建HttpURLConnection
                URL url = new URL(path);
                 
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                 
                conn.setRequestMethod("GET");
//              conn.setReadTimeout(5);
                 
                //设置一个头信息Range,表示要读取的流的字节范围。
                conn.setRequestProperty("Range""bytes=" + last_index + "-" + end_index);
                 
                 
                 
                //获取状态码
                int responseCode = conn.getResponseCode();
                //注意多线程下载状态得用206来判断
                if(responseCode == 206)
                {
                    //得到服务器的输出流
                    InputStream serverIn = conn.getInputStream();
                    //得到客户端的文件输出流
                    RandomAccessFile myClientFile = new RandomAccessFile(
                            Environment.getExternalStorageDirectory() + "/"
                                    + sourceFileName, "rwd");
                    myClientFile.seek(last_index);
                    //读取服务器文件写入到客户端文件中
                    //断点续传文件
                     
                    int len = 0;
                    byte[] buf = new byte[1024*1024];
                    while((len = serverIn.read(buf))!= -1)
                    {
                        myClientFile.write(buf,0,len);
                        total += len;
                        //实时存储当前读取到的文件位置。
                         int current_index = last_index + total;
                        //因为此处实时的存储读取文件的位置,将缓冲区buf稍微弄大点。
                         
                        // 改
                        /***注意读取文件位置的流一定要用RandomAccessFile,因为它可以直接同步到缓存(磁盘上)。***/
                        RandomAccessFile raffAccessFile = new RandomAccessFile(Environment.getExternalStorageDirectory().getPath()+"/"+threadID+".txt""rwd");
                        raffAccessFile.write(String.valueOf(current_index).getBytes());
                        raffAccessFile.close();
                        //设置进度条的当前进度
                        list_pbBars.get(threadID).setProgress((int)(total + last_index - start_index));
                    }
                    myClientFile.close();
                    serverIn.close();
                     
                     
                    //操作共享资源,注意加锁。
                    synchronized (DownloadThread.class) {
                        runningThreadCount--;
                        if(runningThreadCount == 0)
                        {
                            Toast.makeText(getApplicationContext(), "文件下载完毕"0).show();
                        }
                    }
                     
                }
                else 
                {
                    showToast("服务器忙");
                }
            catch (Exception e) {
                e.printStackTrace();
            }
             
        }
    }

   


      本文转自屠夫章哥  51CTO博客,原文链接:http://blog.51cto.com/4259297/1676571,如需转载请自行联系原作者