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

Android实现下载工具的简单代码

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

北京

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

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

看不清楚,换张图片

免费获取短信验证码

Android实现下载工具的简单代码

下载应该是每个App都必须的一项功能,不采用第三方框架的话,就需要我们自己去实现下载工具了。如果我们自己实现可以怎么做呢?

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

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

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

下边分别是单个任务下载、多任务列表下载、以及service下载的效果图:


single_task

task_manage

service_task

基本实现原理:

接下来看看具体的实现原理,由于我们的下载是基于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)方法。

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)
到这里基本的就介绍完了,更多的细节和具体的使用都在demo中,不合理的地方还请多多指教哦。

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

您可能感兴趣的文章:解决Android SDK下载和更新失败的方法详解Android zip文件下载和解压实例Android实现下载文件功能的方法Android中实现下载和解压zip文件功能代码分享Android编程实现应用自动更新、下载、安装的方法Android使用okHttp(get方式)下载图片Android实现多线程下载文件的方法使用Android的OkHttp包实现基于HTTP协议的文件上传下载Android通过startService实现文件批量下载详解Android使用OKHttp3实现下载(断点续传、显示进度)


免责声明:

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

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

Android实现下载工具的简单代码

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

下载Word文档

猜你喜欢

Android实现下载工具的简单代码

下载应该是每个App都必须的一项功能,不采用第三方框架的话,就需要我们自己去实现下载工具了。如果我们自己实现可以怎么做呢? 首先如果服务器文件支持断点续传,则我们需要实现的主要功能点如下: 多线程、断点续传下载 下载管理:开始、暂停、继续、
2022-06-06

Android顶部工具栏和底部工具栏的简单实现代码

废话少说,直接上图,有图有真相。这两个工具栏全是用布局来实现的。底部工具栏布局代码: 代码代码如下: < xmlns:android="http://schemas.android.com/apk/res/android" and
2022-06-06

Android ContentProvider的实现及简单实例代码

一、概念及说明ContentProvider定义:内容提供者是一个Android应用的基础模块,提供内容给这个应用,它们封装数据和提供它给应用通过这个ContentResolver接口,使用ContentProvider可以在不同的应用程序
2022-06-06

android计算器简单实现代码

本文实例为大家分享了android计算器的具体实现代码,供大家参考,具体内容如下java代码:package com.itheima74.simplecalculator4; import android.os.Bundle; import
2022-06-06

Android实现用代码简单安装和卸载APK的方法

本文实例讲述了Android实现用代码简单安装和卸载APK的方法。分享给大家供大家参考,具体如下:public class TestInstallAPK extends Activity {@Overrideprotected void o
2022-06-06

Linux下载工具wget和axel的简单介绍

本篇内容介绍了“Linux下载工具wget和axel的简单介绍”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Wget Wget是一个十分常用
2023-06-10

android实现简单的乘法计算代码

开发环境:android4.1.实验功能:在第一个界面中的2个乘数输入处分别输入2个数字,按下结果button,会自动跳到第二个界面并显示输入2个数字相乘的结果。如果在第一个界面中点击系统的menu按钮,则会自动弹出一个菜单,菜单栏包括退出
2022-06-06

android实现简单的画画板实例代码

直接看代码,注释都写清楚了代码如下:public class MainActivity extends Activity { private ImageView iv; private Bitmap baseBitmap; private
2022-06-06

JavaWeb 文件的上传和下载功能简单实现代码

一、文件的上传和下载1、文件上传的原理分析 1、文件上传的必要前提: a、提供form表单,method必须是post b、form表单的enctype必须是multipart/form-data
2023-05-31

Android 登录处理简单实例(源码下载)

Android 登录处理简单实例 今天整理一下之前在项目中写的关于某些界面需要登录判断处理。这里整理了一个简易的 Demo 模拟一下 登录情况 和 未登录情况 下的界面跳转处理, 效果如图:以上分别模拟了,未登录和已登录 情况下的 界面跳转
2022-06-06

Android侧滑效果简单实现代码

先看看效果: 首先,导入包:compile files('libs/nineoldandroids-2.4.0.jar')r然后在main中创建一个widget包。 c创建ViewDragHelper类public class ViewD
2022-06-06

android webview 简单浏览器实现代码

文件main.java 代码如下:package com.HHBrowser.android;import android.app.Activity;import android.os.Bundle;import android.os.Ha
2022-06-06

Webview实现android简单的浏览器实例代码

WebView是Android中一个非常实用的组件,它和Safai、Chrome一样都是基于Webkit网页渲染引擎,可以通过加载HTML数据的方式便捷地展现软件的界面,下面通过本文给大家介绍Webview实现android简单的浏览器实例
2022-06-06

Android中Glide实现超简单的图片下载功能

本文介绍了Glide实现超简单的图片下载功能,具体步骤如下:添加依赖compile 'com.github.bumptech.glide:glide:3.7.0' 添加权限2022-06-06

Android文件下载进度条的实现代码

main.xml: 代码如下:2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录