一、多線程下載
多線程下載就是搶占服務器資源
原理:服務器CPU 分配給每條線程的時間片相同,服務器帶寬平均分配給每條線程,所以客戶端開啟的線程越多,就能搶占到更多的服務器資源。
1、設置開啟線程數,發送http請求到下載地址,獲取下載文件的總長度
然后創建一個長度一致的臨時文件,避免下載到一半存儲空間不夠了,并計算每個線程下載多少數據
2、計算每個線程下載數據的開始和結束位置
再次發送請求,用 Range 頭請求開始位置和結束位置的數據
3、將下載到的數據,存放至臨時文件中
4、帶斷點續傳的多線程下載
定義一個int變量,記錄每條線程下載的數據總長度,然后加上該線程的下載開始位置,得到的結果就是下次下載時,該線程的開始位置,把得到的結果存入緩存文件,當文件下載完成,刪除臨時進度文件。
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
|
public class MultiDownload { static int ThreadCount = ; static int finishedThread = ; //確定下載地址 static String filename = "EditPlus.exe" ; static String path = "http://...:/" +filename; public static void main(String[] args) { //、發送get請求,去獲得下載文件的長度 try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod( "GET" ); conn.setConnectTimeout(); conn.setReadTimeout(); if (conn.getResponseCode()==) { //如果請求成功,拿到所請求資源文件的長度 int length = conn.getContentLength(); //、生成一個與原文件同樣的大小的臨時文件,以免下載一半存儲空間不夠了 File file = new File(filename); //演示,所以將保存的文件目錄放在工程的同目錄 //使用RandomAccessFile 生成臨時文件,可以用指針定位文件的任意位置, //而且能夠實時寫到硬件底層設備,略過緩存,這對下載文件是突然斷電等意外是有好處的 RandomAccessFile raf = new RandomAccessFile(file, "rwd" ); //rwd, 實時寫到底層設備 //設置臨時文件的大小 raf.setLength(length); raf.close(); //、計算出每個線程應該下載多少個字節 int size = length/ThreadCount; //如果有余數,負責最后一部分的線程負責下砸 //開啟多線程 for ( int threadId = ; threadId < ThreadCount; threadId++) { //計算每個線程下載的開始位置和結束位置 int startIndex = threadId*size; // 開始 = 線程id * size int endIndex = (threadId+)*size - ; //結束 = (線程id + )*size - //如果是最后一個線程,那么結束位置寫死為文件結束位置 if (threadId == ThreadCount - ) { endIndex = length - ; } //System.out.println("線程"+threadId+"的下載區間是: "+startIndex+"----"+endIndex); new DownloadThread(startIndex,endIndex,threadId).start(); } } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } class DownloadThread extends Thread{ private int startIndex; private int endIndex; private int threadId; public DownloadThread( int startIndex, int endIndex, int threadId) { super(); this .startIndex = startIndex; this .endIndex = endIndex; this .threadId = threadId; } public void run() { //每個線程再次發送http請求,下載自己對應的那部分數據 try { File progressFile = new File(threadId+ ".txt" ); //判斷進度文件是否存在,如果存在,則接著斷點繼續下載,如果不存在,則從頭下載 if (progressFile.exists()) { FileInputStream fis = new FileInputStream(progressFile); BufferedReader br = new BufferedReader( new InputStreamReader(fis)); //從進度文件中度取出上一次下載的總進度,然后與原本的開始進度相加,得到新的開始進度 startIndex += Integer.parseInt(br.readLine()); fis.close(); } System. out .println( "線程" +threadId+ "的下載區間是:" +startIndex+ "----" +endIndex); //、每個線程發送http請求自己的數據 URL url = new URL(MultiDownload.path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod( "GET" ); conn.setConnectTimeout(); conn.setReadTimeout(); //設置本次http請求所請求的數據的區間 conn.setRequestProperty( "Range" , "bytes=" +startIndex+ "-" +endIndex); //請求部分數據,響應碼是 if (conn.getResponseCode()==) { //此時,流里只有ThreadCount分之一的原文件數據 InputStream is = conn.getInputStream(); byte [] b = new byte []; int len = ; int total = ; //total 用于保存斷點續傳的斷點 //拿到臨時文件的輸出流 File file = new File(MultiDownload.filename); RandomAccessFile raf = new RandomAccessFile(file, "rwd" ); //把文件的寫入位置移動至 startIndex raf.seek(startIndex); while ((len = is .read(b))!=-) { //每次讀取流里數據之后,同步把數據寫入臨時文件 raf.write(b, , len); total += len; //System.out.println("線程" + threadId + "下載了" + total); //生成一個一個專門用來記錄下載進度的臨時文件 RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd" ); progressRaf.write((total+ "" ).getBytes()); progressRaf.close(); } System. out .println( "線程" +threadId+ "下載完了---------------------" ); raf.close(); //當所有的線程下載完之后,將進度文件刪除 MultiDownload.finishedThread++; synchronized (MultiDownload.path) { //所有線程使用同一個鎖 if (MultiDownload.finishedThread==MultiDownload.ThreadCount) { for ( int i = ; i < MultiDownload.ThreadCount; i++) { File f = new File(i+ ".txt" ); f.delete(); } MultiDownload.finishedThread=; } } } } catch (Exception e) { e.printStackTrace(); } } } |
二、Android手機版帶斷點續傳的多線程下載
Android手機版的帶斷點續傳的多線程下載邏輯與PC版的幾乎一樣,只不過在Android手機中耗時操作不能放在主線程,網絡下載屬于耗時操作,所以多線程下載要在Android中開啟一個子線程執行。并使用消息隊列機制刷新文本進度條。
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
|
public class MainActivity extends Activity { static int ThreadCount = ; static int FinishedThread = ; int currentProgess; static String Filename = "QQPlayer.exe" ; static String Path = "http://...:/" +Filename; static MainActivity ma; static ProgressBar pb; static TextView tv; static Handler handler = new Handler(){ public void handleMessage(android.os.Message msg){ tv.setText(( long )pb.getProgress()* /pb.getMax() + "%" ); }; }; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ma = this ; pb = (ProgressBar) findViewById(R.id.pb); tv = (TextView) findViewById(R.id.tv); } public void download(View v){ Thread t = new Thread(){ public void run() { //發送http請求獲取文件的長度,創建臨時文件 try { URL url= new URL(Path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod( "GET" ); conn.setConnectTimeout(); conn.setReadTimeout(); if (conn.getResponseCode()==) { int length = conn.getContentLength(); //設置進度條的最大值就是原文件的總長度 pb.setMax(length); //生成一個與原文件相同大小的臨時文件 File file = new File(Environment.getExternalStorageDirectory(),Filename); RandomAccessFile raf = new RandomAccessFile(file, "rwd" ); raf.setLength(length); raf.close(); //計算每個線程需要下載的數據大小 int size = length/ThreadCount; //開啟多線程 for ( int threadId = ; threadId < ThreadCount; threadId++) { int startIndex = threadId*size; int endIndex = (threadId + )*size - ; if (threadId==ThreadCount - ) { endIndex = length - ; } new DownloadThread(startIndex, endIndex, threadId).start(); } } } catch (Exception e) { e.printStackTrace(); } } }; t.start(); } class DownloadThread extends Thread{ private int startIndex; private int endIndex; private int threadId; public DownloadThread( int startIndex, int endIndex, int threadId) { super(); this .startIndex = startIndex; this .endIndex = endIndex; this .threadId = threadId; } public void run() { // 每個線程發送http請求自己的數據 try { //先判斷是不是斷點續傳 File progessFile = new File(Environment.getExternalStorageDirectory(),threadId+ ".txt" ); if (progessFile.exists()) { FileReader fr = new FileReader(progessFile); BufferedReader br = new BufferedReader(fr); int lastProgess = Integer.parseInt(br.readLine()); startIndex += lastProgess; //把上次下載的進度顯示至進度條 currentProgess +=lastProgess; pb.setProgress(currentProgess); //發消息,讓主線程刷新文本進度 handler.sendEmptyMessage(); br.close(); fr.close(); } URL url = new URL(Path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod( "GET" ); conn.setConnectTimeout(); conn.setReadTimeout(); conn.setRequestProperty( "Range" , "bytes=" +startIndex+ "-" +endIndex); if (conn.getResponseCode()==) { InputStream is = conn.getInputStream(); byte [] buffer = new byte []; int len = ; int total = ; File file = new File(Environment.getExternalStorageDirectory(),Filename); RandomAccessFile raf = new RandomAccessFile(file, "rwd" ); raf.seek(startIndex); while ((len = is .read(buffer))!= -) { raf.write(buffer, , len); total += len; //每次讀取流里數據之后,把本次讀取的數據的長度顯示至進度條 currentProgess += len; pb.setProgress(currentProgess); //發消息,讓主線程刷新文本進度 handler.sendEmptyMessage(); //生成臨時文件保存下載進度,用于斷點續傳,在所有線程現在完畢后刪除臨時文件 RandomAccessFile progressRaf = new RandomAccessFile(progessFile, "rwd" ); progressRaf.write((total+ "" ).getBytes()); progressRaf.close(); } raf.close(); System. out .println( "線程" +threadId+ "下載完了" ); //當所有線程都下在完了之后,刪除臨時進度文件 FinishedThread++; synchronized (Path) { if (FinishedThread==ThreadCount) { for ( int i = ; i < ThreadCount; i++) { File f = new File(Environment.getExternalStorageDirectory(),i+ ".txt" ); f.delete(); } FinishedThread=; } } } } catch (Exception e) { e.printStackTrace(); } } } } |
以上內容是小編跟大家分享的PC版與Android手機版帶斷點續傳的多線程下載,希望大家喜歡。