Android实现断点多线程下载

断点多线程下载的几个关键点:①:得到要下载的文件大小后,均分给几个线程。②:使用RandomAccessFile类进行读写,可以指定开始写入的位置。③:数据库保存下载信息,下一次继续下载的时候从数据库取出数据,然后从上次下载结束的地方开始。

这里我使用了FinalDb的数据库框架,同时在内存中存储了一份所有线程的下载信息,负责时时更新和查询下载进度。我测试用的是百度云网盘,文件大小50M左右。注意,线程中指定了开始结束下载位置的网络请求成功的返回码是206,并不是200。

效果图:

线程类:线程类负责具体的下载,线程的下载信息被存储到了数据库。线程开始下载时,根据线程ID查询自己的存储信息,然后开始从指定的位置下载和写入文件。完毕后根据自己的当前下载结果设置自己当前的下载状态。时时的下载进度存储只存储到了内存,只在本次下载结束才存储到数据库。

public class DownloadThread extends Thread {

  /**
   * 数据库操作工具
   */
  private FinalDb finalDb;

  /**
   * 下载状态:未开始
   */
  public static final int STATE_READY = 1;

  /**
   * 下载状态:下载中
   */
  public static final int STATE_LOADING = 2;

  /**
   * 下载状态:下载暂停中
   */
  public static final int STATE_PAUSING = 3;

  /**
   * 下载状态:下载完成
   */
  public static final int STATE_FINISH = 4;

  /**
   * 下载状态
   */
  public int downloadState;

  /**
   * 线程ID
   */
  private int threadID;

  /**
   * 要下载的URL路径
   */
  private String url;

  /**
   * 本线程要下载的文件
   */
  public RandomAccessFile file;

  /**
   * 构造器
   */
  public DownloadThread(Context context, int threadID, String downloadUrl, RandomAccessFile randomAccessFile) {
    this.threadID = threadID;
    this.url = downloadUrl;
    this.file = randomAccessFile;
    finalDb = DBUtil.getFinalDb(context);
  }

  @Override
  public void run() {
    //数据库查询本线程下载进度
    List<ThreadDownloadInfoBean> list = finalDb.findAllByWhere(ThreadDownloadInfoBean.class, "threadID='" + threadID + "'");
    //下载信息存放到内存
    if (list.get(0) != null) {
      MapUtil.map.put(threadID, list.get(0));
    }
    //取出实体类
    ThreadDownloadInfoBean bean = MapUtil.map.get(threadID);
    Utils.Print("bean:" + bean.toString());
    InputStream is;
    HttpURLConnection conn;
    try {
      Utils.Print("线程" + threadID + "开始连接");
      conn = (HttpURLConnection) new URL(url).openConnection();
      conn.setConnectTimeout(5000);
      conn.setReadTimeout(5000);
      conn.setRequestMethod("GET");
      //设置下载开始和结束的位置
      conn.setRequestProperty("Range", "bytes=" + (bean.startDownloadPosition + bean.downloadedSize) + "-" + bean.endDownloadPosition);
      conn.connect();
      if (conn.getResponseCode() == 206) {
        //更改下载状态
        downloadState = STATE_LOADING;
        bean.downloadState = STATE_LOADING;
        Utils.Print("线程" + threadID + "连接成功");
        is = conn.getInputStream();
        // 1K的数据缓冲
        byte[] bs = new byte[1024];
        // 读取到的数据长度
        int len;
        //从指定的位置开始下载
        file.seek(bean.startDownloadPosition);
        // 循环读取,当已经下载的大小达到了指定的本线程负责的大小时跳出循环,线程之间负责的文件首尾有重合的话没有影响,因为写入的内容时相同的
        while ((len = is.read(bs)) != -1) {
          //不用在这个循环里面更新数据库
          file.write(bs, 0, len);
          //时时更新内存中的已下载大小信息
          bean.downloadedSize += len;
          //如果调用者暂停下载,则跳出结束方法
          if (downloadState == STATE_PAUSING) {
            Utils.Print("线程" + threadID + "暂停下载");
            break;
          }
        }
        is.close();
        file.close();
      } else {
        Utils.Print("线程" + threadID + "连接失败");
      }
      conn.disconnect();
      //如果这个线程已经下载完了自己负责的部分就修改下载状态
      if (bean.downloadedSize >= bean.downloadTotalSize) {
        bean.downloadState = STATE_FINISH;
      } else {
        bean.downloadState = STATE_PAUSING;
      }
      //内存中信息更新至数据库
      finalDb.update(bean, "threadID='" + bean.threadID + "'");
    } catch (IOException e) {
      Utils.Print("线程" + threadID + "IO异常");
      e.printStackTrace();
    }
  }
}

线程信息的封装类:负责存储每个线程的ID,开始下载的位置,结束下载的位置,已经下载的大小,下载状态等;这个类用FinalDb数据库存储到数据库,一定要写get,set方法和空参构造器

@Table(name = "ThreadInfo")
public class ThreadDownloadInfoBean {

  /**
   * id
   */
  public int id;

  /**
   * 线程ID
   */
  public int threadID;

  /**
   * 本线程时时下载开始的位置
   */
  public long startDownloadPosition;

  /**
   * 本线程时时下载结束的位置
   */
  public long endDownloadPosition;

  /**
   * 本线程负责下载的文件大小
   */
  public long downloadTotalSize;

  /**
   * 已经下载了的文件大小
   */
  public long downloadedSize;

  /**
   * 本线程的下载状态
   */
  public int downloadState;

  public ThreadDownloadInfoBean() {

  }

  public ThreadDownloadInfoBean(int downloadState, long downloadedSize, long downloadTotalSize, long endDownloadPosition, long startDownloadPosition, int threadID) {
    this.downloadState = downloadState;
    this.downloadedSize = downloadedSize;
    this.downloadTotalSize = downloadTotalSize;
    this.endDownloadPosition = endDownloadPosition;
    this.startDownloadPosition = startDownloadPosition;
    this.threadID = threadID;
  }

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public int getThreadID() {
    return threadID;
  }

  public void setThreadID(int threadID) {
    this.threadID = threadID;
  }

  public long getStartDownloadPosition() {
    return startDownloadPosition;
  }

  public void setStartDownloadPosition(long startDownloadPosition) {
    this.startDownloadPosition = startDownloadPosition;
  }

  public long getEndDownloadPosition() {
    return endDownloadPosition;
  }

  public void setEndDownloadPosition(long endDownloadPosition) {
    this.endDownloadPosition = endDownloadPosition;
  }

  public long getDownloadTotalSize() {
    return downloadTotalSize;
  }

  public void setDownloadTotalSize(long downloadTotalSize) {
    this.downloadTotalSize = downloadTotalSize;
  }

  public long getDownloadedSize() {
    return downloadedSize;
  }

  public void setDownloadedSize(long downloadedSize) {
    this.downloadedSize = downloadedSize;
  }

  public int getDownloadState() {
    return downloadState;
  }

  public void setDownloadState(int downloadState) {
    this.downloadState = downloadState;
  }

  @Override
  public String toString() {
    return "ThreadDownloadInfoBean{" +
        "id=" + id +
        ", threadID=" + threadID +
        ", startDownloadPosition=" + startDownloadPosition +
        ", endDownloadPosition=" + endDownloadPosition +
        ", downloadTotalSize=" + downloadTotalSize +
        ", downloadedSize=" + downloadedSize +
        ", downloadState=" + downloadState +
        '}';
  }
}

下载工具类:这个类负责得到下载文件大小,分配线程下载大小,管理下载线程

public class DownUtil {

  /**
   * 数据库操作工具
   */
  public FinalDb finalDb;

  /**
   * 下载状态:准备好
   */
  public static final int STATE_READY = 1;

  /**
   * 下载状态:下载中
   */
  public static final int STATE_LOADING = 2;

  /**
   * 下载状态:暂停中
   */
  public static final int STATE_PAUSING = 3;

  /**
   * 下载状态:下载完成
   */
  public static final int STATE_FINISH = 4;

  /**
   * 下载状态
   */
  public int downloadState;

  /**
   * context
   */
  private Context context;

  /**
   * 要下载文件的大小
   */
  public long fileSize;

  /**
   * 线程集合
   */
  private ArrayList<DownloadThread> threadList = new ArrayList<>();

  /**
   * 构造器
   */
  public DownUtil(Context context) {
    this.context = context;
    finalDb = DBUtil.getFinalDb(context);
    judgeDownState();
  }

  /**
   * 初始化时判断下载状态
   */
  public void judgeDownState() {
    //取出数据库中的下载信息,存储到内存中
    List<ThreadDownloadInfoBean> list = finalDb.findAll(ThreadDownloadInfoBean.class);
    if (list != null && list.size() == DownloadActivity.threadNum) {
      for (int i = 0; i < list.size(); i++) {
        MapUtil.map.put(i, list.get(i));
      }
    }
    //查询SP中是否存储过要下载的文件大小
    Long spFileSize = SPUtil.getInstance(context).getLong(DownloadActivity.fileName, 0L);
    long downloadedSize = getFinishedSize();
    //SP中或者数据库中没有查询到说明从没有进行过下载
    if (spFileSize == 0 || downloadedSize == 0) {
      downloadState = STATE_READY;
    } else if (downloadedSize >= spFileSize) {
      downloadState = STATE_FINISH;
    } else {
      downloadState = STATE_PAUSING;
      fileSize = spFileSize;
    }
  }

  /**
   * 点击了开始按钮
   */
  public void clickDownloadBtn() {
    if (downloadState == STATE_READY) {
      startDownload();
    } else if (downloadState == STATE_PAUSING) {
      continueDownload();
    }
  }

  /**
   * 进入应用第一次开始下载
   */
  private void startDownload() {
    //开启新线程,得到要下载的文件大小
    new Thread() {
      @Override
      public void run() {
        try {
          HttpURLConnection conn;
          conn = (HttpURLConnection) new URL(DownloadActivity.url).openConnection();
          conn.setConnectTimeout(5000);
          conn.setReadTimeout(5000);
          conn.setRequestMethod("GET");
          conn.connect();
          if (conn.getResponseCode() == 200) {
            Utils.Print("DownUtil连接成功");
            //得到要下载的文件大小
            fileSize = conn.getContentLength();
            //得到文件名后缀名
            String contentDisposition = new String(conn.getHeaderField("Content-Disposition").getBytes("ISO-8859-1"), "UTF-8");
            String fileName = "下载测试" + contentDisposition.substring(contentDisposition.lastIndexOf("."), contentDisposition.lastIndexOf("\""));
            //得到存储路径
            String sdCardPath = context.getExternalFilesDir(null).getPath();
            DownloadActivity.fileName = sdCardPath + "/" + fileName;
            SPUtil.getInstance(context).saveString(DownloadActivity.FILE_NAME, DownloadActivity.fileName);
            SPUtil.getInstance(context).saveLong(DownloadActivity.fileName, fileSize);
            /*
             * 计算一下每个线程需要分担的下载文件大小 比如 总下载量为100 一共有三个线程
             * 那么 线程1负责0-32,线程2负责33-65,线程3负责66-99和100,
             * 也就是说下载总量除以线程数如果有余数,那么最后一个线程多下载一个余数部分
             */
            //每个线程均分的大小
            long threadDownSize = fileSize / DownloadActivity.threadNum;
            //线程均分完毕剩余的大小
            long leftDownSize = fileSize % DownloadActivity.threadNum;
            //创建要写入的文件
            RandomAccessFile file = new RandomAccessFile(DownloadActivity.fileName, "rw");
            //设置文件大小
            file.setLength(fileSize);
            //关闭
            file.close();
            for (int i = 0; i < DownloadActivity.threadNum; i++) {
              Utils.Print("开启线程" + i);
              //指定每个线程开始下载的位置
              long startPosition = i * threadDownSize;
              //指定每个线程负责下载的大小,当现场是集合里面最后一个线程的时候,它要增加leftDownSize的大小
              threadDownSize = i == DownloadActivity.threadNum - 1 ? threadDownSize + leftDownSize : threadDownSize;
              //存储線程信息
              ThreadDownloadInfoBean bean = new ThreadDownloadInfoBean(DownloadThread.STATE_READY, 0, threadDownSize, startPosition + threadDownSize, startPosition, i);
              finalDb.save(bean);
              RandomAccessFile threadFile = new RandomAccessFile(DownloadActivity.fileName, "rw");
              threadList.add(new DownloadThread(context, i, DownloadActivity.url, threadFile));
              threadList.get(i).start();
            }
            downloadState = STATE_LOADING;
            downloadInfoListener.connectSuccess();
          } else {
            Utils.Print("DownUtil-连接失败");
            downloadInfoListener.connectFail();
          }
          conn.disconnect();
        } catch (IOException e) {
          Utils.Print("DownUtil-IO异常");
          downloadInfoListener.IOException();
          e.printStackTrace();
        }
      }
    }.start();
  }

  /**
   * 继续下载
   */
  private void continueDownload() {
    List<ThreadDownloadInfoBean> list = finalDb.findAll(ThreadDownloadInfoBean.class);
    for (int i = 0; i < DownloadActivity.threadNum; i++) {
      //当前线程已经下载完了就不再开启
      if (list.get(i).downloadState != DownloadThread.STATE_FINISH) {
        Utils.Print("重新开启线程" + i);
        RandomAccessFile threadFile = null;
        try {
          threadFile = new RandomAccessFile(DownloadActivity.fileName, "rw");
        } catch (FileNotFoundException e) {
          e.printStackTrace();
        }
        DownloadThread downloadThread = new DownloadThread(context, i, DownloadActivity.url, threadFile);
        threadList.add(downloadThread);
        downloadThread.start();
      }
    }
    downloadState = STATE_LOADING;
    downloadInfoListener.connectSuccess();
  }

  /**
   * 点击了暂停的按钮
   */
  public void clickPauseBtn() {
    if (downloadState == STATE_LOADING) {
      stopDownload();
    }
  }

  /**
   * 暂停下载
   */
  private void stopDownload() {
    for (int i = 0; i < threadList.size(); i++) {
      if (threadList.get(i).downloadState == DownloadThread.STATE_LOADING) {
        threadList.get(i).downloadState = DownloadThread.STATE_PAUSING;
      }
    }
    downloadState = STATE_PAUSING;
    threadList.clear();
  }

  /**
   * 返回此刻所有线程完成的下载大小
   */
  public long getFinishedSize() {
    long totalSize = 0;
    for (int i = 0; i < DownloadActivity.threadNum; i++) {
      ThreadDownloadInfoBean bean = MapUtil.map.get(i);
      if (bean != null) {
        //如果该线程已经下载的部分大于分配给它的部分多余部分不予计算
        long addSize = bean.downloadedSize > bean.downloadTotalSize ? bean.downloadTotalSize : bean.downloadedSize;
        totalSize += addSize;
      }
    }
    return totalSize;
  }

  /**
   * 下载信息监听器
   */
  private DownloadInfoListener downloadInfoListener;

  /**
   * 下载信息监听器
   */
  public interface DownloadInfoListener {

    void connectSuccess();

    void connectFail();

    void IOException();
  }

  /**
   * 设置下载信息监听器
   */
  public void setDownloadInfoListener(DownloadInfoListener downloadInfoListener) {
    this.downloadInfoListener = downloadInfoListener;
  }

}

页面Activity:负责展示下载进度等信息,提供操作页面

public class DownloadActivity extends BaseActivity {

  /**
   * 下载地址输入框
   */
  private EditText et_download_url;

  /**
   * 确定输入地址的按钮
   */
  private Button btn_download_geturl;

  /**
   * 进度条
   */
  private NumberProgressView np_download;

  /**
   * 开始下载的按钮
   */
  private Button btn_download_start;

  /**
   * 暂停下载的按钮
   */
  private Button btn_download_pause;

  /**
   * 取消下载的按钮
   */
  private Button btn_download_cancel;

  /**
   * 文件信息显示
   */
  private TextView tv_download_file_info;

  /**
   * 下载速度显示
   */
  private TextView tv_download_speed;

  /**
   * 显示下载信息
   */
  private TextView tv_download_speed_info;

  /**
   * 每隔一段时间刷新下载进度显示
   */
  private final static int WHAT_INCREACE = 1;

  /**
   * 得到了文件名称
   */
  private final static int WHAT_GET_FILENAME = 2;

  /**
   * downUtil连接失败
   */
  private final static int WHAI_CONNECT_FAIL = 3;

  /**
   * downUtilIO异常
   */
  private final static int WHAT_IO_EXCEPTION = 4;

  /**
   * 下载工具
   */
  private DownUtil downUtil;

  /**
   * 需要开启的线程数量
   */
  public static final int threadNum = 5;

  /**
   * 存放文件路径名称的SP键名
   */
  public static final String FILE_NAME = "fileName";

  /**
   * 要下载的文件的url地址
   */
  public static String url = "";

  /**
   * 文件下载路径和文件名称
   */
  public static String fileName;

  /**
   * 上次统计已经完成下载的文件大小
   */
  private long lastFinishedSize;

  private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      switch (msg.what) {
        case WHAT_INCREACE:
          updateView();
          break;
        case WHAT_GET_FILENAME:
          tv_download_file_info.setText("下载路径:" + fileName);
          break;
        case WHAI_CONNECT_FAIL:
          tv_download_file_info.setText("连接失败");
          break;
        case WHAT_IO_EXCEPTION:
          tv_download_file_info.setText("IO异常");
          break;
      }
    }
  };

  /**
   * 更新视图
   */
  private void updateView() {
    //当前已经完成下载的文件大小
    long currentFinishedSize = downUtil.getFinishedSize();
    //要显示的下载信息的文字
    StringBuilder downloadInfo = new StringBuilder();
    tv_download_speed.setText("当前下载速度:" + formateSize(currentFinishedSize - lastFinishedSize) + "/s");
    //本次统计比上次统计增加了,更新视图,本次统计较上次统计没有变化,不做视图更新
    if (currentFinishedSize > lastFinishedSize) {
      lastFinishedSize = currentFinishedSize;
      downloadInfo.append("下载进度:" + formateSize(currentFinishedSize) + "/" + formateSize(downUtil.fileSize) + "\n");
      for (int i = 0; i < threadNum; i++) {
        ThreadDownloadInfoBean bean = MapUtil.map.get(i);
        if (bean.downloadTotalSize != 0) {
          downloadInfo.append("线程" + i + "下载进度:" + bean.downloadedSize * 100 / bean.downloadTotalSize + "%  " + formateSize(bean.downloadedSize) + "/" + formateSize(bean.downloadTotalSize) + "\n");
        }
      }
      np_download.setProgress((int) (currentFinishedSize * 100 / downUtil.fileSize));
      tv_download_speed_info.setText(downloadInfo);
      //下载完成后
      if (currentFinishedSize >= downUtil.fileSize) {
        tv_download_speed.setText("下载完毕");
        handler.removeMessages(WHAT_INCREACE);
        return;
      }
    }
    handler.sendEmptyMessageDelayed(WHAT_INCREACE, 1000);
  }

  /**
   * 返回子类要显示的布局 R.layout......
   */
  @Override
  protected int childView() {
    return R.layout.activity_download;
  }

  /**
   * 强制子类实现该抽象方法,初始化各自的View
   */
  @Override
  protected void findChildView() {
    et_download_url = (EditText) findViewById(R.id.et_download_url);
    btn_download_geturl = (Button) findViewById(R.id.btn_download_geturl);
    np_download = (NumberProgressView) findViewById(R.id.np_download);
    btn_download_start = (Button) findViewById(R.id.btn_download_start);
    btn_download_pause = (Button) findViewById(R.id.btn_download_pause);
    btn_download_cancel = (Button) findViewById(R.id.btn_download_cancel);
    tv_download_file_info = (TextView) findViewById(R.id.tv_download_file_info);
    tv_download_speed = (TextView) findViewById(R.id.tv_download_speed);
    tv_download_speed_info = (TextView) findViewById(R.id.tv_download_speed_info);
  }

  /**
   * 强制子类实现该抽象方法,初始化各自数据
   */
  @Override
  protected void initChildData() {
    downUtil = new DownUtil(this);
    lastFinishedSize = downUtil.getFinishedSize();
    StringBuilder downloadInfo = new StringBuilder();
    fileName = SPUtil.getInstance(this).getString(FILE_NAME, null);
    if (fileName != null) {
      downloadInfo.append("下载路径:" + fileName);
      if (downUtil.downloadState == DownUtil.STATE_FINISH) {
        np_download.setProgress(100);
      } else if (downUtil.downloadState == DownUtil.STATE_PAUSING) {
        np_download.setProgress((int) (downUtil.getFinishedSize() * 100 / downUtil.fileSize));
      }
      tv_download_file_info.setText(downloadInfo);
    }
  }

  /**
   * 强制子类实现该抽象方法,设置自己的监听器
   */
  @Override
  protected View[] setChildListener() {
    downUtil.setDownloadInfoListener(new DownUtil.DownloadInfoListener() {

      @Override
      public void connectSuccess() {
        //不能在此更新,需要到主线程刷新UI
        handler.sendEmptyMessage(WHAT_GET_FILENAME);
        handler.sendEmptyMessage(WHAT_INCREACE);
      }

      @Override
      public void connectFail() {
        handler.sendEmptyMessage(WHAI_CONNECT_FAIL);
      }

      @Override
      public void IOException() {
        handler.sendEmptyMessage(WHAT_IO_EXCEPTION);
      }
    });
    return new View[]{btn_download_start, btn_download_pause, btn_download_geturl, btn_download_cancel};
  }

  /**
   * 子类在这个方法里面实现自己View的点击监听事件的相应
   *
   * @param v 父类传递到子类的点击的View
   */
  @Override
  protected void clickChildView(View v) {
    switch (v.getId()) {

      case R.id.btn_download_start:
        downUtil.clickDownloadBtn();
        break;

      case R.id.btn_download_pause:
        downUtil.clickPauseBtn();
        handler.removeMessages(WHAT_INCREACE);
        break;

      case R.id.btn_download_cancel:
        //停止发送消息
        handler.removeMessages(WHAT_INCREACE);
        //暂停下载
        downUtil.clickPauseBtn();
        //重置状态
        downUtil.downloadState = DownUtil.STATE_READY;
        //统计下载的大小归零
        lastFinishedSize = 0;
        //重置文本信息
        tv_download_speed.setText("");
        tv_download_file_info.setText("");
        tv_download_speed_info.setText("");
        //进度条归零
        np_download.setProgress(0);
        //清空内存数据
        MapUtil.map.clear();
        //sp清理
        SPUtil.getInstance(this).removeAll();
        //清空数据库
        downUtil.finalDb.deleteAll(ThreadDownloadInfoBean.class);
        //删除文件
        if (fileName != null) {
          File file = new File(fileName);
          if (file.exists()) {
            boolean delete = file.delete();
            if (delete) {
              Utils.ToastS(this, "删除" + fileName + "成功");
            } else {
              Utils.ToastS(this, "删除失败");
            }
          } else {
            Utils.ToastS(this, "文件不存在");
          }
        } else {
          Utils.ToastS(this, "文件不存在");
        }
        break;

      case R.id.btn_download_geturl:
        String editTextUrl = et_download_url.getText().toString();
        if (editTextUrl.trim().equals("")) {
          Utils.ToastS(this, "请输入地址");
        } else {
          url = editTextUrl;
        }
        break;
    }
  }

  @Override
  protected void onDestroy() {
    handler.removeCallbacksAndMessages(null);
    downUtil.clickPauseBtn();
    super.onDestroy();
  }

  /**
   * 格式化数据大小
   */
  private String formateSize(long size) {
    return Formatter.formatFileSize(this, size);
  }

}

布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  tools:context="com.example.testdemo.activity.DownloadActivity">

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <EditText
      android:id="@+id/et_download_url"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:hint="输入下载地址"/>

    <Button
      android:id="@+id/btn_download_geturl"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="确定"/>

  </LinearLayout>

  <com.example.testdemo.view.NumberProgressView
    android:id="@+id/np_download"
    android:layout_width="match_parent"
    android:layout_height="100dp">
  </com.example.testdemo.view.NumberProgressView>

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
      android:id="@+id/btn_download_start"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="开始下载"/>

    <Button
      android:id="@+id/btn_download_pause"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="暂停下载"/>

    <Button
      android:id="@+id/btn_download_cancel"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="取消下载"/>

  </LinearLayout>

  <TextView
    android:id="@+id/tv_download_file_info"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

  <TextView
    android:id="@+id/tv_download_speed"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

  <TextView
    android:id="@+id/tv_download_speed_info"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

</LinearLayout>

下载Activity的父类

public abstract class BaseActivity extends Activity {

  /**
   * 点击监听器,子类也可以使用
   */
  protected BaseOnClickListener onClickListener = new BaseOnClickListener();

  /**
   * 在onCreate方法里面,找到视图,初始化数据,设置点击监听器
   */
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(childView());
    findView();
    initData();
    setListener();
  }

  /**
   * 返回子类要显示的布局 R.layout......
   */
  protected abstract int childView();

  /**
   * 找到需要的视图---网址:https://www.buzzingandroid.com/tools/android-layout-finder/
   */
  private void findView() {
    findChildView();
  }

  /**
   * 初始化数据
   */
  private void initData() {
    // 初始化子类的数据
    initChildData();
  }

  /**
   * 对需要设置监听 的视图设置监听
   */
  private void setListener() {
    // 子类实现setChildListene()方法,返回一个View数组,里面包含所有需要设置点击监听的View,然后进行For循环,对里面所有View设置监听器
    if (setChildListener() == null || setChildListener().length == 0) {
      return;
    }
    View[] viewArray = setChildListener();
    for (View view : viewArray) {
      view.setOnClickListener(onClickListener);
    }
  }

  /**
   * 自定义的点击监听类
   */
  protected class BaseOnClickListener implements View.OnClickListener {
    @Override
    public void onClick(View v) {
      clickChildView(v);
    }
  }

  /**
   * 强制子类实现该抽象方法,初始化各自的View
   */
  protected abstract void findChildView();

  /**
   * 强制子类实现该抽象方法,初始化各自数据
   */
  protected abstract void initChildData();

  /**
   * 强制子类实现该抽象方法,设置自己的监听器
   */
  protected abstract View[] setChildListener();

  /**
   * 子类在这个方法里面实现自己View的点击监听事件的相应
   *
   * @param v 父类传递到子类的点击的View
   */

  protected abstract void clickChildView(View v);

}

得到数据库的操作类

public class DBUtil {

  public static FinalDb getFinalDb(final Context context) {

    FinalDb finalDb = FinalDb.create(context, "REMUXING.db", false, 1, new FinalDb.DbUpdateListener() {
      @Override
      public void onUpgrade(SQLiteDatabase db, int oldVirsion, int newVirsion) {

      }
    });
    return finalDb;
  }
}

内存中存储下载信息的类

public class MapUtil {
  public static HashMap<Integer, ThreadDownloadInfoBean> map = new HashMap<>();
}

SP存储的工具类

public enum SPUtil {

  SPUTIL;

  public static final String NOTIFICATIONBAR_HEIGHT = "notificationbar_height";

  private static SharedPreferences sp;

  public static SPUtil getInstance(Context context) {

    if (sp == null) {
      sp = context.getSharedPreferences("REMUXING", Context.MODE_PRIVATE);
    }
    return SPUTIL;
  }

  public void saveLong(String key, Long value) {
    sp.edit().putLong(key, value).apply();
  }

  public Long getLong(String key, Long defValue) {
    return sp.getLong(key, defValue);
  }

  public void saveString(String key, String value) {
    sp.edit().putString(key, value).apply();
  }

  public String getString(String key, String defValue) {
    return sp.getString(key, defValue);
  }

  public void removeAll() {
    sp.edit().clear().apply();
  }

}

工具类

public class Utils {

  /**
   * 打印
   */
  public static void Print(String message) {
    Log.e("TAG", "-----   " + message + "   ------") ;
  }

  /**
   * 短吐司
   */
  public static void ToastS(Context context, String message) {
    Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
  }
}

进度条:这个可以忽略,用安卓原生的,代码看我的另一篇博客:Android自定义View实现水平带数字百分比进度条

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android入门:多线程断点下载详细介绍

    本案例在于实现文件的多线程断点下载,即文件在下载一部分中断后,可继续接着已有进度下载,并通过进度条显示进度.也就是说在文件开始下载的同时,自动创建每个线程的下载进度的本地文件,下载中断后,重新进入应用点击下载,程序检查有没有本地文件的存在,若存在,获取本地文件中的下载进度,继续进行下载.当下载完成后,自动删除本地文件. 一.多线程断点下载介绍 所谓的多线程断点下载就是利用多线程下载,并且可被中断,如果突然没电了,重启手机后可以继续下载,而不需要重新下载: 利用的技术有:SQLite存储各个线程的

  • Android HttpURLConnection断点下载(单线程)

    HttpCilent 跟 HttpURLConnection 是安卓原生的用来实现http请求的类: Android 6.0之后取消了HttpClient,不支持跟新 ,今天小编使用的是HttpURLConnection : 直接上代码: URL url = null; BufferedInputStream bin = null; HttpURLConnection httpURLConnection = null; Context context; try { //你要下载文件的路径 Str

  • Android实现断点下载的方法

    最近做的项目中需要实现断点下载,即用户一次下载可以分多次进行,下载过程可以中断,在目前大多数的带离线缓存的软件都是需要实现这一功能.本文阐述了通过sqlite3简单实现了一个具有断点下载功能的demo.言归正传,开始正文. 设计 数据库表存储元数据 DBHelper.java 用于业务存储的Dao Dao.java 抽象下载信息的Bean LoadInfo.java 呈现下载信息View MainActivity.java 存储下载信息Bean DownloadInfo.java 封装好的下载类

  • android多线程断点下载-带进度条和百分比进度显示效果

    android多线程断点下载,带进度条和百分比显示,断点下载的临时数据保存到SD卡的文本文档中,建议可以保存到本地数据库中,这样可以提高存取效率,从而提高系统性能. 效果: 打开软件: 下载中: 下载完毕: 附代码如下: package com.yy.multiDownloadOfBreakPoint; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.R

  • Android原生实现多线程断点下载实例代码

    各位父老乡亲,我单汉三又回来了,今天为大家带来一个用原生的安卓写的多线程断点下载Demo. 通过本文你可以学习到: SQLite的基本使用,数据库的增删改查. Handler的消息处理与更新UI. Service(主要用于下载)的进阶与使用. 原生的json文件解析(多层嵌套). RandomAccessFile的基本使用,可以将文件分段. 基于HttpURLConnection的大文件下载. 上面内容结合,实现多线程,断点下载. Demo是在TV上运行的,图片显示的问题不要纠结了. 文件下载的

  • Android实现多线程断点下载的方法

    本文实例讲述了Android实现多线程断点下载的方法.分享给大家供大家参考.具体实现方法如下: package cn.itcast.download; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputSt

  • Android使用多线程实现断点下载

    多线程下载是加快下载速度的一种方式,通过开启多个线程去执行一个任务..可以使任务的执行速度变快..多线程的任务下载时常都会使用得到..比如说我们手机内部应用宝的下载机制..一定是通过使用了多线程创建的下载器..并且这个下载器可以实现断点下载..在任务被强行终止之后..下次可以通过触发按钮来完成断点下载...那么如何实现断点下载这就是一个问题了.. 首先我们需要明确一点就是多线程下载器通过使用多个线程对同一个任务进行下载..但是这个多线程并不是线程的数目越多,下载的速度就越快..当线程增加的很多的

  • 详解Android中的多线程断点下载

    首先来看一下多线程下载的原理.多线程下载就是将同一个网络上的原始文件根据线程个数分成均等份,然后每个单独的线程下载对应的一部分,然后再将下载好的文件按照原始文件的顺序"拼接"起来就构 成了完整的文件了.这样就大大提高了文件的下载效率.对于文件下载来说,多线程下载是必须要考虑的环节. 多线程下载大致可分为以下几个步骤: 一.获取服务器上的目标文件的大小 显然这一步是需要先访问一下网络,只需要获取到目标文件的总大小即可.目的是为了计算每个线程应该分配的下载任务. 二. 在本地创建一个跟原始

  • Android 断点下载和自动安装的示例代码

    今天说一下Android中下载App到手机中并自动安装,啥也不说了先上效果图了! 上面呢是下载中的一个图片和下载后会自动提示你安装的一个图片,二话不说,这接开代码吧! 首先来一个下布局: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ap

  • Android实现断点多线程下载

    断点多线程下载的几个关键点:①:得到要下载的文件大小后,均分给几个线程.②:使用RandomAccessFile类进行读写,可以指定开始写入的位置.③:数据库保存下载信息,下一次继续下载的时候从数据库取出数据,然后从上次下载结束的地方开始. 这里我使用了FinalDb的数据库框架,同时在内存中存储了一份所有线程的下载信息,负责时时更新和查询下载进度.我测试用的是百度云网盘,文件大小50M左右.注意,线程中指定了开始结束下载位置的网络请求成功的返回码是206,并不是200. 效果图: 线程类:线程

  • Android的HTTP多线程下载示例代码

    本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 多线程断点需要的功能 1.多线程下载, 2.支持断点. 使用多线程的好处:使用多线程下载会提升文件下载的速度. 多线程下载文件的过程是: (1)首先获得下载文件的长度,然后设置本地文件的长度. HttpURLConnection.getContentLength();//获取下载文件的长度 RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe",&q

  • Android多线程下载示例详解

    一.概述 说到Android中的文件下载,Android API中明确要求将耗时的操作放到一个子线程中执行,文件的下载无疑是需要耗费时间的,所以要将文件的下载放到子线程中执行.下面,我们一起来实现一个Android中利用多线程下载文件的小例子. 二.服务端准备 在这个小例子中我以下载有道词典为例,在网上下载有道词典的安装包,在eclipse中新建项目web,将下载的有道词典安装包放置在WebContent目录下,并将项目发布到Tomcat中,具体如下图所示 三.Android实现 1.布局 界面

  • Android版多线程下载 仿下载助手(最新)

    首先声明一点: 这里的多线程下载并不是指多个线程下载一个 文件,而是每个线程负责一个文件,今天给大家分享一个多线程下载的 例子.先看一下效果,点击下载开始下载,同时显示下载进度,下载完成,变成程安装,点击安装提示安装应用. 界面效果图: 线程池ThreadPoolExecutor ,先简单学习下这个线程池的使用 /** * Parameters: corePoolSize the number of threads to keep in the pool, even if they are id

  • Android实现网络多线程断点续传下载功能

    我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多线程断点需要什么功能? 1.多线程下载 2.支持断点 使用多线程的好处:使用多线程下载会提升文件下载的速度 原理 多线程下载的原理就是将要下载的文件分成若干份,其中每份都使用一个单独的线程进行下载,这样对于文件的下载速度自然就提高了许多. 既然要分成若干部分分工下载,自然要知道各个线程自己要下载的起始位置,与要下载的大小.所以我们要解决线程的分配与各个线程定位到下载

  • PC版与Android手机版带断点续传的多线程下载

    一.多线程下载 多线程下载就是抢占服务器资源 原理:服务器CPU 分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源. 1.设置开启线程数,发送http请求到下载地址,获取下载文件的总长度           然后创建一个长度一致的临时文件,避免下载到一半存储空间不够了,并计算每个线程下载多少数据              2.计算每个线程下载数据的开始和结束位置           再次发送请求,用 Range 头请求开始位置和结束位

  • Android实现网络多线程断点续传下载实例

    我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多线程断点需要什么功能? 1.多线程下载, 2.支持断点. 使用多线程的好处:使用多线程下载会提升文件下载的速度.那么多线程下载文件的过程是: (1)首先获得下载文件的长度,然后设置本地文件的长度. HttpURLConnection.getContentLength();//获取下载文件的长度 RandomAccessFile file = new RandomAc

随机推荐