一、多線程下載
多線程下載就是搶占服務器資源
原理:服務器CPU 分配給每條線程的時間片相同,服務器帶寬平均分配給每條線程,所以客戶端開啟的線程越多,就能搶占到更多的服務器資源。
1、設置開啟線程數,發送http請求到下載地址,獲取下載文件的總長度
然后創建一個長度一致的臨時文件,避免下載到一半存儲空間不夠了,並計算每個線程下載多少數據
2、計算每個線程下載數據的開始和結束位置
再次發送請求,用 Range 頭請求開始位置和結束位置的數據
3、將下載到的數據,存放至臨時文件中
4、帶斷點續傳的多線程下載
定義一個int變量,記錄每條線程下載的數據總長度,然后加上該線程的下載開始位置,得到的結果就是下次下載時,該線程的開始位置,把得到的結果存入緩存文件,當文件下載完成,刪除臨時進度文件。
1 public class MultiDownload { 2 3 static int ThreadCount = 3; 4 static int finishedThread = 0; 5 //確定下載地址 6 static String filename = "EditPlus.exe"; 7 static String path = "http://192.168.3.45:8080/"+filename; 8 public static void main(String[] args) { 9 //1、發送get請求,去獲得下載文件的長度 10 try { 11 URL url = new URL(path); 12 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 13 conn.setRequestMethod("GET"); 14 conn.setConnectTimeout(5000); 15 conn.setReadTimeout(5000); 16 17 if (conn.getResponseCode()==200) { 18 //如果請求成功,拿到所請求資源文件的長度 19 int length = conn.getContentLength(); 20 21 //2、生成一個與原文件同樣的大小的臨時文件,以免下載一半存儲空間不夠了 22 File file = new File(filename);//演示,所以將保存的文件目錄放在工程的同目錄 23 //使用RandomAccessFile 生成臨時文件,可以用指針定位文件的任意位置, 24 //而且能夠實時寫到硬件底層設備,略過緩存,這對下載文件是突然斷電等意外是有好處的 25 RandomAccessFile raf = new RandomAccessFile(file, "rwd");//rwd, 實時寫到底層設備 26 //設置臨時文件的大小 27 raf.setLength(length); 28 raf.close(); 29 30 //3、計算出每個線程應該下載多少個字節 31 int size = length/ThreadCount;//如果有余數,負責最后一部分的線程負責下砸 32 33 //開啟多線程 34 for (int threadId = 0; threadId < ThreadCount; threadId++) { 35 //計算每個線程下載的開始位置和結束位置 36 int startIndex = threadId*size; // 開始 = 線程id * size 37 int endIndex = (threadId+1)*size - 1; //結束 = (線程id + 1)*size - 1 38 //如果是最后一個線程,那么結束位置寫死為文件結束位置 39 if (threadId == ThreadCount - 1) { 40 endIndex = length - 1; 41 } 42 43 //System.out.println("線程"+threadId+"的下載區間是: "+startIndex+"----"+endIndex); 44 new DownloadThread(startIndex,endIndex,threadId).start(); 45 } 46 } 47 } catch (MalformedURLException e) { 48 e.printStackTrace(); 49 } catch (IOException e) { 50 e.printStackTrace(); 51 } 52 } 53 } 54 55 class DownloadThread extends Thread{ 56 57 private int startIndex; 58 private int endIndex; 59 private int threadId; 60 61 public DownloadThread(int startIndex, int endIndex, int threadId) { 62 super(); 63 this.startIndex = startIndex; 64 this.endIndex = endIndex; 65 this.threadId = threadId; 66 } 67 68 @Override 69 public void run() { 70 //每個線程再次發送http請求,下載自己對應的那部分數據 71 try { 72 File progressFile = new File(threadId+".txt"); 73 //判斷進度文件是否存在,如果存在,則接着斷點繼續下載,如果不存在,則從頭下載 74 if (progressFile.exists()) { 75 FileInputStream fis = new FileInputStream(progressFile); 76 BufferedReader br = new BufferedReader(new InputStreamReader(fis)); 77 //從進度文件中度取出上一次下載的總進度,然后與原本的開始進度相加,得到新的開始進度 78 startIndex += Integer.parseInt(br.readLine()); 79 fis.close(); 80 } 81 System.out.println("線程"+threadId+"的下載區間是:"+startIndex+"----"+endIndex); 82 83 //4、每個線程發送http請求自己的數據 84 URL url = new URL(MultiDownload.path); 85 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 86 conn.setRequestMethod("GET"); 87 conn.setConnectTimeout(5000); 88 conn.setReadTimeout(5000); 89 //設置本次http請求所請求的數據的區間 90 conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex); 91 92 //請求部分數據,響應碼是206 93 if (conn.getResponseCode()==206) { 94 //此時,流里只有ThreadCount分之一的原文件數據 95 InputStream is = conn.getInputStream(); 96 byte[] b = new byte[1024]; 97 int len = 0; 98 int total = 0;//total 用於保存斷點續傳的斷點 99 //拿到臨時文件的輸出流 100 File file = new File(MultiDownload.filename); 101 RandomAccessFile raf = new RandomAccessFile(file, "rwd"); 102 //把文件的寫入位置移動至 startIndex 103 raf.seek(startIndex); 104 105 while ((len = is.read(b))!=-1) { 106 //每次讀取流里數據之后,同步把數據寫入臨時文件 107 raf.write(b, 0, len); 108 total += len; 109 110 //System.out.println("線程" + threadId + "下載了" + total); 111 //生成一個一個專門用來記錄下載進度的臨時文件 112 RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd"); 113 progressRaf.write((total+"").getBytes()); 114 progressRaf.close(); 115 } 116 System.out.println("線程"+threadId+"下載完了---------------------"); 117 raf.close(); 118 119 //當所有的線程下載完之后,將進度文件刪除 120 MultiDownload.finishedThread++; 121 synchronized (MultiDownload.path) {//所有線程使用同一個鎖 122 if (MultiDownload.finishedThread==MultiDownload.ThreadCount) { 123 for (int i = 0; i < MultiDownload.ThreadCount; i++) { 124 File f = new File(i+".txt"); 125 f.delete(); 126 } 127 MultiDownload.finishedThread=0; 128 } 129 } 130 } 131 } catch (Exception e) { 132 e.printStackTrace(); 133 } 134 135 } 136 }
二、Android手機版帶斷點續傳的多線程下載
Android手機版的帶斷點續傳的多線程下載邏輯與PC版的幾乎一樣,只不過在Android手機中耗時操作不能放在主線程,網絡下載屬於耗時操作,所以多線程下載要在Android中開啟一個子線程執行。
並使用消息隊列機制刷新文本進度條。
public class MainActivity extends Activity { static int ThreadCount = 3; static int FinishedThread = 0; int currentProgess; static String Filename = "QQPlayer.exe"; static String Path = "http://192.168.3.45:8080/"+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()*100 /pb.getMax() +"%"); }; }; @Override 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(){ @Override public void run() { //發送http請求獲取文件的長度,創建臨時文件 try { URL url= new URL(Path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); if (conn.getResponseCode()==200) { 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 = 0; threadId < ThreadCount; threadId++) { int startIndex = threadId*size; int endIndex = (threadId + 1)*size - 1; if (threadId==ThreadCount - 1) { endIndex = length - 1; } 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; } @Override 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(1); br.close(); fr.close(); } URL url = new URL(Path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex); if (conn.getResponseCode()==206) { InputStream is = conn.getInputStream(); byte[] buffer = new byte[1024]; int len = 0; int total = 0; File file = new File(Environment.getExternalStorageDirectory(),Filename); RandomAccessFile raf = new RandomAccessFile(file, "rwd"); raf.seek(startIndex); while ((len = is.read(buffer))!= -1) { raf.write(buffer, 0, len); total += len; //每次讀取流里數據之后,把本次讀取的數據的長度顯示至進度條 currentProgess += len; pb.setProgress(currentProgess); //發消息,讓主線程刷新文本進度 handler.sendEmptyMessage(1); //生成臨時文件保存下載進度,用於斷點續傳,在所有線程現在完畢后刪除臨時文件 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 = 0; i < ThreadCount; i++) { File f = new File(Environment.getExternalStorageDirectory(),i+".txt"); f.delete(); } FinishedThread=0; } } } } catch (Exception e) { e.printStackTrace(); } } } }