android实现多线程断点续传功能

本文实例为大家分享了android实现多线程断点续传的具体代码,供大家参考,具体内容如下

布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:id="@+id/activity_main"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:paddingBottom="@dimen/activity_vertical_margin"
 android:paddingLeft="@dimen/activity_horizontal_margin"
 android:paddingRight="@dimen/activity_horizontal_margin"
 android:paddingTop="@dimen/activity_vertical_margin">

 <TextView
  android:text="下载进度"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_alignParentTop="true"
  android:layout_alignParentLeft="true"
  android:layout_alignParentStart="true"
  android:layout_marginTop="15dp"
  android:id="@+id/tv_progress" />

 <Button
  android:text="下载"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_below="@+id/firstBar"
  android:layout_alignParentLeft="true"
  android:layout_alignParentStart="true"
  android:layout_marginTop="29dp"
  android:id="@+id/bt" />

 <Button
  android:text="暂停"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_alignTop="@+id/bt"
  android:layout_toRightOf="@+id/bt"
  android:layout_toEndOf="@+id/bt"
  android:layout_marginLeft="15dp"
  android:layout_marginStart="15dp"
  android:id="@+id/bt_pause" />

 <Button
  android:text="继续"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_alignBottom="@+id/bt_pause"
  android:layout_toRightOf="@+id/bt_pause"
  android:layout_toEndOf="@+id/bt_pause"
  android:layout_marginLeft="18dp"
  android:layout_marginStart="18dp"
  android:id="@+id/bt_start" />

 <ProgressBar
  android:id="@+id/firstBar"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  style="@android:style/Widget.ProgressBar.Horizontal"
  android:layout_below="@+id/tv_progress"
  android:layout_alignParentLeft="true"
  android:layout_alignParentStart="true"
  android:layout_marginTop="21dp" />
</RelativeLayout>

MainActivity :

import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity {
 @BindView(R.id.firstBar)
 ProgressBar firstBar;
 @BindView(R.id.tv_progress)
 TextView tvprogress;
 @BindView(R.id.bt)
 Button bt;
 @BindView(R.id.bt_pause)
 Button btPause;
 @BindView(R.id.bt_start)
 Button btStart;
 //下载地址
 private String loadUrl = "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4";
 //文件缓存路径
 private String filePath = Environment.getExternalStorageDirectory() + "/" + "911Mothers.mp4";
 DownLoadFile downLoadFile;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  ButterKnife.bind(this);
  downLoadFile = new DownLoadFile(this, loadUrl, filePath, 3);
  downLoadFile.setOnDownLoadListener(new DownLoadFile.DownLoadListener() {
   @Override
   public void getProgress(int progress) {
    tvprogress.setText("当前进度 :" + progress + " %");
    //设置进度条进度
    firstBar.setProgress(progress);
    if (progress==100){
     Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
    }
   }

   @Override
   public void onComplete() {
    Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
   }

   @Override
   public void onFailure() {
    Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show();
   }
  });

 }

 /*
 * 点击事件
 * */
 @OnClick({R.id.bt, R.id.bt_pause, R.id.bt_start})
 public void onViewClicked(View view) {
  switch (view.getId()) {
   case R.id.bt:
    downLoadFile.downLoad();
    break;
   case R.id.bt_pause:
    downLoadFile.onPause();
    break;
   case R.id.bt_start:
    downLoadFile.onStart();
    break;
  }
 }

 @Override
 protected void onDestroy() {
  downLoadFile.onDestroy();
  super.onDestroy();
 }
}

DownLoadFile:

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Message;

import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

public class DownLoadFile {
 private static final String SP_NAME = "download_file";
 private static final String CURR_LENGTH = "curr_length";
 private static final int DEFAULT_THREAD_COUNT = 4;//默认下载线程数

 //以下为线程状态
 private static final String DOWNLOAD_INIT = "1"; //初始化
 private static final String DOWNLOAD_ING = "2";  //正在下载
 private static final String DOWNLOAD_PAUSE = "3"; //暂停
 private Context mContext;
 private String loadUrl;//网络获取的url
 private String filePath;//下载到本地的path
 private int threadCount = DEFAULT_THREAD_COUNT;//下载线程数
 private int fileLength;//文件总大小
 //使用volatile防止多线程不安全
 private volatile int currLength;//当前总共下载的大小
 private volatile int runningThreadCount;//正在运行的线程数
 private Thread[] mThreads;
 private String stateDownload = DOWNLOAD_INIT;//当前线程状态

 //下载进度监听
 private DownLoadListener mDownLoadListener;

 public void setOnDownLoadListener(DownLoadListener mDownLoadListener) {
  this.mDownLoadListener = mDownLoadListener;
 }

 //定义一个下载监听的接口
 public interface DownLoadListener {
  //返回当前下载进度的百分比
  void getProgress(int progress);
  //下载完成
  void onComplete();
  //下载失败
  void onFailure();
 }

 /*
 * 四种有参构造
 * */
 public DownLoadFile(Context mContext, String loadUrl, String filePath) {
  this(mContext, loadUrl, filePath, DEFAULT_THREAD_COUNT, null);
 }

 public DownLoadFile(Context mContext, String loadUrl, String filePath, DownLoadListener mDownLoadListener) {
  this(mContext, loadUrl, filePath, DEFAULT_THREAD_COUNT, mDownLoadListener);
 }

 public DownLoadFile(Context mContext, String loadUrl, String filePath, int threadCount) {
  this(mContext, loadUrl, filePath, threadCount, null);
 }

 public DownLoadFile(Context mContext, String loadUrl, String filePath, int threadCount, DownLoadListener mDownLoadListener) {
  this.mContext = mContext;
  this.loadUrl = loadUrl;
  this.filePath = filePath;
  this.threadCount = threadCount;
  runningThreadCount = 0;
  this.mDownLoadListener = mDownLoadListener;
 }

 /**
  * 开始下载
  */
 protected void downLoad() {
  //在线程中运行,防止anr
  new Thread(new Runnable() {
   @Override
   public void run() {
    try {
     //初始化数据
     if (mThreads == null)
      mThreads = new Thread[threadCount];

     //建立连接请求
     URL url = new URL(loadUrl);
     HttpURLConnection conn = (HttpURLConnection) url.openConnection();
     conn.setConnectTimeout(5000);
     conn.setRequestMethod("GET");
     int code = conn.getResponseCode();//获取返回码
     if (code == 200) {//请求成功,根据文件大小开始分多线程下载
      fileLength = conn.getContentLength();
      //根据文件大小,先创建一个空文件
      //“r“——以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
      //“rw“——打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
      //“rws“—— 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
      //“rwd“——打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。
      RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");
      raf.setLength(fileLength);
      raf.close();
      //计算各个线程下载的数据段
      int blockLength = fileLength / threadCount;

      SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
      //获取上次取消下载的进度,若没有则返回0
      currLength = sp.getInt(CURR_LENGTH, 0);
      for (int i = 0; i < threadCount; i++) {
       //开始位置,获取上次取消下载的进度,默认返回i*blockLength,即第i个线程开始下载的位置
       int startPosition = sp.getInt(SP_NAME + (i + 1), i * blockLength);
       //结束位置,-1是为了防止上一个线程和下一个线程重复下载衔接处数据
       int endPosition = (i + 1) * blockLength - 1;
       //将最后一个线程结束位置扩大,防止文件下载不完全,大了不影响,小了文件失效
       if ((i + 1) == threadCount)
        endPosition = endPosition * 2;

       mThreads[i] = new DownThread(i + 1, startPosition, endPosition);
       mThreads[i].start();
      }
     } else {
      handler.sendEmptyMessage(FAILURE);
     }
    } catch (Exception e) {
     e.printStackTrace();
     handler.sendEmptyMessage(FAILURE);
    }
   }
  }).start();
 }

 /**
  * 取消下载
  */
 protected void cancel() {
  if (mThreads != null) {
   //若线程处于等待状态,则while循环处于阻塞状态,无法跳出循环,必须先唤醒线程,才能执行取消任务
   if (stateDownload.equals(DOWNLOAD_PAUSE))
    onStart();
   for (Thread dt : mThreads) {
    ((DownThread) dt).cancel();
   }
  }
 }

 /**
  * 暂停下载
  */
 protected void onPause() {
  if (mThreads != null)
   stateDownload = DOWNLOAD_PAUSE;
 }

 /**
  * 继续下载
  */
 protected void onStart() {
  if (mThreads != null)
   synchronized (DOWNLOAD_PAUSE) {
    stateDownload = DOWNLOAD_ING;
    DOWNLOAD_PAUSE.notifyAll();
   }
 }

 protected void onDestroy() {
  if (mThreads != null)
   mThreads = null;
 }

 private class DownThread extends Thread {

  private boolean isGoOn = true;//是否继续下载
  private int threadId;
  private int startPosition;//开始下载点
  private int endPosition;//结束下载点
  private int currPosition;//当前线程的下载进度

  private DownThread(int threadId, int startPosition, int endPosition) {
   this.threadId = threadId;
   this.startPosition = startPosition;
   currPosition = startPosition;
   this.endPosition = endPosition;
   runningThreadCount++;
  }

  @Override
  public void run() {
   SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
   try {
    URL url = new URL(loadUrl);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    conn.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition);
    conn.setConnectTimeout(5000);
    //若请求头加上Range这个参数,则返回状态码为206,而不是200
    if (conn.getResponseCode() == 206) {
     InputStream is = conn.getInputStream();
     RandomAccessFile raf = new RandomAccessFile(filePath, "rwd");
     raf.seek(startPosition);//跳到指定位置开始写数据
     int len;
     byte[] buffer = new byte[1024];
     while ((len = is.read(buffer)) != -1) {
      //是否继续下载
      if (!isGoOn)
       break;
      //回调当前进度
      if (mDownLoadListener != null) {
       currLength += len;
       int progress = (int) ((float) currLength / (float) fileLength * 100);
       handler.sendEmptyMessage(progress);
      }

      raf.write(buffer, 0, len);
      //写完后将当前指针后移,为取消下载时保存当前进度做准备
      currPosition += len;
      synchronized (DOWNLOAD_PAUSE) {
       if (stateDownload.equals(DOWNLOAD_PAUSE)) {
        DOWNLOAD_PAUSE.wait();
       }
      }
     }
     is.close();
     raf.close();
     //线程计数器-1
     runningThreadCount--;
     //若取消下载,则直接返回
     if (!isGoOn) {
      //此处采用SharedPreferences保存每个线程的当前进度,和三个线程的总下载进度
      if (currPosition < endPosition) {
       sp.edit().putInt(SP_NAME + threadId, currPosition).apply();
       sp.edit().putInt(CURR_LENGTH, currLength).apply();
      }
      return;
     }
     if (runningThreadCount == 0) {
      sp.edit().clear().apply();
      handler.sendEmptyMessage(SUCCESS);
      handler.sendEmptyMessage(100);
      mThreads = null;
     }
    } else {
     sp.edit().clear().apply();
     handler.sendEmptyMessage(FAILURE);
    }
   } catch (Exception e) {
    sp.edit().clear().apply();
    e.printStackTrace();
    handler.sendEmptyMessage(FAILURE);
   }
  }

  public void cancel() {
   isGoOn = false;
  }
 }

 //请求成功
 private final int SUCCESS = 0x00000101;
 //请求失败
 private final int FAILURE = 0x00000102;

 private Handler handler = new Handler() {
  @Override
  public void handleMessage(Message msg) {

   if (mDownLoadListener != null) {
    if (msg.what == SUCCESS) {
     mDownLoadListener.onComplete();
    } else if (msg.what == FAILURE) {

     mDownLoadListener.onFailure();
    } else {
     mDownLoadListener.getProgress(msg.what);
    }
   }
  }
 };
}

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

(0)

相关推荐

  • Android多线程断点续传下载示例详解

    一.概述 在上一篇博文<Android多线程下载示例>中,我们讲解了如何实现Android的多线程下载功能,通过将整个文件分成多个数据块,开启多个线程,让每个线程分别下载一个相应的数据块来实现多线程下载的功能.多线程下载中,可以将下载这个耗时的操作放在子线程中执行,即不阻塞主线程,又符合Android开发的设计规范. 但是当下载的过程当中突然出现手机卡死,或者网络中断,手机电量不足关机的现象,这时,当手机可以正常使用后,如果重新下载文件,似乎不太符合大多数用户的心理期望,那如何实现当手机可以正

  • Android FTP 多线程断点续传下载\上传的实例

    最近在给我的开源下载框架Aria增加FTP断点续传下载和上传功能,在此过程中,爬了FTP的不少坑,终于将功能实现了,在此把一些核心功能点记录下载. FTP下载原理 FTP单线程断点续传 FTP和传统的HTTP协议有所不同,由于FTP没有所谓的头文件,因此我们不能像HTTP那样通过设置header向服务器指定下载区间. 但是FTP协议提供了一个更好用的命令REST用于从指定位置恢复任务,同时FTP协议也提供了一个命令SIZE用于获取下载的文件大小,有了这两个命令,FTP断点续传也就没有什么问题.

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

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

  • Android多线程断点续传下载功能实现代码

    原理 其实断点续传的原理很简单,从字面上理解,所谓断点续传就是从停止的地方重新下载. 断点:线程停止的位置. 续传:从停止的位置重新下载. 用代码解析就是: 断点:当前线程已经下载完成的数据长度. 续传:向服务器请求上次线程停止位置之后的数据. 原理知道了,功能实现起来也简单.每当线程停止时就把已下载的数据长度写入记录文件,当重新下载时,从记录文件读取已经下载了的长度.而这个长度就是所需要的断点. 续传的实现也简单,可以通过设置网络请求参数,请求服务器从指定的位置开始读取数据. 而要实现这两个功

  • Android多线程+单线程+断点续传+进度条显示下载功能

    效果图 白话分析: 多线程:肯定是多个线程咯 断点:线程停止下载的位置 续传:线程从停止下载的位置上继续下载,直到完成任务为止. 核心分析: 断点: 当前线程已经下载的数据长度 续传: 向服务器请求上次线程停止下载位置的数据 con.setRequestProperty("Range", "bytes=" + start + "-" + end); 分配线程: int currentPartSize = fileSize / mThreadNum

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

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

  • android实现多线程下载文件(支持暂停、取消、断点续传)

    多线程下载文件(支持暂停.取消.断点续传) 多线程同时下载文件即:在同一时间内通过多个线程对同一个请求地址发起多个请求,将需要下载的数据分割成多个部分,同时下载,每个线程只负责下载其中的一部分,最后将每一个线程下载的部分组装起来即可. 涉及的知识及问题 请求的数据如何分段 分段完成后如何下载和下载完成后如何组装到一起 暂停下载和继续下载的实现(wait().notifyAll().synchronized的使用) 取消下载和断点续传的实现 一.请求的数据如何分段 首先通过HttpURLConne

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

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

  • Android编程开发实现多线程断点续传下载器实例

    本文实例讲述了Android编程开发实现多线程断点续传下载器.分享给大家供大家参考,具体如下: 使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线.电量不足等情况下,这就需要使用到断点续传功能,下次启动时从记录位置继续下载,可避免重复部分的下载.这里采用数据库来记录下载的进度. 效果图:   断点续传 1.断点续传需要在下载过程中记录每条线程的下载进度 2.每次下载开始之前先读取数据库

  • android实现多线程断点续传功能

    本文实例为大家分享了android实现多线程断点续传的具体代码,供大家参考,具体内容如下 布局: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools&qu

  • android原生实现多线程断点续传功能

    本文实例为大家分享了android实现多线程断点续传功能的具体代码,供大家参考,具体内容如下 需求描述: 输入一个下载地址,和要启动的线程数量,点击下载 利用多线程将文件下载到手机端,支持 断点续传. 在前两章的java 多线程的从基础上进行 效果展示 示例代码: 布局 activity_main.xml <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.Const

  • Android实现多线程断点续传

    本文实例为大家分享了Android实现多线程断点续传的具体代码,供大家参考,具体内容如下 多线程下载涉及到的知识点: 1.Service的使用:我们在Service中去下载文件:2.Thread的使用:Service本身不支持耗时操作,所以我们要去开启线程:3.Sqlite的使用:使用数据库来存储每个线程下载的文件的进度,和文件的下载情况:4.权限:涉及到文件的读写就要用到权限:5.BroadCastReceiver的使用:通过广播来更新下载进度:6.线程池使用:使用线程池来管理线程,减少资源的

  • Android 使用AsyncTask实现多任务多线程断点续传下载

    这篇博客是AsyncTask下载系列的最后一篇文章,前面写了关于断点续传的和多线程下载的博客,这篇是在前两篇的基础上面实现的,有兴趣的可以去看下. 一.AsyncTask实现断点续传 二.AsyncTask实现多线程断点续传 这里模拟应用市场app下载实现了一个Demo,因为只有一个界面,所以没有将下载放到Service中,而是直接在Activity中创建.在正式的项目中,下载都是放到Service中,然后通过BroadCast通知界面更新进度. 上代码之前,先看下demo的运行效果图吧. 下面

  • Android 使用AsyncTask实现多线程断点续传

    前面一篇博客<AsyncTask实现断点续传>讲解了如何实现单线程下的断点续传,也就是一个文件只有一个线程进行下载.    对于大文件而言,使用多线程下载就会比单线程下载要快一些.多线程下载相比单线程下载要稍微复杂一点,本博文将详细讲解如何使用AsyncTask来实现多线程的断点续传下载. 一.实现原理 多线程下载首先要通过每个文件总的下载线程数(我这里设定5个)来确定每个线程所负责下载的起止位置. long blockLength = mFileLength / DEFAULT_POOL_S

随机推荐