实现原理
(1)首先获得下载文件的长度,然后设置本地文件的长度。
(2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置。
如:文件的长度为6m,线程数为3,那么,每条线程下载的数据长度为2m,每条线程开始下载的位置如下图所示:
(网上找的图)
例如10m大小,使用3个线程来下载,
线程下载的数据长度 (10%3 == 0 ? 10/3:10/3+1) ,第1,2个线程下载长度是4m,第三个线程下载长度为2m
下载开始位置:线程id*每条线程下载的数据长度 = ?
下载结束位置:(线程id+1)*每条线程下载的数据长度-1=?
之前练习时的一个demo,不多说了,直接上代码吧,有关断点续传,需要使用数据库,不再加了,网上有很多成熟的项目可以直接用。
实例
mainapp:
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
|
package com.amos.app; import java.io.file; import java.io.ioexception; import java.net.malformedurlexception; import java.net.url; import java.net.urlconnection; import com.amos.download.r; import android.annotation.suppresslint; import android.app.activity; import android.os.bundle; import android.os.environment; import android.os.handler; import android.os.message; import android.util.log; import android.view.view; import android.view.view.onclicklistener; import android.widget.progressbar; import android.widget.textview; import android.widget.toast; /** * @author yangxiaolong * @2014-5-6 */ public class mainapp extends activity implements onclicklistener { private static final string tag = mainapp. class .getsimplename(); /** 显示下载进度textview */ private textview mmessageview; /** 显示下载进度progressbar */ private progressbar mprogressbar; @override protected void oncreate(bundle savedinstancestate) { super .oncreate(savedinstancestate); setcontentview(r.layout.progress_activity); findviewbyid(r.id.download_btn).setonclicklistener( this ); mmessageview = (textview) findviewbyid(r.id.download_message); mprogressbar = (progressbar) findviewbyid(r.id.download_progress); } @override public void onclick(view v) { if (v.getid() == r.id.download_btn) { dodownload(); } } /** * 使用handler更新ui界面信息 */ @suppresslint ( "handlerleak" ) handler mhandler = new handler() { @override public void handlemessage(message msg) { mprogressbar.setprogress(msg.getdata().getint( "size" )); float temp = ( float ) mprogressbar.getprogress() / ( float ) mprogressbar.getmax(); int progress = ( int ) (temp * 100 ); if (progress == 100 ) { toast.maketext(mainapp. this , "下载完成!" , toast.length_long).show(); } mmessageview.settext( "下载进度:" + progress + " %" ); } }; /** * 下载准备工作,获取sd卡路径、开启线程 */ private void dodownload() { // 获取sd卡路径 string path = environment.getexternalstoragedirectory() + "/amosdownload/" ; file file = new file(path); // 如果sd卡目录不存在创建 if (!file.exists()) { file.mkdir(); } // 设置progressbar初始化 mprogressbar.setprogress( 0 ); // 简单起见,我先把url和文件名称写死,其实这些都可以通过httpheader获取到 string downloadurl = "http://gdown.baidu.com/data/wisegame/91319a5a1dfae322/baidu_16785426.apk" ; string filename = "baidu_16785426.apk" ; int threadnum = 5 ; string filepath = path + filename; log.d(tag, "download file path:" + filepath); downloadtask task = new downloadtask(downloadurl, threadnum, filepath); task.start(); } /** * 多线程文件下载 * * @author yangxiaolong * @2014-8-7 */ class downloadtask extends thread { private string downloadurl; // 下载链接地址 private int threadnum; // 开启的线程数 private string filepath; // 保存文件路径地址 private int blocksize; // 每一个线程的下载量 public downloadtask(string downloadurl, int threadnum, string fileptah) { this .downloadurl = downloadurl; this .threadnum = threadnum; this .filepath = fileptah; } @override public void run() { filedownloadthread[] threads = new filedownloadthread[threadnum]; try { url url = new url(downloadurl); log.d(tag, "download file http path:" + downloadurl); urlconnection conn = url.openconnection(); // 读取下载文件总大小 int filesize = conn.getcontentlength(); if (filesize <= 0 ) { system.out.println( "读取文件失败" ); return ; } // 设置progressbar最大的长度为文件size mprogressbar.setmax(filesize); // 计算每条线程下载的数据长度 blocksize = (filesize % threadnum) == 0 ? filesize / threadnum : filesize / threadnum + 1 ; log.d(tag, "filesize:" + filesize + " blocksize:" ); file file = new file(filepath); for ( int i = 0 ; i < threads.length; i++) { // 启动线程,分别下载每个线程需要下载的部分 threads[i] = new filedownloadthread(url, file, blocksize, (i + 1 )); threads[i].setname( "thread:" + i); threads[i].start(); } boolean isfinished = false ; int downloadedallsize = 0 ; while (!isfinished) { isfinished = true ; // 当前所有线程下载总量 downloadedallsize = 0 ; for ( int i = 0 ; i < threads.length; i++) { downloadedallsize += threads[i].getdownloadlength(); if (!threads[i].iscompleted()) { isfinished = false ; } } // 通知handler去更新视图组件 message msg = new message(); msg.getdata().putint( "size" , downloadedallsize); mhandler.sendmessage(msg); // log.d(tag, "current downloadsize:" + downloadedallsize); thread.sleep( 1000 ); // 休息1秒后再读取下载进度 } log.d(tag, " all of downloadsize:" + downloadedallsize); } catch (malformedurlexception e) { e.printstacktrace(); } catch (ioexception e) { e.printstacktrace(); } catch (interruptedexception e) { e.printstacktrace(); } } } } |
filedownloadthread:
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
|
package com.amos.app; import java.io.bufferedinputstream; import java.io.file; import java.io.ioexception; import java.io.randomaccessfile; import java.net.url; import java.net.urlconnection; import android.util.log; /** * 文件下载类 * * @author yangxiaolong * @2014-5-6 */ public class filedownloadthread extends thread { private static final string tag = filedownloadthread. class .getsimplename(); /** 当前下载是否完成 */ private boolean iscompleted = false ; /** 当前下载文件长度 */ private int downloadlength = 0 ; /** 文件保存路径 */ private file file; /** 文件下载路径 */ private url downloadurl; /** 当前下载线程id */ private int threadid; /** 线程下载数据长度 */ private int blocksize; /** * * @param url:文件下载地址 * @param file:文件保存路径 * @param blocksize:下载数据长度 * @param threadid:线程id */ public filedownloadthread(url downloadurl, file file, int blocksize, int threadid) { this .downloadurl = downloadurl; this .file = file; this .threadid = threadid; this .blocksize = blocksize; } @override public void run() { bufferedinputstream bis = null ; randomaccessfile raf = null ; try { urlconnection conn = downloadurl.openconnection(); conn.setallowuserinteraction( true ); int startpos = blocksize * (threadid - 1 ); //开始位置 int endpos = blocksize * threadid - 1 ; //结束位置 //设置当前线程下载的起点、终点 conn.setrequestproperty( "range" , "bytes=" + startpos + "-" + endpos); system.out.println(thread.currentthread().getname() + " bytes=" + startpos + "-" + endpos); byte [] buffer = new byte [ 1024 ]; bis = new bufferedinputstream(conn.getinputstream()); raf = new randomaccessfile(file, "rwd" ); raf.seek(startpos); int len; while ((len = bis.read(buffer, 0 , 1024 )) != - 1 ) { raf.write(buffer, 0 , len); downloadlength += len; } iscompleted = true ; log.d(tag, "current thread task has finished,all size:" + downloadlength); } catch (ioexception e) { e.printstacktrace(); } finally { if (bis != null ) { try { bis.close(); } catch (ioexception e) { e.printstacktrace(); } } if (raf != null ) { try { raf.close(); } catch (ioexception e) { e.printstacktrace(); } } } } /** * 线程文件是否下载完毕 */ public boolean iscompleted() { return iscompleted; } /** * 线程下载文件长度 */ public int getdownloadlength() { return downloadlength; } } |
效果图:
log控制台:
可以看到文件总大小、我们创建的5个线程每个负责下载的区间
sd卡:
关于android实现网络多线程文件下载小编就给大家介绍这么多,希望对大家有所帮助!同时也非常感谢大家一直以来对服务器之家网站的支持!