笔沫

有梦为马,随处可栖

  • 主页
  • 技术
  • 随想
  • 音乐
所有文章 其他

笔沫

有梦为马,随处可栖

  • 主页
  • 技术
  • 随想
  • 音乐

HTTP文件断点续传原理解析

2017-09-08

生活中,有许多事物,在没有被揭开面纱之前,我们往往会觉得很神秘很高深,认为它一定很难,进而望而却步,失去了解它的机会。然而,很多事,只要我们自己能沉下心来,细细研究,那些神秘高深的,也会变得简单明了。”HTTP文件断点续传”就是这样一个好例子,深入了解背后之理,“HTTP文件断点续传原理”其实很简单。

一、什么是断点续传

1.定义:

可以从下载或上传断开点继续开始传输,就叫断点续传。

2.核心实现原理:

i.RandomAccessFile(文件任意位置保存)
方法seek():可以移动到保存文件任意位置,在该位置发生下一个读取或写入操作

ii.HttpURLConnection.setRequestProperty()(任意位置请求返回剩余文件)
HttpURLConnection.setRequestProperty(“Range”, “bytes=” + start + “-“ + end)

二、实例分析

流程图

实现步骤

  • 1.建立数据库:保存文件下载信息
  • 2.下载服务类(DownloadService)
  • 3.两个线程:文件信息线程(FileInfoThread)和文件下载线程(DownloadThread)
  • 4.广播(BroadcastReceiver):UI进度更新

1.建立数据库
按常规数据库建立方法,具体(略)。数据保存信息为:

1
2
3
4
5
6
7
8
9
10
/**
* 下载信息类
*/
public class DownloadInfo {
private int id;
private String url;//下载链接
private long start;//开始大小
private long end;//最终大小
private long progress;//下载进度
}

2.下载服务类
利用service多次启动只调用onStartCommand()方法,处理开始或暂停下载逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getAction().equals(ACTION_START)) {
FileInfo fileInfo = (FileInfo) intent.getSerializableExtra(TAG_FILEINFO);
mFileInfoThread = new FileInfoThread(fileInfo,mHandler);
mFileInfoThread.start();
} else if (intent.getAction().equals(ACTION_PAUSE)) {
if (mDownloadThread != null) {
mDownloadThread.setPause(true);
}
}
return super.onStartCommand(intent, flags, startId);
}

3.两个线程
i.文件信息线程(FileInfoThread)

通过网络获取下载文件大小,并建立对应大小的保存文件路径。

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
HttpURLConnection conn = null;
RandomAccessFile raf = null;
try {
URL url = new URL(mFileInfo.getUrl());
conn = (HttpURLConnection) url.openConnection();//连接网络文件
conn.setConnectTimeout(3000);
conn.setRequestMethod("GET");
int length = -1;
Log.e(TAG,"HttpResponseCode=="+ conn.getResponseCode() + "");
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
//获取文件长度
length = conn.getContentLength();
}
if (length < 0) {
return;
}
File dir = new File(DownloadService.DOWNLOAD_PATH);
if (!dir.exists()) {
dir.mkdir();
}
//在本地创建文件
File file = new File(dir, mFileInfo.getFileName());
raf = new RandomAccessFile(file, "rwd");
//设置本地文件长度
raf.setLength(length);
mFileInfo.setLength(length);
Log.e(TAG,"下载文件大小(size)"+ mFileInfo.getLength() + "");
mHandler.obtainMessage(DownloadService.MSG_FILEINFO, mFileInfo).sendToTarget();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null && raf != null) {
raf.close();
conn.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}

ii.文件下载线程(DownloadThread)
断点续传原理核心类。
1.判断下载进度是否有保存,若无,数据插入一条数据。

1
2
3
if (!mDatabaseOperation.isExists(downloadInfo.getUrl(), downloadInfo.getId())) {
mDatabaseOperation.insert(downloadInfo);
}

2.设置网络请求Range参数,从请求位置返回数据

1
2
3
//设置下载位置
long start = downloadInfo.getStart() + downloadInfo.getProgress();
connection.setRequestProperty("Range", "bytes=" + start + "-" + downloadInfo.getEnd());

3.通过RandomAccessFile从进度保存位置保存文件

1
2
3
4
5
6
7
RandomAccessFile raf;
File file = new File(DownloadService.DOWNLOAD_PATH, mFileInfo.getFileName());
raf = new RandomAccessFile(file, "rwd");
raf.seek(start);
...
//写入文件
raf.write(buffer, 0, len);

4.用户暂停时,保存下载进度

1
2
3
4
5
6
//下载暂停时,保存进度
if (isPause) {
Log.e(TAG,"保存进度文件(size):"+progress + "");
mDatabaseOperation.update(mFileInfo.getUrl(), mFileInfo.getId(), progress);
return;
}

4.广播(BroadcastReceiver):
每秒广播一次,刷新UI

1
2
3
4
5
6
7
long time = System.currentTimeMillis();
......
if (System.currentTimeMillis() - time > 1000) {//超过一秒,就刷新UI
time = System.currentTimeMillis();
sendBroadcast(intent,(int)(progress * 100 / mFileInfo.getLength()));
Log.e(TAG,"进度:" + progress * 100 / mFileInfo.getLength() + "%");
}

DownloadThread类源码:

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
/**
* 文件下载线程
* Created by AwenZeng on 2017/9/6.
*/
public class DownloadThread extends Thread {
private DownloadInfo downloadInfo;
private FileInfo mFileInfo;
private long progress = 0;
private boolean isPause;
private DatabaseOperation mDatabaseOperation;
private Context mContext;
private static final String TAG = "DownloadThread";
public DownloadThread(Context context, DatabaseOperation databaseOperation, DownloadInfo threadInfo, FileInfo fileInfo) {
this.downloadInfo = threadInfo;
mContext = context;
mDatabaseOperation = databaseOperation;
mFileInfo = fileInfo;
}
public void setPause(boolean pause) {
isPause = pause;
}
@Override
public void run() {
if (!mDatabaseOperation.isExists(downloadInfo.getUrl(), downloadInfo.getId())) {
mDatabaseOperation.insert(downloadInfo);
}
HttpURLConnection connection;
RandomAccessFile raf;
InputStream is;
try {
URL url = new URL(downloadInfo.getUrl());
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(3000);
connection.setRequestMethod("GET");
//设置下载位置
long start = downloadInfo.getStart() + downloadInfo.getProgress();
connection.setRequestProperty("Range", "bytes=" + start + "-" + downloadInfo.getEnd());
//设置文件写入位置
File file = new File(DownloadService.DOWNLOAD_PATH, mFileInfo.getFileName());
raf = new RandomAccessFile(file, "rwd");
raf.seek(start);
progress += downloadInfo.getProgress();
Log.e(TAG,"下载文件进度(size):"+ downloadInfo.getProgress() + "");
Log.e(TAG,"HttpResponseCode ==="+connection.getResponseCode() + "");
//开始下载
if (connection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL) {
Log.e(TAG,"剩余文件(size):"+connection.getContentLength() + "");
Intent intent = new Intent(DownloadService.ACTION_UPDATE);//广播intent
is = connection.getInputStream();
byte[] buffer = new byte[1024 * 4];
int len = -1;
long time = System.currentTimeMillis();
while ((len = is.read(buffer)) != -1) {
//下载暂停时,保存进度
if (isPause) {
Log.e(TAG,"保存进度文件(size):"+progress + "");
mDatabaseOperation.update(mFileInfo.getUrl(), mFileInfo.getId(), progress);
return;
}
//写入文件
raf.write(buffer, 0, len);
//把下载进度发送广播给Activity
progress += len;
if (System.currentTimeMillis() - time > 1000) {//超过一秒,就刷新UI
time = System.currentTimeMillis();
sendBroadcast(intent,(int)(progress * 100 / mFileInfo.getLength()));
Log.e(TAG,"进度:" + progress * 100 / mFileInfo.getLength() + "%");
}
}
sendBroadcast(intent,100);
/**
* 删除下载信息(重新下载)
*/
mDatabaseOperation.delete(mFileInfo.getUrl(), mFileInfo.getId());
is.close();
}
raf.close();
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendBroadcast(Intent intent,int progress){
intent.putExtra("progress",progress);
mContext.sendBroadcast(intent);
}
}

三、源码地址

如果你觉得还不错,欢迎star或fork。
https://github.com/awenzeng/BreakPointDemo

四、参考文献

RandomAccessFiley详解

http断点续传原理:http头 Range、Content-Range

InputStream中read()与read(byte[] b)

赏

感谢认可,么么哒

支付宝
微信
  • 技术
  • Android
  • Http文件断点续传

扫一扫,分享到微信

微信分享二维码
Java集合类详解
你本是一个肉体,是什么驱使你前行[3]
© 2019 笔沫
Hexo Theme Yilia by Litten
  • 所有文章
  • 其他

tag:

  • 技术
  • Android
  • Activity的启动模式
  • Android注解
  • Java反射机制
  • Java动态代理
  • Android基础
  • MVP
  • NDK
  • JNI C/C++
  • 开源框架
  • AS Gradle优化
  • 博客搭建
  • 设计模式
  • Fragment
  • Rxjava
  • Rxandroid
  • 响应式编程
  • 随想
  • 你本是一个肉体,是什么驱使你前行
  • 原创
  • 笔沫拾光
  • Java
  • Java基础
  • 王阳明心学
  • 中国历史
  • 人生的意义
  • 演讲
  • 执着的人是幸福的
  • 郭川
  • Android框架源码解析
  • 加解密算法
  • Binder通信机制
  • 开源框架源码解析
  • LeakCanary框架源码分析
  • Java集合类
  • 转载
  • Http文件断点续传
  • Logger框架源码解析
  • Android应用程序入口源码解析
  • DecorView绘制流程
  • Android消息机制源码解析
  • Activity启动流程
  • Butterknife框架源码解析
  • 我之存在,因为有你
  • 霍华德*舒尔茨
  • Android相机
  • Camera
  • 悬浮窗
  • WindowManager

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • 风光摄影
  • 星空摄影
  • 人像摄影
  • 学习站点