国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

android實現多線程下載文件(支持暫停、取消、斷點續傳)

2019-10-23 19:45:42
字體:
來源:轉載
供稿:網友

多線程下載文件(支持暫停、取消、斷點續傳)

多線程同時下載文件即:在同一時間內通過多個線程對同一個請求地址發起多個請求,將需要下載的數據分割成多個部分,同時下載,每個線程只負責下載其中的一部分,最后將每一個線程下載的部分組裝起來即可。

涉及的知識及問題

  • 請求的數據如何分段
  • 分段完成后如何下載和下載完成后如何組裝到一起
  • 暫停下載和繼續下載的實現(wait()、notifyAll()、synchronized的使用)
  • 取消下載和斷點續傳的實現

一、請求的數據如何分段

首先通過HttpURLConnection請求總文件大小,而后根據線程數計算每一個線程的下載量,在分配給每一個線程去下載

fileLength = conn.getContentLength();//根據文件大小,先創建一個空文件//“r“——以只讀方式打開。調用結果對象的任何 write 方法都將導致拋出 IOException。//“rw“——打開以便讀取和寫入。如果該文件尚不存在,則嘗試創建該文件。//“rws“—— 打開以便讀取和寫入,對于 “rw”,還要求對文件的內容或元數據的每個更新都同步寫入到底層存儲設備。//“rwd“——打開以便讀取和寫入,對于 “rw”,還要求對文件內容的每個更新都同步寫入到底層存儲設備。RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");raf.setLength(fileLength);raf.close();//計算各個線程下載的數據段int blockLength = fileLength / threadCount;

二、分段完成后如何下載和下載完成后如何組裝到一起

分段完成后給每一個線程的請求頭設置Range參數,他允許客戶端只請求文件的一部分數據,每一個線程只請求下載相應范圍內的數據,使用RandomAccessFile(可隨機讀寫的文件)寫入到同一個文件里即可組裝成目標文件Range,是在 HTTP/1.1里新增的一個 header field,它允許客戶端實際上只請求文檔的一部分(范圍可以相互重疊)

Range的使用形式:

 

屬性 解釋
bytes=0-499 表示頭500個字節
bytes=500-999 表示第二個500字節
bytes=-500 表示最后500個字節
bytes=500- 表示500字節以后的范圍
bytes=0-0,-1 第一個和最后一個字節

 

HttpUrlConnection中設置請求頭

URL url = new URL(loadUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);conn.setConnectTimeout(5000);//若請求頭加上Range這個參數,則返回狀態碼為206,而不是200if (conn.getResponseCode() == 206) {  InputStream is = conn.getInputStream();  RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");  raf.seek(startPosition);//跳到指定位置開始寫數據}

三、暫停下載和繼續下載的實現(wait()、notifyAll()、synchronized的使用)

關于synchronized只需記住一下五點:

  1. 當兩個并發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以后才能執行該代碼塊。
  2. 然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
  3. 尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
  4. 第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
  5. 以上規則對其它對象鎖同樣適用.
 protected void onPause() {    if (mThreads != null)      stateDownload = DOWNLOAD_PAUSE;  }  protected void onStart() {    if (mThreads != null)      synchronized (DOWNLOAD_PAUSE) {        stateDownload = DOWNLOAD_ING;        DOWNLOAD_PAUSE.notifyAll();      }  }

對于wait()、notify()、notifyAll()需要注意的是

  1. 調用任何對象的wait()方法時,都必須先獲得該對象的鎖,即調用的wait()方法必須得寫在synchronized(obj){…}之內
  2. 當調用對象的wait()方法后,該線程若想繼續執行,必須得再次獲得該對象的鎖才可以
  3. 如果A1,A2,A3線程都在obj.wait(),則B調用object.notify()只能喚醒A1,A2,A3中的一個(具體哪一個由JVM決定)
  4. 當B調用object.notify/notifyAll的時候,B正持有object鎖,因此,A1,A2,A3雖被喚醒,但是仍無法獲得object鎖直到B退出synchronized塊,釋放object鎖后,A1,A2,A3中的一個/全部才有機會獲得鎖繼續執行
  synchronized (DOWNLOAD_PAUSE) {    if (stateDownload.equals(DOWNLOAD_PAUSE)) {      DOWNLOAD_PAUSE.wait();    }  }

四、取消下載和斷點續傳的實現

取消下載即取消每個線程的執行,不建議直接使用Thread.stop()方法,安全的取消線程即run方法執行結束。只要控制住循環,就可以讓run方法結束,也就是線程結束

  while ((len = is.read(buffer)) != -1) {    //是否繼續下載    if (!isGoOn)      break;  }

斷點續傳即其實和重新下載是一樣的,不過文件的大小和每一個線程下載時的起始位置和結束位置都不是重新計算的。而是上次取消下載時,每一個線程保存的當前位置和結束位置,讓每一個線程接著上次的地方繼續下載即可

  SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);  //獲取上次取消下載的進度,若沒有則返回0  currLength = sp.getInt(CURR_LENGTH, 0);  for (int i = 0; i < threadCount; i++) {    //開始位置,獲取上次取消下載的進度,默認返回i*blockLength,即第i個線程開始下載的位置    int startPosition = sp.getInt(SP_NAME + (i + 1), i * blockLength);    //結束位置,-1是為了防止上一個線程和下一個線程重復下載銜接處數據    int endPosition = (i + 1) * blockLength - 1;    //將最后一個線程結束位置擴大,防止文件下載不完全,大了不影響,小了文件失效    if ((i + 1) == threadCount)    endPosition = endPosition * 2;    mThreads[i] = new DownThread(i + 1, startPosition, endPosition);    mThreads[i].start();  }

網絡獲取和讀寫SD卡都需要添加相應權限

<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

下面貼上全部的代碼,里面有詳細的注釋DownLoadFile.Java

import android.content.Context;import android.content.SharedPreferences;import android.os.Handler;import android.os.Message;import java.io.InputStream;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;/** * Created by tianzhao on 2017/2/21 09:25. * 多線程下載文件 */public class DownLoadFile {  private static final String SP_NAME = "download_file";  private static final String CURR_LENGTH = "curr_length";  private static final int DEFAULT_THREAD_COUNT = 4;//默認下載線程數  //以下為線程狀態  private static final String DOWNLOAD_INIT = "1";  private static final String DOWNLOAD_ING = "2";  private static final String DOWNLOAD_PAUSE = "3";  private Context mContext;  private String loadUrl;//網絡獲取的url  private String filePath;//下載到本地的path  private int threadCount = DEFAULT_THREAD_COUNT;//下載線程數  private int fileLength;//文件總大小  //使用volatile防止多線程不安全  private volatile int currLength;//當前總共下載的大小  private volatile int runningThreadCount;//正在運行的線程數  private Thread[] mThreads;  private String stateDownload = DOWNLOAD_INIT;//當前線程狀態  private DownLoadListener mDownLoadListener;  public void setOnDownLoadListener(DownLoadListener mDownLoadListener) {    this.mDownLoadListener = mDownLoadListener;  }  interface DownLoadListener {    //返回當前下載進度的百分比    void getProgress(int progress);    void onComplete();    void onFailure();  }  public DownLoadFile(Context mContext, String loadUrl, String filePath) {    this(mContext, loadUrl, filePath, DEFAULT_THREAD_COUNT, null);  }  public DownLoadFile(Context mContext, String loadUrl, String filePath, DownLoadListener mDownLoadListener) {    this(mContext, loadUrl, filePath, DEFAULT_THREAD_COUNT, mDownLoadListener);  }  public DownLoadFile(Context mContext, String loadUrl, String filePath, int threadCount) {    this(mContext, loadUrl, filePath, threadCount, null);  }  public DownLoadFile(Context mContext, String loadUrl, String filePath, int threadCount, DownLoadListener mDownLoadListener) {    this.mContext = mContext;    this.loadUrl = loadUrl;    this.filePath = filePath;    this.threadCount = threadCount;    runningThreadCount = 0;    this.mDownLoadListener = mDownLoadListener;  }  /**   * 開始下載   */  protected void downLoad() {    //在線程中運行,防止anr    new Thread(new Runnable() {      @Override      public void run() {        try {          //初始化數據          if (mThreads == null)            mThreads = new Thread[threadCount];          //建立連接請求          URL url = new URL(loadUrl);          HttpURLConnection conn = (HttpURLConnection) url.openConnection();          conn.setConnectTimeout(5000);          conn.setRequestMethod("GET");          int code = conn.getResponseCode();//獲取返回碼          if (code == 200) {//請求成功,根據文件大小開始分多線程下載            fileLength = conn.getContentLength();            //根據文件大小,先創建一個空文件            //“r“——以只讀方式打開。調用結果對象的任何 write 方法都將導致拋出 IOException。            //“rw“——打開以便讀取和寫入。如果該文件尚不存在,則嘗試創建該文件。            //“rws“—— 打開以便讀取和寫入,對于 “rw”,還要求對文件的內容或元數據的每個更新都同步寫入到底層存儲設備。            //“rwd“——打開以便讀取和寫入,對于 “rw”,還要求對文件內容的每個更新都同步寫入到底層存儲設備。            RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");            raf.setLength(fileLength);            raf.close();            //計算各個線程下載的數據段            int blockLength = fileLength / threadCount;            SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);            //獲取上次取消下載的進度,若沒有則返回0            currLength = sp.getInt(CURR_LENGTH, 0);            for (int i = 0; i < threadCount; i++) {              //開始位置,獲取上次取消下載的進度,默認返回i*blockLength,即第i個線程開始下載的位置              int startPosition = sp.getInt(SP_NAME + (i + 1), i * blockLength);              //結束位置,-1是為了防止上一個線程和下一個線程重復下載銜接處數據              int endPosition = (i + 1) * blockLength - 1;              //將最后一個線程結束位置擴大,防止文件下載不完全,大了不影響,小了文件失效              if ((i + 1) == threadCount)                endPosition = endPosition * 2;              mThreads[i] = new DownThread(i + 1, startPosition, endPosition);              mThreads[i].start();            }          } else {            handler.sendEmptyMessage(FAILURE);          }        } catch (Exception e) {          e.printStackTrace();          handler.sendEmptyMessage(FAILURE);        }      }    }).start();  }  /**   * 取消下載   */  protected void cancel() {    if (mThreads != null) {      //若線程處于等待狀態,則while循環處于阻塞狀態,無法跳出循環,必須先喚醒線程,才能執行取消任務      if (stateDownload.equals(DOWNLOAD_PAUSE))        onStart();      for (Thread dt : mThreads) {        ((DownThread) dt).cancel();      }    }  }  /**   * 暫停下載   */  protected void onPause() {    if (mThreads != null)      stateDownload = DOWNLOAD_PAUSE;  }  /**   * 繼續下載   */  protected void onStart() {    if (mThreads != null)      synchronized (DOWNLOAD_PAUSE) {        stateDownload = DOWNLOAD_ING;        DOWNLOAD_PAUSE.notifyAll();      }  }  protected void onDestroy() {    if (mThreads != null)      mThreads = null;  }  private class DownThread extends Thread {    private boolean isGoOn = true;//是否繼續下載    private int threadId;    private int startPosition;//開始下載點    private int endPosition;//結束下載點    private int currPosition;//當前線程的下載進度    private DownThread(int threadId, int startPosition, int endPosition) {      this.threadId = threadId;      this.startPosition = startPosition;      currPosition = startPosition;      this.endPosition = endPosition;      runningThreadCount++;    }    @Override    public void run() {      SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);      try {        URL url = new URL(loadUrl);        HttpURLConnection conn = (HttpURLConnection) url.openConnection();        conn.setRequestMethod("GET");        conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);        conn.setConnectTimeout(5000);        //若請求頭加上Range這個參數,則返回狀態碼為206,而不是200        if (conn.getResponseCode() == 206) {          InputStream is = conn.getInputStream();          RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");          raf.seek(startPosition);//跳到指定位置開始寫數據          int len;          byte[] buffer = new byte[1024];          while ((len = is.read(buffer)) != -1) {            //是否繼續下載            if (!isGoOn)              break;            //回調當前進度            if (mDownLoadListener != null) {              currLength += len;              int progress = (int) ((float) currLength / (float) fileLength * 100);              handler.sendEmptyMessage(progress);            }            raf.write(buffer, 0, len);            //寫完后將當前指針后移,為取消下載時保存當前進度做準備            currPosition += len;            synchronized (DOWNLOAD_PAUSE) {              if (stateDownload.equals(DOWNLOAD_PAUSE)) {                DOWNLOAD_PAUSE.wait();              }            }          }          is.close();          raf.close();          //線程計數器-1          runningThreadCount--;          //若取消下載,則直接返回          if (!isGoOn) {            //此處采用SharedPreferences保存每個線程的當前進度,和三個線程的總下載進度            if (currPosition < endPosition) {              sp.edit().putInt(SP_NAME + threadId, currPosition).apply();              sp.edit().putInt(CURR_LENGTH, currLength).apply();            }            return;          }          if (runningThreadCount == 0) {            sp.edit().clear().apply();            handler.sendEmptyMessage(SUCCESS);            handler.sendEmptyMessage(100);            mThreads = null;          }        } else {          sp.edit().clear().apply();          handler.sendEmptyMessage(FAILURE);        }      } catch (Exception e) {        sp.edit().clear().apply();        e.printStackTrace();        handler.sendEmptyMessage(FAILURE);      }    }    public void cancel() {      isGoOn = false;    }  }  private final int SUCCESS = 0x00000101;  private final int FAILURE = 0x00000102;  private Handler handler = new Handler() {    @Override    public void handleMessage(Message msg) {      if (mDownLoadListener != null) {        if (msg.what == SUCCESS) {          mDownLoadListener.onComplete();        } else if (msg.what == FAILURE) {          mDownLoadListener.onFailure();        } else {          mDownLoadListener.getProgress(msg.what);        }      }    }  };}

在MainActivity中的使用

import android.os.Bundle;import android.os.Environment;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends AppCompatActivity {  DownLoadFile downLoadFile;  private String loadUrl = "http://gdown.baidu.com/data/wisegame/d2fbbc8e64990454/wangyiyunyinle_87.apk";  private String filePath = Environment.getExternalStorageDirectory()+"/"+"網易云音樂.apk";  @Override  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    final TextView tvprogress = (TextView) findViewById(R.id.tv_progress);    downLoadFile = new DownLoadFile(this,loadUrl, filePath, 3);    downLoadFile.setOnDownLoadListener(new DownLoadFile.DownLoadListener() {      @Override      public void getProgress(int progress) {        tvprogress.setText("當前進度 :"+progress+" %");      }      @Override      public void onComplete() {        Toast.makeText(MainActivity.this,"下載完成",Toast.LENGTH_SHORT).show();      }      @Override      public void onFailure() {        Toast.makeText(MainActivity.this,"下載失敗",Toast.LENGTH_SHORT).show();      }    });    findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {      @Override      public void onClick(View v) {        downLoadFile.downLoad();      }    });    findViewById(R.id.bt_pause).setOnClickListener(new View.OnClickListener() {      @Override      public void onClick(View v) {        downLoadFile.onPause();      }    });    findViewById(R.id.bt_start).setOnClickListener(new View.OnClickListener() {      @Override      public void onClick(View v) {        downLoadFile.onStart();      }    });    findViewById(R.id.bt_cancel).setOnClickListener(new View.OnClickListener() {      @Override      public void onClick(View v) {        downLoadFile.cancel();      }    });  }  @Override  protected void onDestroy() {    downLoadFile.onDestroy();    super.onDestroy();  }}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 瑞丽市| 青川县| 商河县| 德安县| 威信县| 梁山县| 渑池县| 襄樊市| 三门峡市| 乐亭县| 阳山县| 金阳县| 罗平县| 醴陵市| 漯河市| 巴马| 肇庆市| 怀宁县| 潜山县| 甘泉县| 勃利县| 阳东县| 新津县| 保定市| 重庆市| 阿拉善右旗| 宝鸡市| 德惠市| 扬中市| 荔浦县| 桂林市| 福清市| 南充市| 神农架林区| 沅江市| 桐庐县| 巴彦县| 濮阳县| 田阳县| 津市市| 宁明县|