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

首頁 > 系統(tǒng) > Android > 正文

Android實現(xiàn)下載工具的簡單代碼

2019-12-12 03:19:38
字體:
供稿:網(wǎng)友

下載應(yīng)該是每個App都必須的一項功能,不采用第三方框架的話,就需要我們自己去實現(xiàn)下載工具了。如果我們自己實現(xiàn)可以怎么做呢?

首先如果服務(wù)器文件支持斷點續(xù)傳,則我們需要實現(xiàn)的主要功能點如下:

多線程、斷點續(xù)傳下載
下載管理:開始、暫停、繼續(xù)、取消、重新開始

如果服務(wù)器文件不支持斷點續(xù)傳,則只能進行普通的單線程下載,而且不能暫停、繼續(xù)。當然一般情況服務(wù)器文件都應(yīng)該支持斷點續(xù)傳吧!

下邊分別是單個任務(wù)下載、多任務(wù)列表下載、以及service下載的效果圖:


single_task

task_manage

service_task

基本實現(xiàn)原理:

接下來看看具體的實現(xiàn)原理,由于我們的下載是基于okhttp實現(xiàn)的,首先我們需要一個OkHttpManager類,進行最基本的網(wǎng)絡(luò)請求封裝:

public class OkHttpManager {  ............省略..............  /**   * 異步(根據(jù)斷點請求)   *   * @param url   * @param start   * @param end   * @param callback   * @return   */  public Call initRequest(String url, long start, long end, final Callback callback) {    Request request = new Request.Builder()        .url(url)        .header("Range", "bytes=" + start + "-" + end)        .build();    Call call = builder.build().newCall(request);    call.enqueue(callback);    return call;  }  /**   * 同步請求   *   * @param url   * @return   * @throws IOException   */  public Response initRequest(String url) throws IOException {    Request request = new Request.Builder()        .url(url)        .header("Range", "bytes=0-")        .build();    return builder.build().newCall(request).execute();  }  /**   * 文件存在的情況下可判斷服務(wù)端文件是否已經(jīng)更改   *   * @param url   * @param lastModify   * @return   * @throws IOException   */  public Response initRequest(String url, String lastModify) throws IOException {    Request request = new Request.Builder()        .url(url)        .header("Range", "bytes=0-")        .header("If-Range", lastModify)        .build();    return builder.build().newCall(request).execute();  }  /**   * https請求時初始化證書   *   * @param certificates   * @return   */  public void setCertificates(InputStream... certificates) {    try {      CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");      KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());      keyStore.load(null);      int index = 0;      for (InputStream certificate : certificates) {        String certificateAlias = Integer.toString(index++);        keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));        try {          if (certificate != null)            certificate.close();        } catch (IOException e) {        }      }      SSLContext sslContext = SSLContext.getInstance("TLS");      TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());      trustManagerFactory.init(keyStore);      sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom());      builder.sslSocketFactory(sslContext.getSocketFactory());    } catch (Exception e) {      e.printStackTrace();    }  }}

這個類里包含了基本的超時配置、根據(jù)斷點信息發(fā)起異步請求、校驗服務(wù)器文件是否有更新、https證書配置等。這樣網(wǎng)絡(luò)請求部分就有了。

接下來,我們還需要數(shù)據(jù)庫的支持,以便記錄下載文件的基本信息,這里我們使用SQLite,只有一張表:

/**   * download_info表建表語句   */  public static final String CREATE_DOWNLOAD_INFO = "create table download_info ("      + "id integer primary key autoincrement, "      + "url text, "      + "path text, "      + "name text, "      + "child_task_count integer, "      + "current_length integer, "      + "total_length integer, "      + "percentage real, "      + "last_modify text, "      + "date text)";

當然還有對應(yīng)表的增刪改查工具類,具體的可參考源碼。

由于需要下載管理,所以線程池也是必不可少的,這樣可以避免過多的創(chuàng)建子線程,達到復(fù)用的目的,當然線程池的大小可以根據(jù)需求進行配置,主要代碼如下:

public class ThreadPool {  //可同時下載的任務(wù)數(shù)(核心線程數(shù))  private int CORE_POOL_SIZE = 3;  //緩存隊列的大小(最大線程數(shù))  private int MAX_POOL_SIZE = 20;  //非核心線程閑置的超時時間(秒),如果超時則會被回收  private long KEEP_ALIVE = 10L;  private ThreadPoolExecutor THREAD_POOL_EXECUTOR;  private ThreadFactory sThreadFactory = new ThreadFactory() {    private final AtomicInteger mCount = new AtomicInteger();    @Override    public Thread newThread(@NonNull Runnable runnable) {      return new Thread(runnable, "download_task#" + mCount.getAndIncrement());    }  };  ...................省略................  public void setCorePoolSize(int corePoolSize) {    if (corePoolSize == 0) {      return;    }    CORE_POOL_SIZE = corePoolSize;  }  public void setMaxPoolSize(int maxPoolSize) {    if (maxPoolSize == 0) {      return;    }    MAX_POOL_SIZE = maxPoolSize;  }  public int getCorePoolSize() {    return CORE_POOL_SIZE;  }  public int getMaxPoolSize() {    return MAX_POOL_SIZE;  }  public ThreadPoolExecutor getThreadPoolExecutor() {    if (THREAD_POOL_EXECUTOR == null) {      THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(          CORE_POOL_SIZE, MAX_POOL_SIZE,          KEEP_ALIVE, TimeUnit.SECONDS,          new LinkedBlockingDeque<Runnable>(),          sThreadFactory);    }    return THREAD_POOL_EXECUTOR;  }}

接下來就是我們核心的下載類FileTask了,它實現(xiàn)了Runnable接口,這樣就能在線程池中執(zhí)行,首先看下run()方法的邏輯:

@Override  public void run() {    try {      File saveFile = new File(path, name);      File tempFile = new File(path, name + ".temp");      DownloadData data = Db.getInstance(context).getData(url);      if (Utils.isFileExists(saveFile) && Utils.isFileExists(tempFile) && data != null) {        Response response = OkHttpManager.getInstance().initRequest(url, data.getLastModify());        if (response != null && response.isSuccessful() && Utils.isNotServerFileChanged(response)) {          TEMP_FILE_TOTAL_SIZE = EACH_TEMP_SIZE * data.getChildTaskCount();          onStart(data.getTotalLength(), data.getCurrentLength(), "", true);        } else {          prepareRangeFile(response);        }        saveRangeFile();      } else {        Response response = OkHttpManager.getInstance().initRequest(url);        if (response != null && response.isSuccessful()) {          if (Utils.isSupportRange(response)) {            prepareRangeFile(response);            saveRangeFile();          } else {            saveCommonFile(response);          }        }      }    } catch (IOException e) {      onError(e.toString());    }  }

如果下載的目標文件、記錄斷點的臨時文件、數(shù)據(jù)庫記錄都存在,則我們先判斷服務(wù)器文件是否有更新,如果沒有更新則根據(jù)之前的記錄直接開始下載,否則需要先進行斷點下載前的準備。如果記錄文件不全部存在則需要先判斷是否支持斷點續(xù)傳,如果支持則按照斷點續(xù)傳的流程進行,否則采用普通下載。

首先看下prepareRangeFile()方法,在這里進行斷點續(xù)傳的準備工作:

private void prepareRangeFile(Response response) {   .................省略.................    try {      File saveFile = Utils.createFile(path, name);      File tempFile = Utils.createFile(path, name + ".temp");      long fileLength = response.body().contentLength();      onStart(fileLength, 0, Utils.getLastModify(response), true);      Db.getInstance(context).deleteData(url);      Utils.deleteFile(saveFile, tempFile);      saveRandomAccessFile = new RandomAccessFile(saveFile, "rws");      saveRandomAccessFile.setLength(fileLength);      tempRandomAccessFile = new RandomAccessFile(tempFile, "rws");      tempRandomAccessFile.setLength(TEMP_FILE_TOTAL_SIZE);      tempChannel = tempRandomAccessFile.getChannel();      MappedByteBuffer buffer = tempChannel.map(READ_WRITE, 0, TEMP_FILE_TOTAL_SIZE);      long start;      long end;      int eachSize = (int) (fileLength / childTaskCount);      for (int i = 0; i < childTaskCount; i++) {        if (i == childTaskCount - 1) {          start = i * eachSize;          end = fileLength - 1;        } else {          start = i * eachSize;          end = (i + 1) * eachSize - 1;        }        buffer.putLong(start);        buffer.putLong(end);      }    } catch (Exception e) {      onError(e.toString());    } finally {      .............省略............    }  }

首先是清除歷史記錄,創(chuàng)建新的目標文件和臨時文件,childTaskCount代表文件需要通過幾個子任務(wù)去下載,這樣就可以得到每個子任務(wù)需要下載的任務(wù)大小,進而得到具體的斷點信息并記錄到臨時文件中。文件下載我們采用MappedByteBuffer 類,相比RandomAccessFile 更加的高效。同時執(zhí)行onStart()方法將代表下載的準備階段,具體細節(jié)后面會說到。

接下來看saveRangeFile()方法:

private void saveRangeFile() {     .................省略..............    for (int i = 0; i < childTaskCount; i++) {      final int tempI = i;      Call call = OkHttpManager.getInstance().initRequest(url, range.start[i], range.end[i], new Callback() {        @Override        public void onFailure(Call call, IOException e) {          onError(e.toString());        }        @Override        public void onResponse(Call call, Response response) throws IOException {          startSaveRangeFile(response, tempI, range, saveFile, tempFile);        }      });      callList.add(call);    }    .................省略..............  }

就是根據(jù)臨時文件保存的斷點信息發(fā)起childTaskCount數(shù)量的異步請求,如果響應(yīng)成功則通過startSaveRangeFile()方法分段保存文件:

private void startSaveRangeFile(Response response, int index, Ranges range, File saveFile, File tempFile) {   .................省略..............    try {      saveRandomAccessFile = new RandomAccessFile(saveFile, "rws");      saveChannel = saveRandomAccessFile.getChannel();      MappedByteBuffer saveBuffer = saveChannel.map(READ_WRITE, range.start[index], range.end[index] - range.start[index] + 1);      tempRandomAccessFile = new RandomAccessFile(tempFile, "rws");      tempChannel = tempRandomAccessFile.getChannel();      MappedByteBuffer tempBuffer = tempChannel.map(READ_WRITE, 0, TEMP_FILE_TOTAL_SIZE);      inputStream = response.body().byteStream();      int len;      byte[] buffer = new byte[BUFFER_SIZE];      while ((len = inputStream.read(buffer)) != -1) {        //取消        if (IS_CANCEL) {          handler.sendEmptyMessage(CANCEL);          callList.get(index).cancel();          break;        }        saveBuffer.put(buffer, 0, len);        tempBuffer.putLong(index * EACH_TEMP_SIZE, tempBuffer.getLong(index * EACH_TEMP_SIZE) + len);        onProgress(len);        //退出保存記錄        if (IS_DESTROY) {          handler.sendEmptyMessage(DESTROY);          callList.get(index).cancel();          break;        }        //暫停        if (IS_PAUSE) {          handler.sendEmptyMessage(PAUSE);          callList.get(index).cancel();          break;        }      }      addCount();    } catch (Exception e) {      onError(e.toString());    } finally {      .................省略..............    }

在while循環(huán)中進行目前文件的寫入和將當前下載到的位置保存到臨時文件:

 saveBuffer.put(buffer, 0, len); tempBuffer.putLong(index * EACH_TEMP_SIZE, tempBuffer.getLong(index * EACH_TEMP_SIZE) + len);

同時調(diào)用onProgress()方法將進度發(fā)送出去,其中取消、退出保存記錄、暫停需要中斷while循環(huán)。

因為下載是在子線程進行的,但我們一般需要在UI線程根據(jù)下載狀態(tài)來更新UI,所以我們通過Handler將下載過程的狀態(tài)數(shù)據(jù)發(fā)送到UI線程:即調(diào)用handler.sendEmptyMessage()方法。

最后FileTask類還有一個saveCommonFile()方法,即進行不支持斷點續(xù)傳的普通下載。

前邊我們提到了通過Handler將下載過程的狀態(tài)數(shù)據(jù)發(fā)送到UI線程,接下看下ProgressHandler類基本的處理:

private Handler mHandler = new Handler() {    @Override    public void handleMessage(Message msg) {      super.handleMessage(msg);      switch (mCurrentState) {        case START:          break;        case PROGRESS:          break;        case CANCEL:          break;        case PAUSE:          break;        case FINISH:          break;        case DESTROY:          break;        case ERROR:          break;      }    }  };

在handleMessage()方法中,我們根據(jù)當前的下載狀態(tài)進行相應(yīng)的操作。
如果是START則需要將下載數(shù)據(jù)插入數(shù)據(jù)庫,執(zhí)行初始化回調(diào)等;如果是PROGRESS則執(zhí)行下載進度回調(diào);如果是CANCEL則刪除目標文件、臨時文件、數(shù)據(jù)庫記錄并執(zhí)行對應(yīng)回調(diào)等;如果是PAUSE則更新數(shù)據(jù)庫文件記錄并執(zhí)行暫停的回調(diào)等;如果是FINISH則刪除臨時文件和數(shù)據(jù)庫記錄并執(zhí)行完成的回調(diào);如果是DESTROY則代表直接在Activity中下載,退出Activity則會更新數(shù)據(jù)庫記錄;最后的ERROR則對應(yīng)出錯的情況。具體的細節(jié)可參考源碼。

最后在DownloadManger類里使用線程池執(zhí)行下載操作:

ThreadPool.getInstance().getThreadPoolExecutor().execute(fileTask);

 //如果正在下載的任務(wù)數(shù)量等于線程池的核心線程數(shù),則新添加的任務(wù)處于等待狀態(tài)    if (ThreadPool.getInstance().getThreadPoolExecutor().getActiveCount() == ThreadPool.getInstance().getCorePoolSize()) {      downloadCallback.onWait();    }

以及判斷新添加的任務(wù)是否處于等待的狀態(tài),方便在UI層處理。到這里核心的實現(xiàn)原理就完了,更多的細節(jié)可以參考源碼。

如何使用:

DownloadManger是個單例類,在這里封裝在了具體的使用操作,我們可以根據(jù)url進行下載的開始、暫停、繼續(xù)、取消、重新開始、線程池配置、https證書配置、查詢數(shù)據(jù)的記錄數(shù)據(jù)、獲得當前某個下載狀態(tài)的數(shù)據(jù):

開始一個下載任務(wù)我們可以通過三種方式來進行:
1、通過DownloadManager類的start(DownloadData downloadData, DownloadCallback downloadCallback)方法,data可以設(shè)置url、保存路徑、文件名、子任務(wù)數(shù)量:
2、先執(zhí)行DownloadManager類的setOnDownloadCallback(DownloadData downloadData, DownloadCallback downloadCallback)方法,綁定data和callback,再執(zhí)行start(String url)方法。

3、鏈式調(diào)用,需要通過DUtil類來進行:例如

DUtil.init(mContext)        .url(url)        .path(Environment.getExternalStorageDirectory() + "/DUtil/")        .name(name.xxx)        .childTaskCount(3)        .build()        .start(callback);

start()方法會返回DownloadManager類的實例,如果你不關(guān)心返回值,使用DownloadManger.getInstance(context)同樣可以得到DownloadManager類的實例,以便進行后續(xù)的暫停、繼續(xù)、取消等操作。

關(guān)于callback可以使用DownloadCallback接口實現(xiàn)完整的回調(diào):

new DownloadCallback() {          //開始          @Override          public void onStart(long currentSize, long totalSize, float progress) {          }          //下載中          @Override          public void onProgress(long currentSize, long totalSize, float progress) {           }          //暫停          @Override          public void onPause() {          }          //取消          @Override          public void onCancel() {          }          //下載完成          @Override          public void onFinish(File file) {           }          //等待          @Override          public void onWait() {          }          //下載出錯          @Override          public void onError(String error) {          }        }

也可以使用SimpleDownloadCallback接口只實現(xiàn)需要的回調(diào)方法。

暫停下載中的任務(wù):pause(String url)

繼續(xù)暫停的任務(wù):resume(String url)
     ps:不支持斷點續(xù)傳的文件無法進行暫停和繼續(xù)操作。

取消任務(wù):cancel(String url),可以取消下載中、或暫停的任務(wù)。

重新開始下載:restart(String url),暫停、下載中、已取消、已完成的任務(wù)均可重新開始下載。
下載數(shù)據(jù)保存:destroy(String url)、destroy(String... urls),如在Activity中直接下載,直接退出時可在onDestroy()方法中調(diào)用,以保存數(shù)據(jù)。
配置線程池:setTaskPoolSize(int corePoolSize, int maxPoolSize),設(shè)置核心線程數(shù)以及總線程數(shù)。
配置okhttp證書:setCertificates(InputStream... certificates)
在數(shù)據(jù)庫查詢單個數(shù)據(jù)DownloadData getDbData(String url),查詢?nèi)繑?shù)據(jù):List<DownloadData> getAllDbData()
ps:數(shù)據(jù)庫不保存已下載完成的數(shù)據(jù)
獲得下載隊列中的某個文件數(shù)據(jù):DownloadData getCurrentData(String url)
到這里基本的就介紹完了,更多的細節(jié)和具體的使用都在demo中,不合理的地方還請多多指教哦。

github地址:https://github.com/Othershe/DUtil

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 望都县| 普洱| 拉萨市| 柳江县| 凤台县| 沙坪坝区| 临清市| 拜城县| 莲花县| 铅山县| 长葛市| 冷水江市| 城口县| 滨海县| 永康市| 桦川县| 阿尔山市| 吉木萨尔县| 灵台县| 澄城县| 马鞍山市| 伊宁市| 上高县| 徐闻县| 崇信县| 惠水县| 盐池县| 镇雄县| 武山县| 大竹县| 容城县| 什邡市| 梁平县| 阿尔山市| 华池县| 栾川县| 资兴市| 谢通门县| 巴彦淖尔市| 高台县| 仙桃市|