加入收藏 | 设为首页 | 会员中心 | 我要投稿 北几岛 (https://www.beijidao.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

安卓开发笔记——关于文件断点续传

发布时间:2021-07-06 06:08:00 所属栏目:大数据 来源: https://www.jb51.cc
导读:什么是断点续传? 客户端软件断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必

什么是断点续传?

客户端软件断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,节省流量,也提高速度。

?

我写了个小Demo,先看下实现效果图:

?

?

断点续传的原理?

断点续传其实并没有那么神秘,它只不过是利用了HTTP传输协议中请求头(REQUEST HEADER)的不同来进行断点续传的。

?

以下是我刚在百度音乐下载MP3的时候用Firebug截下来的HTTP请求头:

Accept:image/webp,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-CN,zh;q=0.8
Connection:keep-alive
Cookie:BIDUPSID=6B8AF721169ED82B182A7EE22F75BB87; BAIDUID=6B8AF721169ED82B182A7EE22F75BB87:FG=1; BDUSS=1pWS14dzl6Ry02MVJoN0toT1RlTzRIdkdBRVlsN1JJdG9OVmQ5djAybTJ1a1JWQVFBQUFBJCQAAAAAAAAAAAEAAACkSkgjTXJfTGVlX-fiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALYtHVW2LR1VaW; __xsptplus188=188.1.1428024707.1428024712.2%234%7C%7C%7C%7C%7C%23%23QdAfR9H5KZHSIGakiCWebLQCd6CjKjz5%23; locale=zh; cflag=65279%3A3; BDRCVFR[-z8N-kPXoJt]=I67x6TjHwwYf0; H_PS_PSSID=11099_13386_1439_13425_13075_10902_12953_12868_13320_12691_13410_12722_12737_13085_13325_13203_12835_13161_8498
Host:b.hiphotos.baidu.com
Referer:http://music.baidu.com/song/124380645
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/39.0.2171.95 Safari/537.36

这是百度音乐服务器给我返回的信息:

Age:347651
Cache-Control:max-age=31536000
Content-Length:16659
Content-Type:image/jpeg
Date:Sat,11 Apr 2015 06:48:45 GMT
Error-Message:OK
ETag:"10487865676202532488"
Expires:Wed,06 Apr 2016 06:02:50 GMT
Last-Modified:Tue,07 Apr 2015 05:45:32 GMT
Ohc-Cachable:1
Server:JSP3/2.0.7

这是一般的下载请求,服务器会给我们返回我们请求下载文件的长度(Content-Length),成功状态码为:200。

而断点续传是要从我们之前已经下载好的文件位置继续上一次的传输,顾名思义我们需要告诉服务器我们上一次已经下载到哪里了,所以在我们的HTTP请求头里要额外的包含一条信息,而这也就是断点续传的奥秘所在了。

这条信息为:(RANGE为请求头属性,X为从什么地方开始,Y为到什么地方结束)

RANGE: bytes=X-Y 

例如:

Range : 用于客户端到服务器端的请求,可通过该字段指定下载文件的某一段大小,及其单位。典型的格式如:
Range: bytes=0-499 下载第0-499字节范围的内容
Range: bytes=500-999 下载第500-999字节范围的内容
Range: bytes=-500 下载最后500字节的内容
Range: bytes=500- 下载从第500字节开始到文件结束部分的内容

?

以上是HTTP需要注意的地方,接下来讲讲Java里需要注意的几个类

关于这个HTTP请求,在Java的Net包下,给我们封装好了一系列的类,我们直接拿来使用就行了。

这是java.net.URLConnection类下的一个方法,用来设置HTTP请求头的,Key对应HTTP请求头属性,Value对应请求头属性的值。

这是java.io.RandomAccessFile类,这个类的实例支持对随机访问文件的读取和写入,这个类里有个很重要的seek(long pos)方法,

这个方法可以在你设置的长度的下一个位置进行写入,例如seek(599),那么系统下一次写入的位置就是该文件的600位置。

?

既然是断点续传,那么我们自身肯定也要记录断点位置,所以这里数据库也是必不可少的。

?

具体实现:

好了,了解了上面的知识点后,可以开始动手写程序了。

下面是我写的一个小Demo,用单线程来实现的断点续传的,注释非常全。

?这是Demo的结构图:(实在不会画图,凑合着看哈)

主类(Activity):

  1 package com.example.ui;
  2 
  3 import android.app.Activity;
  4  android.app.AlertDialog;
  5  android.app.ProgressDialog;
  6  android.app.AlertDialog.Builder;
  7  android.content.BroadcastReceiver;
  8  android.content.Context;
  9  android.content.DialogInterface;
 10  android.content.Intent;
 11  android.content.IntentFilter;
 12  android.os.Bundle;
 13  android.util.Log;
 14  android.view.View;
 15  android.view.View.OnClickListener;
 16  android.widget.Button;
 17  android.widget.ProgressBar;
 18  android.widget.TextView;
 19 
 20  com.example.downloadfiletest.R;
 21  com.example.entity.FileInfo;
 22  com.example.logic.DownloadService;
 23 
 24 public class MainActivity extends Activity {
 25 
 26     private TextView textView;
 27      ProgressBar progressBar;
 28      Button bt_start;
 29      Button bt_stop;
 30 
 31     @Override
 32     protected void onCreate(Bundle savedInstanceState) {
 33         super.onCreate(savedInstanceState);
 34         setContentView(R.layout.activity_main);
 35         initView(); // 初始化控件
 36 
 37          注册广播接收者
 38         IntentFilter intentFilter = new IntentFilter();
 39         intentFilter.addAction(DownloadService.UPDATE);
 40         registerReceiver(broadcastReceiver,intentFilter);
 41 
 42     }
 43 
 44  45      onDestroy() {
 46         .onDestroy();
 47          解绑
 48         unregisterReceiver(broadcastReceiver);
 49  50 
 51      初始化控件
 52     private  initView() {
 53         textView = (TextView) findViewById(R.id.textView);
 54         progressBar = (ProgressBar) findViewById(R.id.progressBar);
 55         bt_start = (Button) findViewById(R.id.bt_start);
 56         bt_stop = (Button) findViewById(R.id.bt_stop);
 57         progressBar.setMax(100);
 58 
 59         final FileInfo fileInfo = new FileInfo(0,"Best Of Joy.MP3-Michael Jackson","http://music.baidu.com/data/music/file?link=http://yinyueshiting.baidu.com/data2/music/38264529/382643441428735661128.mp3?xcode=46e7c02e3acba184b6145f688bb9f2422c866f9e4969f410&song_id=38264344",0 60 
 61          点击开始下载
 62         bt_start.setOnClickListener( OnClickListener() {
 63 
 64             @Override
 65              onClick(View v) {
 66                 Intent intent = new Intent(MainActivity.this,DownloadService.class 67                 intent.setAction(DownloadService.START);
 68                 intent.putExtra("FileInfo",fileInfo);
 69                 startService(intent);
 70                 textView.setText("正在下载文件:" + fileInfo.getFileName());
 71 
 72             }
 73         });
 74 
 75          点击停止下载
 76         bt_stop.setOnClickListener( 77 
 78  79              80                 Intent intent =  81                 intent.setAction(DownloadService.STOP);
 82                 intent.putExtra("FileInfo" 83  84                 textView.setText("任务已暂停,请点击下载继续" 85  86  87 
 88  89 
 90      广播接收者
 91     BroadcastReceiver broadcastReceiver =  BroadcastReceiver() {
 92 
 93         @Override
 94          onReceive(Context context,Intent intent) {
 95             if (intent.getAction().equals(DownloadService.UPDATE)) {
 96                 int finished = intent.getIntExtra("finished",1)"> 97                 progressBar.setProgress(finished);
 98                 
 99                 用户界面友好,提醒用户任务下载完成
100                 if (finished ==100) {
101                     AlertDialog.Builder builder=new AlertDialog.Builder(MainActivity.this102                     builder.setTitle("任务状态"103                     builder.setMessage("文件下载已完成!"104                     builder.setPositiveButton("确认", DialogInterface.OnClickListener() {
105                         
106                         @Override
107                         void onClick(DialogInterface dialog,1)">int which) {
108                                     progressBar.setProgress(0109                                     textView.setText("请点击下载"110                         }
111                     });
112                     builder.show();
113                 }
114 115         }
116     };
117 }

后台服务类(Service)

 com.example.logic;
 java.io.File;
 java.io.IOException;
 java.io.RandomAccessFile;
 java.net.HttpURLConnection;
 java.net.URL;
  8 
 org.apache.http.HttpStatus;
 org.apache.http.client.ClientProtocolException;
 11 
 android.app.Service;
 android.os.Environment;
 android.os.Handler;
 android.os.IBinder;
 18 
 19  20 
class DownloadService  Service {
 22 
 23      按钮标志符
 24     static final String START = "START";
 25     final String STOP = "STOP" 更新进度标志
final String UPDATE = "UPDATE" 下载路径(内存卡(SD)根目录下的/downloads/)
final String DOWNLOADPATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/downloads/" 30      定义初始化文件操作标志
 31     final int INIT = 0 32 
 33      DownloadTask downloadTask;
 34 
 35     private Handler handler =  Handler() {
 36          handleMessage(android.os.Message msg) {
 37             switch (msg.what) {
 38             case INIT:
 39                 FileInfo fileInfo = (FileInfo) msg.obj;
 40                 Log.i("init" 41                  进行下载任务操作
 42                 downloadTask = new DownloadTask(DownloadService. 43                 downloadTask.download();
 44                 break 45  46         };
 47  48 
 49     /**
 50      * 当Service启动时会被调用,用来接收Activity传送过来的数据
 51      */
 52  53     int onStartCommand(Intent intent,1)">int flags,1)"> startId) {
 54          (intent.getAction().equals(START)) {
 55              当点击开始下载操作时
 56              接收Activity(putExtra)过来的数据
 57             FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("FileInfo" 58             Log.i(START,1)"> 59             new Thread( InitFileThread(fileInfo)).start();
 61         } else  (intent.getAction().equals(STOP)) {
 62              当点击停止下载操作时
 63             FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("FileInfo"            Log.i(STOP,1)"> 暂定任务
 66             if (downloadTask != null 67                 downloadTask.flag = true 68  70 
 71         return .onStartCommand(intent,flags,startId);
 73 
 74  75     public IBinder onBind(Intent intent) {
 76          77  78 
 79      初始化文件操作获取网络资源大小长度,开辟子线程
 80     class InitFileThread implements Runnable {
 81 
 82          FileInfo fileInfo;
 83 
 84          构造方法,获取文件对象
 85          InitFileThread(FileInfo fileInfo) {
 86             this.fileInfo = fileInfo;
 87  88 
 89  90          run() {
 91             /*
 92              * 1、打开网络连接,获取文件长度 2、创建本地文件,长度和网络文件相等
 93               94             HttpURLConnection httpURLConnection =  95             RandomAccessFile randomAccessFile =  96             try {
 97                 URL url =  URL(fileInfo.getUrl());
 98                 httpURLConnection = (HttpURLConnection) url.openConnection();
 知识点:除了下载文件,其他一律用POST
100                 httpURLConnection.setConnectTimeout(3000101                 httpURLConnection.setRequestMethod("GET"102                  定义文件长度
103                 int length = -1104                  网络连接成功
105                 if (httpURLConnection.getResponseCode() == HttpStatus.SC_OK) {
106                     length = httpURLConnection.getContentLength();
107 108                  判断是否取得文件长度
109                 if (length <= 0110                     return112 
113                  创建文件目录对象
114                 File dir =  File(DOWNLOADPATH);
115                 if (!dir.exists()) {
116                      若目录不存在,创建
117                     dir.mkdir();
118 119                  创建文件对象
120                 File file =  File(dir,fileInfo.getFileName());
121                  创建随机访问文件流 参数二为权限:读写删
122                 randomAccessFile = new RandomAccessFile(file,"rwd"123                 randomAccessFile.setLength(length);
124                 fileInfo.setLength(length);
125                  发送handler
126                 handler.obtainMessage(INIT,fileInfo).sendToTarget();
127 
128             } catch (ClientProtocolException e) {
129                 e.printStackTrace();
130             }  (IOException e) {
131 132             } finally133                 if (randomAccessFile != 134                     135                         randomAccessFile.close();
136                     } 137                         e.printStackTrace();
138                     }
139 140 141 
142 143 
144 145 
146 }

下载任务类:

 java.io.InputStream;
 java.net.MalformedURLException;
 java.util.List;
 13 
 17 
 com.example.dao.ThreadDAO;
 com.example.dao.ThreadDAOImpl;
 com.example.entity.ThreadInfo;
 23  * 下载任务类
 25  * 
 26  * @author Balla_兔子
 27  28   29  DownloadTask {
 Context context;
 ThreadDAO dao;
 初始化下载进度,默认为0
 34     int finished = 0 35 
 36      是否暂停下载标识符
 37     boolean flag = false 38 
 39      DownloadTask(Context context,FileInfo fileInfo) {
 40         this.context = context;
 41          42         dao =  ThreadDAOImpl(context);
 44 
 download() {
 线程信息的url和文件的url对应
 47         List<ThreadInfo> threadInfos = dao.getThreadInfo(fileInfo.getUrl());
 48         ThreadInfo threadInfo =  49         if (threadInfos.size() == 0 50              若数据库无此线程任务
 51             threadInfo = new ThreadInfo(0,fileInfo.getUrl(),fileInfo.getLength(),1)"> 52         } else 53             threadInfo = threadInfos.get(0 54  55          创建子线程进行下载
 56          DownloadThread(threadInfo)).start();
 57  59      执行下载任务,开辟子线程
 60     class DownloadThread  61 
 62          ThreadInfo threadInfo;
 64          DownloadThread(ThreadInfo threadInfo) {
this.threadInfo = threadInfo;
 66  67 
 69          70             HttpURLConnection urlConnection =  71             InputStream inputStream =  72             RandomAccessFile randomAccessFile =  74              75              * 执行下载任务 
 76              * 1、查询数据库,确定是否已存在此下载线程,便于继续下载 
             * 2、设置从哪个位置开始下载 
             * 3、设置文件的写入位置
 79              * 4、开始下载 
 80              * 5、广播通知UI更新下载进度 
             * 6、暂停线程的操作 
 82              * 7、下载完毕,删除数据库信息
 83               84              1、查询数据库
 85             dao.isExists(threadInfo.getUrl(),threadInfo.getThread_id())) {
 86                  若不存在,插入新线程信息
                dao.insertThread(threadInfo);
 90              2、设置下载位置
 92                 URL url =  URL(threadInfo.getUrl());
 93                 urlConnection = 94                  设置连接超时时间
 95                 urlConnection.setConnectTimeout(3000 96                 urlConnection.setRequestMethod("GET" 97 
 98                  设置请求属性
 参数一:Range头域可以请求实体的一个或者多个子范围(一半用于断点续传),如果用户的请求中含有range
 ,则服务器的相应代码为206。
101                  参数二:表示请求的范围:比如头500个字节:bytes=0-499
102 
 获取线程已经下载的进度
int start = threadInfo.getStart() + threadInfo.getFinished();
105                 urlConnection.setRequestProperty("range","bytes=" + start + "-" + threadInfo.getEnd());
106 
107                  3、设置文件的写入位置
108                 File file =  File(DownloadService.DOWNLOADPATH,1)">109                 randomAccessFile = 110                  设置从哪里开始写入,如参数为100,那就从101开始写入
                randomAccessFile.seek(start);
113                 finished +=114                 Intent intent =  Intent(DownloadService.UPDATE);
 4、开始下载
116                 if (urlConnection.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
117                     inputStream = urlConnection.getInputStream();
118                      设置字节数组缓冲区
119                     byte[] data = new byte[1024*4];
120                      读取长度
121                     int len = -1122                      取得当前时间
123                     long time = System.currentTimeMillis();
124                     while ((len = inputStream.read(data)) != -1125                          读取成功,写入文件
126                         randomAccessFile.write(data,len);
128                          避免更新过快,减缓主线程压力,让其0.5秒发送一次进度
129                         if (System.currentTimeMillis() - time > 500130                             time =131                              把当前进度通过广播传递给UI
132                             finished += len;
133                             Log.i("finished:",finished+""134                             Log.i("file:",fileInfo.getLength()+""135                             intent.putExtra("finished",finished*100 / fileInfo.getLength());
136                             context.sendBroadcast(intent);
138 
139                          (flag) {
140                              暂停下载,更新进度到数据库
141                             dao.updateThread(threadInfo.getUrl(),threadInfo.getThread_id(),finished);
142                              结束线程
143                             146 147                      当下载执行完毕时,删除数据库线程信息
148                     dao.deleteThread(threadInfo.getUrl(),threadInfo.getThread_id());
149 150 
151             }  (MalformedURLException e) {
152 153             } 154 155             } 156                 if (urlConnection != 157                     urlConnection.disconnect();
158 159                 if (inputStream != 160                     161                         inputStream.close();
162                     } 163 164 165 166                 167                     168 169                     } 170 171 172 173 
174 175 
176 177 178 }

数据库帮助类:

 1  com.example.db;
 2 
 3  4  android.database.sqlite.sqliteDatabase;
 5  android.database.sqlite.sqliteOpenHelper;
 6 
 7 class DBHelper  sqliteOpenHelper {
 8     final String DBNAME = "download.db" 9     int VERSION = 110     final String TABLE="threadinfo"11     final String CREATE_DB = "create table threadinfo (_id integer primary key autoincrement,thread_id integer,url text,start integer,end integer,finished integer) "12     final String DROP_DB = "drop table if exists threadinfo"13 
14      DBHelper(Context context) {
15         super(context,DBNAME,VERSION);
16 17 
18 19      onCreate(sqliteDatabase db) {
20         db.execsql(CREATE_DB);
21 
22 23 
24 25     void onUpgrade(sqliteDatabase db,1)">int oldVersion,1)"> newVersion) {
26         db.execsql(DROP_DB);
27 28 29 
30 }

数据库表接口类:

 com.example.dao;
 4 
interface ThreadDAO {
 新增一条线程信息
 insertThread(ThreadInfo threadInfo);
10 
 删除一条线程信息(多线程下载,可能一个url对应多个线程,所以需要2个条件)
void deleteThread(String url,1)"> thread_id);
 修改一条线程信息
15     void updateThread(String url,1)">int thread_id,1)"> finished);
16 
17      查询线程有关信息(根据url查询下载该url的所有线程信息)
18     public List<ThreadInfo> getThreadInfo(String url);
19 
20      判断线程是否已经存在
21     boolean isExists(String url,1)">22 
24 }

数据库表接口实现类:

 java.util.ArrayList;
 5 
 6  android.content.ContentValues;
 8  android.database.Cursor;
 9 11  com.example.db.DBHelper;
12 14 15  * ThreadDAO接口实现类
17 19  class ThreadDAOImpl 22      DBHelper dbHelper;
24      ThreadDAOImpl(Context context) {
25         dbHelper =  DBHelper(context);
27 
29      insertThread(ThreadInfo threadInfo) {
30         sqliteDatabase db = dbHelper.getWritableDatabase();
31         ContentValues values =  ContentValues();
32         values.put("thread_id"33         values.put("url "34         values.put("start "35         values.put("end "36         values.put("finished "37         db.insert(DBHelper.TABLE,values);
38         db.close();
39 40 
41 42      thread_id) {
43         sqliteDatabase db =44         db.delete(DBHelper.TABLE,"url=? and thread_id=?",1)"> String[] { url,String.valueOf(thread_id) });
45 46 47 
48 49      finished) {
50         sqliteDatabase db =51         db.execsql("update threadinfo set finished = ? where url = ? and thread_id=?",1)"> Object[] { finished,url,thread_id });
52 53 54 
55 56      getThreadInfo(String url) {
57         List<ThreadInfo> list = new ArrayList<ThreadInfo>();
58         sqliteDatabase db =59         Cursor cursor = db.query(DBHelper.TABLE,1)">null,"url=?",1)">new String[] { url },1)">60         while (cursor.moveToNext()) {
61             ThreadInfo threadInfo =  ThreadInfo();
62             threadInfo.setThread_id(cursor.getInt(cursor.getColumnIndex("thread_id")));
63             threadInfo.setUrl(cursor.getString(cursor.getColumnIndex("url"64             threadInfo.setStart(cursor.getInt(cursor.getColumnIndex("start"65             threadInfo.setEnd(cursor.getInt(cursor.getColumnIndex("end"66             threadInfo.setFinished(cursor.getInt(cursor.getColumnIndex("finished"67             list.add(threadInfo);
68 69         cursor.close();
70 71          list;
72 73 
74 75     76         sqliteDatabase db =77         Cursor cursor = db.query(DBHelper.TABLE,1)">new String[] { url,String.valueOf(thread_id) },1)">78         boolean isExists = cursor.moveToNext();
79 80          isExists;
81 82 
83 }

实体类:(文件,线程)

文件实体类:

 com.example.entity;
 java.io.Serializable;
class FileInfo  Serializable {
 7      id;
 String fileName;
 String url;
 length;
 finished;
12 
13      FileInfo() {
15 
16     public FileInfo(int id,String fileName,String url,1)"> length,17             18         19         this.id =20         this.fileName = fileName;
21         this.url = url;
22         this.length =23         this.finished =25 
26      getId() {
27         30     void setId( id) {
31         32 33 
34      String getFileName() {
35         36 37 
38      setFileName(String fileName) {
39         40 41 
 String getUrl() {
43         44 45 
46      setUrl(String url) {
47         49 
50      getLength() {
51         53 
54     void setLength( length) {
55         56 57 
58      getFinished() {
59         60 61 
62     void setFinished(63         64 65 
66 67      String toString() {
68         return "FileInfo [id=" + id + ",fileName=" + fileName + ",url=" + url
69                 + ",length=" + length + ",finished=" + finished + "]"71 
72 }
View Code

线程实体类:

class ThreadInfo  thread_id;
 start;
 end;
 ThreadInfo() {
public ThreadInfo(int start,1)">int end,1)">17         this.thread_id =this.start =this.end =23 24 
 getThread_id() {
26         28 
void setThread_id(30         31 32 
33     34         35 36 
37     38         41      getStart() {
42         43 44 
45     void setStart( start) {
46         47 48 
 getEnd() {
50         51 52 
53     void setEnd( end) {
54         56 
57     58         59 60 
61     62         63 64 
65 66     67         return "ThreadInfo [thread_id=" + thread_id + ",url=" + url + ",start=" + start + ",end=" + end + ",1)">69 
70 }
View Code

?

关于多线程的断点续传,其实原理差不多,还是一样把一个文件分成不同区域,然后每个区域各用一条线程去执行下载任务,然后再把文件合并,改天有时间再上个Demo给大家看吧。

?

?

作者:Balla_兔子
出处:http://www.cnblogs.com/lichenwei/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!旁边有“推荐”二字,你就顺手把它点了吧,相得准,我分文不收;相不准,你也好回来找我!

(编辑:北几岛)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读