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

前面一篇博客《AsyncTask实现断点续传》讲解了如何实现单线程下的断点续传,也就是一个文件只有一个线程进行下载。

    对于大文件而言,使用多线程下载就会比单线程下载要快一些。多线程下载相比单线程下载要稍微复杂一点,本博文将详细讲解如何使用AsyncTask来实现多线程的断点续传下载。

一、实现原理

  多线程下载首先要通过每个文件总的下载线程数(我这里设定5个)来确定每个线程所负责下载的起止位置。

 long blockLength = mFileLength / DEFAULT_POOL_SIZE;
  for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
   long beginPosition = i * blockLength;//每条线程下载的开始位置
   long endPosition = (i + 1) * blockLength;//每条线程下载的结束位置
   if (i == (DEFAULT_POOL_SIZE - 1)) {
    endPosition = mFileLength;//如果整个文件的大小不为线程个数的整数倍,则最后一个线程的结束位置即为文件的总长度
   }
   ......
  }

  这里需要注意的是,文件大小往往不是线程个数的整数倍,所以最后一个线程的结束位置需要设置为文件长度。

  确定好每个线程的下载起止位置之后,需要设置http请求头来下载文件的指定位置:

  //设置下载的数据位置beginPosition字节到endPosition字节
  Header header_size = new BasicHeader("Range", "bytes=" + beginPosition + "-" + endPosition);
  request.addHeader(header_size);

  以上是多线程下载的原理,但是还要实现断点续传需要在每次暂停之后记录每个线程已下载的大小,下次继续下载时从上次下载后的位置开始下载。一般项目中都会存数据库中,我这里为了简单起见直接存在了SharedPreferences中,已下载url和线程编号作为key值。

@Override
  protected void onPostExecute(Long aLong) {
   Log.i(TAG, "download success ");
   //下载完成移除记录
   mSharedPreferences.edit().remove(currentThreadIndex).commit();
  }
  @Override
  protected void onCancelled() {
   Log.i(TAG, "download cancelled ");
   //记录已下载大小current
   mSharedPreferences.edit().putLong(currentThreadIndex, current).commit();
  }

下载的时候,首先获取已下载位置,如果已经下载过,就从上次下载后的位置开始下载:

 //获取之前下载保存的信息,从之前结束的位置继续下载
  //这里加了判断file.exists(),判断是否被用户删除了,如果文件没有下载完,但是已经被用户删除了,则重新下载
  long downedPosition = mSharedPreferences.getLong(currentThreadIndex, 0);
  if(file.exists() && downedPosition != 0) {
   beginPosition = beginPosition + downedPosition;
   current = downedPosition;
   synchronized (mCurrentLength) {
    mCurrentLength += downedPosition;
   }
  }

二、完整代码

package com.bbk.lling.multithreaddownload;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class MainActivity extends Activity {
 private static final String TAG = "MainActivity";
 private static final int DEFAULT_POOL_SIZE = 5;
 private static final int GET_LENGTH_SUCCESS = 1;
 //下载路径
 private String downloadPath = Environment.getExternalStorageDirectory() +
   File.separator + "download";
// private String mUrl = "http://ftp.neu.edu.cn/mirrors/eclipse/technology/epp/downloads/release/juno/SR2/eclipse-java-juno-SR2-linux-gtk-x86_64.tar.gz";
 private String mUrl = "http://p.gdown.baidu.com/c4cb746699b92c9b6565cc65aa2e086552651f73c5d0e634a51f028e32af6abf3d68079eeb75401c76c9bb301e5fb71c144a704cb1a2f527a2e8ca3d6fe561dc5eaf6538e5b3ab0699308d13fe0b711a817c88b0f85a01a248df82824ace3cd7f2832c7c19173236";
 private ProgressBar mProgressBar;
 private TextView mPercentTV;
 SharedPreferences mSharedPreferences = null;
 long mFileLength = 0;
 Long mCurrentLength = 0L;
 private InnerHandler mHandler = new InnerHandler();
 //创建线程池
 private Executor mExecutor = Executors.newCachedThreadPool();
 private List<DownloadAsyncTask> mTaskList = new ArrayList<DownloadAsyncTask>();
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  mProgressBar = (ProgressBar) findViewById(R.id.progressbar);
  mPercentTV = (TextView) findViewById(R.id.percent_tv);
  mSharedPreferences = getSharedPreferences("download", Context.MODE_PRIVATE);
  //开始下载
  findViewById(R.id.begin).setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    new Thread() {
     @Override
     public void run() {
      //创建存储文件夹
      File dir = new File(downloadPath);
      if (!dir.exists()) {
       dir.mkdir();
      }
      //获取文件大小
      HttpClient client = new DefaultHttpClient();
      HttpGet request = new HttpGet(mUrl);
      HttpResponse response = null;
      try {
       response = client.execute(request);
       mFileLength = response.getEntity().getContentLength();
      } catch (Exception e) {
       Log.e(TAG, e.getMessage());
      } finally {
       if (request != null) {
        request.abort();
       }
      }
      Message.obtain(mHandler, GET_LENGTH_SUCCESS).sendToTarget();
     }
    }.start();
   }
  });
  //暂停下载
  findViewById(R.id.end).setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    for (DownloadAsyncTask task : mTaskList) {
     if (task != null && (task.getStatus() == AsyncTask.Status.RUNNING || !task.isCancelled())) {
      task.cancel(true);
     }
    }
    mTaskList.clear();
   }
  });
 }
 /**
  * 开始下载
  * 根据待下载文件大小计算每个线程下载位置,并创建AsyncTask
  */
 private void beginDownload() {
  mCurrentLength = 0L;
  mPercentTV.setVisibility(View.VISIBLE);
  mProgressBar.setProgress(0);
  long blockLength = mFileLength / DEFAULT_POOL_SIZE;
  for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
   long beginPosition = i * blockLength;//每条线程下载的开始位置
   long endPosition = (i + 1) * blockLength;//每条线程下载的结束位置
   if (i == (DEFAULT_POOL_SIZE - 1)) {
    endPosition = mFileLength;//如果整个文件的大小不为线程个数的整数倍,则最后一个线程的结束位置即为文件的总长度
   }
   DownloadAsyncTask task = new DownloadAsyncTask(beginPosition, endPosition);
   mTaskList.add(task);
   task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl, String.valueOf(i));
  }
 }
 /**
  * 更新进度条
  */
 synchronized public void updateProgress() {
  int percent = (int) Math.ceil((float)mCurrentLength / (float)mFileLength * 100);
//  Log.i(TAG, "downloading " + mCurrentLength + "," + mFileLength + "," + percent);
  if(percent > mProgressBar.getProgress()) {
   mProgressBar.setProgress(percent);
   mPercentTV.setText("下载进度:" + percent + "%");
   if (mProgressBar.getProgress() == mProgressBar.getMax()) {
    Toast.makeText(MainActivity.this, "下载结束", Toast.LENGTH_SHORT).show();
   }
  }
 }
 @Override
 protected void onDestroy() {
  for(DownloadAsyncTask task: mTaskList) {
   if(task != null && task.getStatus() == AsyncTask.Status.RUNNING) {
    task.cancel(true);
   }
   mTaskList.clear();
  }
  super.onDestroy();
 }
 /**
  * 下载的AsyncTask
  */
 private class DownloadAsyncTask extends AsyncTask<String, Integer , Long> {
  private static final String TAG = "DownloadAsyncTask";
  private long beginPosition = 0;
  private long endPosition = 0;
  private long current = 0;
  private String currentThreadIndex;
  public DownloadAsyncTask(long beginPosition, long endPosition) {
   this.beginPosition = beginPosition;
   this.endPosition = endPosition;
  }
  @Override
  protected Long doInBackground(String... params) {
   Log.i(TAG, "downloading");
   String url = params[0];
   currentThreadIndex = url + params[1];
   if(url == null) {
    return null;
   }
   HttpClient client = new DefaultHttpClient();
   HttpGet request = new HttpGet(url);
   HttpResponse response = null;
   InputStream is = null;
   RandomAccessFile fos = null;
   OutputStream output = null;
   try {
    //本地文件
    File file = new File(downloadPath + File.separator + url.substring(url.lastIndexOf("/") + 1));
    //获取之前下载保存的信息,从之前结束的位置继续下载
    //这里加了判断file.exists(),判断是否被用户删除了,如果文件没有下载完,但是已经被用户删除了,则重新下载
    long downedPosition = mSharedPreferences.getLong(currentThreadIndex, 0);
    if(file.exists() && downedPosition != 0) {
     beginPosition = beginPosition + downedPosition;
     current = downedPosition;
     synchronized (mCurrentLength) {
      mCurrentLength += downedPosition;
     }
    }
    //设置下载的数据位置beginPosition字节到endPosition字节
    Header header_size = new BasicHeader("Range", "bytes=" + beginPosition + "-" + endPosition);
    request.addHeader(header_size);
    //执行请求获取下载输入流
    response = client.execute(request);
    is = response.getEntity().getContent();
    //创建文件输出流
    fos = new RandomAccessFile(file, "rw");
    //从文件的size以后的位置开始写入,其实也不用,直接往后写就可以。有时候多线程下载需要用
    fos.seek(beginPosition);
    byte buffer [] = new byte[1024];
    int inputSize = -1;
    while((inputSize = is.read(buffer)) != -1) {
     fos.write(buffer, 0, inputSize);
     current += inputSize;
     synchronized (mCurrentLength) {
      mCurrentLength += inputSize;
     }
     this.publishProgress();
     if (isCancelled()) {
      return null;
     }
    }
   } catch (MalformedURLException e) {
    Log.e(TAG, e.getMessage());
   } catch (IOException e) {
    Log.e(TAG, e.getMessage());
   } finally{
    try{
     /*if(is != null) {
      is.close();
     }*/
     if (request != null) {
      request.abort();
     }
     if(output != null) {
      output.close();
     }
     if(fos != null) {
      fos.close();
     }
    } catch(Exception e) {
     e.printStackTrace();
    }
   }
   return null;
  }
  @Override
  protected void onPreExecute() {
   Log.i(TAG, "download begin ");
   super.onPreExecute();
  }
  @Override
  protected void onProgressUpdate(Integer... values) {
   super.onProgressUpdate(values);
   //更新界面进度条
   updateProgress();
  }
  @Override
  protected void onPostExecute(Long aLong) {
   Log.i(TAG, "download success ");
   //下载完成移除记录
   mSharedPreferences.edit().remove(currentThreadIndex).commit();
  }
  @Override
  protected void onCancelled() {
   Log.i(TAG, "download cancelled ");
   //记录已下载大小current
   mSharedPreferences.edit().putLong(currentThreadIndex, current).commit();
  }
  @Override
  protected void onCancelled(Long aLong) {
   Log.i(TAG, "download cancelled(Long aLong)");
   super.onCancelled(aLong);
   mSharedPreferences.edit().putLong(currentThreadIndex, current).commit();
  }
 }
 private class InnerHandler extends Handler {
  @Override
  public void handleMessage(Message msg) {
   switch (msg.what) {
    case GET_LENGTH_SUCCESS :
     beginDownload();
     break;
   }
   super.handleMessage(msg);
  }
 }
}

布局文件和前面一篇博客《AsyncTask实现断点续传》布局文件是一样的,这里就不贴代码了。

  以上代码亲测可用,几百M大文件也没问题。

三、遇到的坑

  问题描述:在使用上面代码下载http://ftp.neu.edu.cn/mirrors/eclipse/technology/epp/downloads/release/juno/SR2/eclipse-java-juno-SR2-linux-gtk-x86_64.tar.gz文件的时候,不知道为什么暂停时候执行AsyncTask.cancel(true)来取消下载任务,不执行onCancel()函数,也就没有记录该线程下载的位置。并且再次点击下载的时候,5个Task都只执行了onPreEexcute()方法,压根就不执行doInBackground()方法。而下载其他文件没有这个问题。

  这个问题折腾了我好久,它又没有报任何异常,调试又调试不出来。看AsyncTask的源码、上stackoverflow也没有找到原因。看到这个网站(https://groups.google.com/forum/#!topic/android-developers/B-oBiS7npfQ)时,我还真以为是AsyncTask的一个bug。

  百番周折,问题居然出现在上面代码239行(这里已注释)。不知道为什么,执行这一句的时候,线程就阻塞在那里了,所以doInBackground()方法一直没有结束,onCancel()方法当然也不会执行了。同时,因为使用的是线程池Executor,线程数为5个,点击取消之后5个线程都阻塞了,所以再次点击下载的时候只执行了onPreEexcute()方法,没有空闲的线程去执行doInBackground()方法。真是巨坑无比有木有。。。

  虽然问题解决了,但是为什么有的文件下载执行到is.close()的时候线程会阻塞而有的不会?这还是个谜。如果哪位大神知道是什么原因,还望指点指点!

源码下载:https://github.com/liuling07/MultiTaskAndThreadDownload

总结

以上所述是小编给大家介绍的Android 使用AsyncTask实现多线程断点续传,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Android开发笔记之:深入理解多线程AsyncTask

    Understanding AsyncTaskAsyncTask是Android 1.5 Cubake加入的用于实现异步操作的一个类,在此之前只能用Java SE库中的Thread来实现多线程异步,AsyncTask是Android平台自己的异步工具,融入了Android平台的特性,让异步操作更加的安全,方便和实用.实质上它也是对Java SE库中Thread的一个封装,加上了平台相关的特性,所以对于所有的多线程异步都强烈推荐使用AsyncTask,因为它考虑,也融入了Android平台的特性,

  • android使用AsyncTask实现多线程下载实例

    AsyncTask不仅方便我们在子线程中对UI进行更新操作,还可以借助其本身的线程池来实现多线程任务.下面是一个使用AsyncTask来实现的多线程下载例子. 01 效果图 02 核心类 - DownloadTask.class public class DownloadTask extends AsyncTask<String, Integer, Integer> { public static final int TYPE_SUCCESS = 0; public static final

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

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

  • Android 使用AsyncTask实现断点续传

    之前公司里面项目的下载模块都是使用xUtils提供的,最近看了下xUtils的源码,它里面也是使用AsyncTask来执行异步任务的,它的下载也包含了断点续传的功能.这里我自己也使用AsyncTask也实现了简单的断点续传的功能. 首先说一说AsyncTask吧,先来看看AsyncTask的定义: public abstract class AsyncTask<Params, Progress, Result> 三种泛型类型分别代表"启动任务执行的输入参数"."后

  • Android多线程AsyncTask详解

    本篇随笔将讲解一下Android的多线程的知识,以及如何通过AsyncTask机制来实现线程之间的通信. 一.android当中的多线程 在Android当中,当一个应用程序的组件启动的时候,并且没有其他的应用程序组件在运行时,Android系统就会为该应用程序组件开辟一个新的线程来执行.默认的情况下,在一个相同Android应用程序当中,其里面的组件都是运行在同一个线程里面的,这个线程我们称之为Main线程.当我们通过某个组件来启动另一个组件的时候,这个时候默认都是在同一个线程当中完成的.当然

  • Android使用AsyncTask实现多线程下载的方法

    本文实例讲述了Android使用AsyncTask实现多线程下载的方法.分享给大家供大家参考,具体如下: public class MainActivity extends Activity implements OnClickListener { private Button btn1, btn2, btn3; private ProgressBar progressBar1, progressBar2, progressBar3; private ImageView img1, img2,

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

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

  • 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实现网络多线程断点续传下载功能

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

随机推荐