Android多线程断点续传

本文详细介绍了如何利用HTTP协议实现断点续传功能,并探讨了多线程下载的技术细节。通过分析HTTP/1.1协议中的Range请求头和响应头,解释了服务器如何支持断点续传。此外,还提供了使用Java实现断点续传的具体代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

断点续传

思考:

1.断点续传,就是当一次下载任务由于某些因素中断下载,能够继续上次下载的位置继续下载,不必在从头开始。

2.支持多线程,就是把一个下载任务分配给多个线程去下载,每一个线程只下载一部分内容。

分析:

http协议,一次下载请求,就是请求远端服务器一个资源(url),建立TCP链接,把服务器资源写入本地的一个过程。

那么如果要实现断点续传的必要条件:

1.我们能够知道下载资源的大小(多少字节)
2.我们可以请求某一部分资源(一个文件的某一段字节数据)
3.服务器告诉我们返回的资源总大小和偏移量(从某一位置开始返回数据)

查找http协议规范

http/1.1请求头中有下面相关字段

请求头
GET /file.zip HTTP/1.1 
Range:bytes=680- // 从某一个位置开始请求
...


响应头
HTTP/1.1 206 Partial Content
Accept-Ranges : bytes ///告诉客户端,我们是支持断点传输的
Content-Length : //返回文件总长度(少字节)
Content-Range :bytes //返回的字节范围
...

模拟一次单线程断点续传:

1.客户端发起请求,下载file.zip

GET /file.zip HTTP/1.1 

2.服务器收到请求返回

HTTP/1.1 200 OK
Accept-Ranges : bytes ///告诉客户端,我们是支持断点传输的
Content-Length : 2000 //返回文件总长度(2000字节)
...

3.客户端在下载到600字节的时候突然间中断了下载链接,然后再次发起续传请求

GET /file.zip HTTP/1.1 
Range:bytes=600- // 告诉服务器我从600字节开始下载数据
...

4.服务器收到请求给予回应

HTTP/1.1 206 Partial Content // 注意响应码206,不是200
Accept-Ranges : bytes ///告诉客户端,我们是支持断点传输的
Content-Length : 1400//返回剩余未下载文件总长度
Content-Range :bytes 600-1399//返回的字节范围
...

多线程的下载其实就是,在第一次请求只拿到要下载文件的总大小,然后计算分配给几个线程,每个线程具体下载哪一段数据,然后在去请求对应的数据。

代码设计与实现:

相关技术介绍:

1.HttpURLConnection

URL url = new URL(mFileInfo.getUrl());//请求的链接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();//打开链接
conn.setConnectTimeout(5 * 1000);//设置超时时间
conn.setRequestMethod("GET");//设置请求方法
conn.setRequestProperty("Range", "bytes=" + start + "-" +end);// 设置下载文件开始到结束的位置
int code = conn.getResponseCode();//拿到响应码
conn.getInputStream();// 拿到输入流

2.RandomAccessFile

RandomAccessFile是不属于InputStream和OutputStream类系的。实际上,除了实现DataInput和DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和这两个类系毫不相干,甚至都没有用InputStream和OutputStream已经准备好的功能;它是一个完全独立的类,所有方法(绝大多数都只属于它自己)都是从零开始写的。这可能是因为RandomAccessFile能在文件里面前后移动,所以它的行为与其它的I/O类有些根本性的不同。总而言之,它是一个直接继承Object的,独立的类。

http://www.android-doc.com/reference/java/io/RandomAccessFile.html

void    seek(long offset)//移动到某一位置,多线程下载(每个线程写入位置都不同)
代码设计思路(多线程下载支持断点续传)
1.使用数据库来存储每一个线程信息的下载信息,(线程id,下载的url,起始下载位置,终止下载位置,当前下载进度)
2.第一次请求的时候,计算好每一个线程需要下载的数据块,存入数据库,并在每一次写入数据时,修改进度
3.还原下载的时候,从数据库拿到这些信息,接着未完成的请求。
4.如果当前线程完成自己的任务,就从数据库里面移除
核心代码

获取文件大小

HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            try {
                URL url = new URL(mFileInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(5 * 1000);
                conn.setRequestMethod("GET");
                int code = conn.getResponseCode();
                int length = -1;
                if (code == HttpURLConnection.HTTP_OK) {
                    length = conn.getContentLength();
                }
                //如果文件长度为小于0,表示获取文件失败,直接返回
                if (length <= 0) {
                    return;
                }
                // 判断文件路径是否存在,不存在创建
                File dir = new File(DownloadPath);
                if (!dir.exists()) {
                    dir.mkdir();
                }
                // 创建本地文件
                File file = new File(dir, mFileInfo.getFileName());
                raf = new RandomAccessFile(file, "rwd");
                raf.setLength(length);
                // 设置文件长度
                mFileInfo.setLength(length);
                // 将FileInfo对象传给Handler
                Message msg = Message.obtain();
                msg.obj = mFileInfo;
                msg.what = MSG_INIT;
                mHandler.sendMessage(msg);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (conn != null) {
                    conn.disconnect();
                }
                try {
                    if (raf != null) {
                        raf.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

下载线程任务

HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            InputStream is = null;
            try {
                URL url = new URL(mFileInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(5 * 1000);
                conn.setRequestMethod("GET");

                long start = threadInfo.getStart() + threadInfo.getFinished();
                // 设置下载文件开始到结束的位置
                conn.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd());
                File file = new File(DownloadService.DownloadPath, mFileInfo.getFileName());
                raf = new RandomAccessFile(file, "rwd");
                // 设置文件偏移量
                raf.seek(start);
                mFinished += threadInfo.getFinished();
                Log.d("xzq","下载起始位置 = "+start+", mFinished = "+mFinished);
                Intent intent = new Intent();
                intent.setAction(DownloadService.ACTION_UPDATE);
                int code = conn.getResponseCode();
                if (code == HttpURLConnection.HTTP_PARTIAL) {
                    is = conn.getInputStream();
                    byte[] bt = new byte[1024];
                    int len = -1;

                    while ((len = is.read(bt)) != -1) {
                        raf.write(bt, 0, len);
                        // 累计整个文件完成进度
                        mFinished += len;
                        // 累加每个线程完成的进度
                        threadInfo.setFinished(threadInfo.getFinished() + len);

                        if (mIsPause) {
                            mDao.updateThread(threadInfo.getUrl(), threadInfo.getId(), threadInfo.getFinished());
                            return;
                        }
                    }

                }
                // 标识线程是否执行完毕
                isFinished = true;
                // 判断是否所有线程都执行完毕
                checkAllFinished();

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (conn != null) {
                    conn.disconnect();
                }
                try {
                    if (is != null) {
                        is.close();
                    }
                    if (raf != null) {
                        raf.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
DEMO下载

完整DEMO下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值