中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

如何在android項目中使用多線程下載文件

如何在android項目中使用多線程下載文件?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、微信平臺小程序開發(fā)、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了懷安免費建站歡迎大家使用!

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

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

涉及的知識及問題

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

一、請求的數(shù)據(jù)如何分段

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

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

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

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

Range的使用形式:

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

HttpUrlConnection中設(shè)置請求頭

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

三、暫停下載和繼續(xù)下載的實現(xiàn)(wait()、notifyAll()、synchronized的使用)

關(guān)于synchronized只需記住一下五點:

  1. 當兩個并發(fā)線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內(nèi)只能有一個線程得到執(zhí)行。另一個線程必須等待當前線程執(zhí)行完這個代碼塊以后才能執(zhí)行該代碼塊。
  2. 然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
  3. 尤其關(guān)鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
  4. 第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結(jié)果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
  5. 以上規(guī)則對其它對象鎖同樣適用.
 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. 調(diào)用任何對象的wait()方法時,都必須先獲得該對象的鎖,即調(diào)用的wait()方法必須得寫在synchronized(obj){…}之內(nèi)
  2. 當調(diào)用對象的wait()方法后,該線程若想繼續(xù)執(zhí)行,必須得再次獲得該對象的鎖才可以
  3. 如果A1,A2,A3線程都在obj.wait(),則B調(diào)用object.notify()只能喚醒A1,A2,A3中的一個(具體哪一個由JVM決定)
  4. 當B調(diào)用object.notify/notifyAll的時候,B正持有object鎖,因此,A1,A2,A3雖被喚醒,但是仍無法獲得object鎖直到B退出synchronized塊,釋放object鎖后,A1,A2,A3中的一個/全部才有機會獲得鎖繼續(xù)執(zhí)行
  synchronized (DOWNLOAD_PAUSE) {
    if (stateDownload.equals(DOWNLOAD_PAUSE)) {
      DOWNLOAD_PAUSE.wait();
    }
  }

四、取消下載和斷點續(xù)傳的實現(xiàn)

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

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

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

  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);
    //結(jié)束位置,-1是為了防止上一個線程和下一個線程重復(fù)下載銜接處數(shù)據(jù)
    int endPosition = (i + 1) * blockLength - 1;
    //將最后一個線程結(jié)束位置擴大,防止文件下載不完全,大了不影響,小了文件失效
    if ((i + 1) == threadCount)
    endPosition = endPosition * 2;
    mThreads[i] = new DownThread(i + 1, startPosition, endPosition);
    mThreads[i].start();
  }

網(wǎng)絡(luò)獲取和讀寫SD卡都需要添加相應(yīng)權(quán)限

<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;//默認下載線程數(shù)
  //以下為線程狀態(tài)
  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;//網(wǎng)絡(luò)獲取的url
  private String filePath;//下載到本地的path
  private int threadCount = DEFAULT_THREAD_COUNT;//下載線程數(shù)

  private int fileLength;//文件總大小
  //使用volatile防止多線程不安全
  private volatile int currLength;//當前總共下載的大小
  private volatile int runningThreadCount;//正在運行的線程數(shù)
  private Thread[] mThreads;
  private String stateDownload = DOWNLOAD_INIT;//當前線程狀態(tài)

  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 {
          //初始化數(shù)據(jù)
          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) {//請求成功,根據(jù)文件大小開始分多線程下載
            fileLength = conn.getContentLength();
            //根據(jù)文件大小,先創(chuàng)建一個空文件
            //“r“——以只讀方式打開。調(diào)用結(jié)果對象的任何 write 方法都將導(dǎo)致拋出 IOException。
            //“rw“——打開以便讀取和寫入。如果該文件尚不存在,則嘗試創(chuàng)建該文件。
            //“rws“—— 打開以便讀取和寫入,對于 “rw”,還要求對文件的內(nèi)容或元數(shù)據(jù)的每個更新都同步寫入到底層存儲設(shè)備。
            //“rwd“——打開以便讀取和寫入,對于 “rw”,還要求對文件內(nèi)容的每個更新都同步寫入到底層存儲設(shè)備。
            RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");
            raf.setLength(fileLength);
            raf.close();
            //計算各個線程下載的數(shù)據(jù)段
            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);
              //結(jié)束位置,-1是為了防止上一個線程和下一個線程重復(fù)下載銜接處數(shù)據(jù)
              int endPosition = (i + 1) * blockLength - 1;
              //將最后一個線程結(jié)束位置擴大,防止文件下載不完全,大了不影響,小了文件失效
              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) {
      //若線程處于等待狀態(tài),則while循環(huán)處于阻塞狀態(tài),無法跳出循環(huán),必須先喚醒線程,才能執(zhí)行取消任務(wù)
      if (stateDownload.equals(DOWNLOAD_PAUSE))
        onStart();
      for (Thread dt : mThreads) {
        ((DownThread) dt).cancel();
      }
    }
  }

  /**
   * 暫停下載
   */
  protected void onPause() {
    if (mThreads != null)
      stateDownload = DOWNLOAD_PAUSE;
  }

  /**
   * 繼續(xù)下載
   */
  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;//是否繼續(xù)下載
    private int threadId;
    private int startPosition;//開始下載點
    private int endPosition;//結(jié)束下載點
    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這個參數(shù),則返回狀態(tài)碼為206,而不是200
        if (conn.getResponseCode() == 206) {
          InputStream is = conn.getInputStream();
          RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");
          raf.seek(startPosition);//跳到指定位置開始寫數(shù)據(jù)
          int len;
          byte[] buffer = new byte[1024];
          while ((len = is.read(buffer)) != -1) {
            //是否繼續(xù)下載
            if (!isGoOn)
              break;
            //回調(diào)當前進度
            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();
          //線程計數(shù)器-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()+"/"+"網(wǎng)易云音樂.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();
  }
}

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對創(chuàng)新互聯(lián)的支持。

網(wǎng)站欄目:如何在android項目中使用多線程下載文件
網(wǎng)站鏈接:http://www.rwnh.cn/article2/jehdoc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供云服務(wù)器、虛擬主機、品牌網(wǎng)站制作、動態(tài)網(wǎng)站手機網(wǎng)站建設(shè)、營銷型網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設(shè)
乃东县| 蓝田县| 拜城县| 什邡市| 滁州市| 凌海市| 淳安县| 定南县| 太谷县| 温泉县| 会东县| 昔阳县| 五家渠市| 丰县| 连云港市| 金堂县| 太仆寺旗| 普定县| 壶关县| 博湖县| 东源县| 嘉善县| 财经| 英超| 利津县| 太白县| 通海县| 措美县| 青神县| 深泽县| 永昌县| 富锦市| 中方县| 新建县| 文成县| 金沙县| 从化市| 富顺县| 宁陵县| 芒康县| 兴业县|