亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在Android應用添加一個下載工具

發布時間:2020-11-27 17:15:32 來源:億速云 閱讀:249 作者:Leah 欄目:移動開發

這篇文章給大家介紹怎么在Android應用添加一個下載工具,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

首先如果服務器文件支持斷點續傳,則我們需要實現的主要功能點如下:

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

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

基本實現原理:

接下來看看具體的實現原理,由于我們的下載是基于okhttp實現的,首先我們需要一個OkHttpManager類,進行最基本的網絡請求封裝:

public class OkHttpManager {
  ............省略..............
  /**
   * 異步(根據斷點請求)
   *
   * @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();
  }

  /**
   * 文件存在的情況下可判斷服務端文件是否已經更改
   *
   * @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();
    }
  }
}

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

接下來,我們還需要數據庫的支持,以便記錄下載文件的基本信息,這里我們使用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)";

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

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

public class ThreadPool {
  //可同時下載的任務數(核心線程數)
  private int CORE_POOL_SIZE = 3;
  //緩存隊列的大小(最大線程數)
  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了,它實現了Runnable接口,這樣就能在線程池中執行,首先看下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());
    }
  }

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

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

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 {
      .............省略............
    }
  }

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

接下來看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);
    }
    .................省略..............
  }

就是根據臨時文件保存的斷點信息發起childTaskCount數量的異步請求,如果響應成功則通過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循環中進行目前文件的寫入和將當前下載到的位置保存到臨時文件:

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

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

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

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

前邊我們提到了通過Handler將下載過程的狀態數據發送到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()方法中,我們根據當前的下載狀態進行相應的操作。
如果是START則需要將下載數據插入數據庫,執行初始化回調等;如果是PROGRESS則執行下載進度回調;如果是CANCEL則刪除目標文件、臨時文件、數據庫記錄并執行對應回調等;如果是PAUSE則更新數據庫文件記錄并執行暫停的回調等;如果是FINISH則刪除臨時文件和數據庫記錄并執行完成的回調;如果是DESTROY則代表直接在Activity中下載,退出Activity則會更新數據庫記錄;最后的ERROR則對應出錯的情況。具體的細節可參考源碼。

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

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

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

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

如何使用:

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

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

3、鏈式調用,需要通過DUtil類來進行:例如

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

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

關于callback可以使用DownloadCallback接口實現完整的回調:

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接口只實現需要的回調方法。

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

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

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

重新開始下載:restart(String url),暫停、下載中、已取消、已完成的任務均可重新開始下載。
下載數據保存:destroy(String url)、destroy(String... urls),如在Activity中直接下載,直接退出時可在onDestroy()方法中調用,以保存數據。
配置線程池:setTaskPoolSize(int corePoolSize, int maxPoolSize),設置核心線程數以及總線程數。
配置okhttp證書:setCertificates(InputStream... certificates)
在數據庫查詢單個數據DownloadData getDbData(String url),查詢全部數據:List<DownloadData> getAllDbData()
ps:數據庫不保存已下載完成的數據
獲得下載隊列中的某個文件數據:DownloadData getCurrentData(String url)

關于怎么在Android應用添加一個下載工具就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

龙里县| 阳新县| 韩城市| 大城县| 南投县| 武清区| 石首市| 景谷| 依兰县| 勐海县| 文水县| 应城市| 正镶白旗| 江西省| 咸丰县| 田东县| 英吉沙县| 南宫市| 杭州市| 原平市| 周宁县| 鄱阳县| 锡林浩特市| 诏安县| 太谷县| 元氏县| 吐鲁番市| 拉孜县| 都匀市| 栖霞市| 疏附县| 丹巴县| 蒙城县| 类乌齐县| 确山县| 满洲里市| 定边县| 淳安县| 水富县| 页游| 衡东县|