我的编程空间,编程开发者的网络收藏夹
学习永远不晚

怎么在Android应用添加一个下载工具

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

怎么在Android应用添加一个下载工具

这篇文章给大家介绍怎么在Android应用添加一个下载工具,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

首先如果服务器文件支持断点续传,则我们需要实现的主要功能点如下:

多线程、断点续传下载
下载管理:开始、暂停、继续、取消、重新开始

如果服务器文件不支持断点续传,则只能进行普通的单线程下载,而且不能暂停、继续。当然一般情况服务器文件都应该支持断点续传吧!

基本实现原理:

接下来看看具体的实现原理,由于我们的下载是基于okhttp实现的,首先我们需要一个OkHttpManager类,进行最基本的网络请求封装:

public class OkHttpManager {  ............省略..............    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;  }    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();  }    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();  }    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,只有一张表:

  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)方法。

链式调用,需要通过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应用添加一个下载工具就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

怎么在Android应用添加一个下载工具

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

怎么在Android应用添加一个下载工具

这篇文章给大家介绍怎么在Android应用添加一个下载工具,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。首先如果服务器文件支持断点续传,则我们需要实现的主要功能点如下:多线程、断点续传下载下载管理:开始、暂停、继续、取
2023-05-31

在android应用中怎么添加一个上拉刷新下拉加载功能

在android应用中怎么添加一个上拉刷新下拉加载功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。下拉刷新首先我们给出如下几个参数,后面要用: private Nest
2023-05-31

怎么在Android应用中添加一个添加物品动画

这篇文章将为大家详细讲解有关怎么在Android应用中添加一个添加物品动画,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。开发环境:AndroidStudio2.1.2+gradle-2.10
2023-05-31

怎么在Android应用中添加一个下拉刷新功能

这篇文章将为大家详细讲解有关怎么在Android应用中添加一个下拉刷新功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。需要给内容加载监听器    function bindEvent()
2023-05-31

Android应用中怎么添加一个联网等待加载动画

Android应用中怎么添加一个联网等待加载动画?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。一、Android带红点的底部导航拦1.首先写底部导航栏的界面vi
2023-05-31

怎么在Android应用中添加一个分享功能

这篇文章将为大家详细讲解有关怎么在Android应用中添加一个分享功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Android 分享功能的实现代码一个Activity中,取出设备上安装的
2023-05-31

怎么在Android应用中添加一个欢迎界面

这篇文章给大家介绍怎么在Android应用中添加一个欢迎界面,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。main_activity.xml文件代码如下:
2023-05-31

怎么在Android应用中添加一个文本输入框

怎么在Android应用中添加一个文本输入框?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。xml如下:<
2023-05-31

怎么在Android应用中添加一个退出确认框

怎么在Android应用中添加一个退出确认框?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。通过对回退键操作的阻断并重写实现:当按下回退键,弹出提示框选择是否退出,是则执行退出代
2023-05-31

怎么在Android应用中添加一个倒计时功能

怎么在Android应用中添加一个倒计时功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。SweepView.java:public class SweepView ext
2023-05-31

Android应用中怎么添加一个splash界面

今天就跟大家聊聊有关Android应用中怎么添加一个splash界面,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。1,在app/build.gradle中的闭包中加入:compile
2023-05-31

怎么在android中利RecycleView添加一个下滑功能

怎么在android中利RecycleView添加一个下滑功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。RecycleView内部有一个滑动监听的抽象类OnScroll
2023-05-31

怎么在Android应用中添加一个文件管理功能

这篇文章给大家介绍怎么在Android应用中添加一个文件管理功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。思路:获取存储器接口 遍历当前目录 利用ListView显示文件文件夹先是布局2023-05-31

Android应用怎么为一个按键添加声音

Android应用怎么为一个按键添加声音?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。为按键添加声音的方法:public class MainActivity extends
2023-05-31

怎么在Android应用中添加一个消息提示音功能

本篇文章为大家展示了怎么在Android应用中添加一个消息提示音功能,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。具体实现的步骤。难点之一:获取到手机系统的提示音,并将它们显示在一个listview
2023-05-31

怎么在Android应用中添加一个图文并茂的按钮

怎么在Android应用中添加一个图文并茂的按钮?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。代码:
2023-05-31

怎么在Android应用中添加一个圆形进度条效果

这篇文章给大家介绍怎么在Android应用中添加一个圆形进度条效果,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。首先我们在attrs属性文件中增加几个自定义属性2023-05-31

怎么在Android应用中添加一个长按删除弹功能

今天就跟大家聊聊有关怎么在Android应用中添加一个长按删除弹功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。只需要调用该方法即可完成绑定:PopupList popupList
2023-05-31

如何在Android应用中添加一个按钮功能

这期内容当中小编将会给大家带来有关如何在Android应用中添加一个按钮功能,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1、首先创建一个按钮
2023-05-31

如何在Android应用中添加一个菜单功能

这篇文章将为大家详细讲解有关如何在Android应用中添加一个菜单功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。开发环境:AndroidStudio2.1.2部分代码(Activity)
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录