后台多任务多线程断点下载

忘记图:



多线程断点下载事实上不是非常难。主要就是三个方面:

1、依据文件的大小和下载线程的数量,确定每一个下载线程要下载的切割文件的大小;

2、记录每一个下载线程已经下载完毕的进度。

3、将每一个线程下载的切割的文件合并到一个文件里。

那么怎么将远程的一个文件切割成三部分来下载呢?事实上在HTTP协议中,有一个Range字段。用于client到server端的请求,可通过该字段指定下载文件的某一段大小,及其单位。格式为:Range: bytes x - y,eg:Range: bytes=0-100 下载从第0 -- 100 字节范围的内容;有个这么个东西就能够非常easy的实现多线程下载了。

接下来就是记录每一个线程下载的进度,在android其中的存储方式基本能够用,像Sqlite、文件等。一个下载线程每次获取到的文件长度累加起来的这个数值就是当前线程到眼下为止所下载的进度,而我们所要记录就是这个数值。

最后就是合并了,首先。先在本地创建一个文件。这个文件的大小和我们要下载的文件大小是相等的;然后用java提供的RandomAccessFile这个类,这个类中有这么一个方法seek();表示的是从哪个位置開始写入数据。而这个位置也就是我们要传入的參数是一个int类型的。

通过以上的几个步骤就能够简单实现多线程下载了。接下来略微说一下基本的一些代码流程:


1、将我们要下载的文件名和地址传到service中

2、对每个下载任务创建一个下载器(Downloader)。并保存到一个集合其中。这样就能够实现多任务下载了

3、获取下载器的具体信息(文件大小、进度、地址),同一时候在这个步骤还需初始化下载器,在本地创建一个同样大小的文件,并确定每个线程要下载的大小,保存到数据库其中

4、开启线程開始下载文件。同一时候将下载的数据写入创建好的文件其中

5、将每一个线程下载的进度传给service,用来更新进度条

具体的代码就不贴了。就贴一下基本的源代码,具体要的话能够下一下源代码。

package com.wpy.multithreadeddownload.service;

import java.util.HashMap;
import java.util.Map;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

import com.wpy.multithreadeddownload.constant.Constant;
import com.wpy.multithreadeddownload.dao.SqliteDao;
import com.wpy.multithreadeddownload.entity.DownloaderInfo;
import com.wpy.multithreadeddownload.entity.FileState;
import com.wpy.multithreadeddownload.util.Downloader;

/**
 * 
 * 项目名称:MultithreadedDownload 类名称:DownloadService 类描写叙述: 后台下载 创建人:wpy
 * 创建时间:2014-10-10 下午5:18:31
 * 
 */
public class DownloadService extends Service {

	// 下载器
	private Downloader downloader;

	private SqliteDao dao;
	/**
	 * 存放各个下载器
	 */
	private Map<String, Downloader> downloaders = new HashMap<String, Downloader>();

	/**
	 * 存放每一个下载文件的总长度
	 */
	private Map<String, Integer> fileSizes = new HashMap<String, Integer>();
	/**
	 * 存放每一个下载文件完毕的长度
	 */
	private Map<String, Integer> completeSizes = new HashMap<String, Integer>();

	/**
	 * 消息处理 接收Download中每一个线程传输过来的数据
	 */
	private Handler mHandler = new Handler() {
		public void handleMessage(android.os.Message msg) {
			if (msg.what == 1) {
				String url = (String) msg.obj;
				int length = msg.arg1;

				int completeSize = completeSizes.get(url);
				completeSize = completeSize + length;
				completeSizes.put(url, completeSize);

				// Log.e("test>>", "消息处理器Handler当前进度:" + completeSize);

				int fileSize = fileSizes.get(url);
				if (completeSize == fileSize) {// 下载完毕
					dao.updataStateByUrl(url);
					downloaders.get(url).delete(url);
					downloaders.remove(url);
					if (downloaders.isEmpty()) {// 假设所有下载完毕。关闭service
						stopSelf();
					}
				}
				// 发送广播更新下载管理的进度
				Intent intent = new Intent();
				intent.setAction(Constant.DOWNLOADMANAGEACTION);
				intent.putExtra("completeSize", completeSize);
				intent.putExtra("url", url);
				DownloadService.this.sendBroadcast(intent);
			}
		};
	};

	@Override
	public void onCreate() {
		super.onCreate();
		dao = new SqliteDao(this);
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		String urlPath = intent.getStringExtra("downloadUrl");
		String name = intent.getStringExtra("name");
		String flag = intent.getStringExtra("flag");
		if (flag.equals("startDownload")) {
			startDownload(name, urlPath, true);
		}
		if (flag.equals("changeState")) {
			changeState(name, urlPath);
		}
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	/**
	 * 開始下载
	 * 
	 * @param urlPath
	 *            下载地址
	 */
	private void startDownload(final String name, final String urlPath,
			final boolean isFirst) {
		Log.e("test>>", "文件的名称:" + name);
		Log.e("test>>", "文件的下载地址:" + urlPath);
		// 初始化一个下载器
		downloader = downloaders.get(urlPath);
		if (null == downloader) {
			downloader = new Downloader(name, urlPath, Constant.LOCALPATH,
					Constant.THREADCOUNT, this, mHandler);
			downloaders.put(urlPath, downloader);
		}
		if (downloader.isDownloading()) {
			return;
		}

		new Thread() {
			public void run() {
				DownloaderInfo downloaderInfo = downloader.getDownloaderInfos();
				completeSizes.put(urlPath, downloaderInfo.getComplete());

				if (fileSizes.get(urlPath) == null) {
					fileSizes.put(urlPath, downloaderInfo.getFileSize());
				}

				// FileState state = dao.query(urlPath);
				if (isFirst) {
					Log.e("test>>", "文件:" + name + "第一次下载");
					FileState fileState = new FileState(name, urlPath, 1,
							downloaderInfo.getComplete(),
							downloaderInfo.getFileSize());
					dao.saveFileState(fileState);
				}

				downloader.download();
			};
		}.start();
	}

	/**
	 * 更改下载状态(若文件正在下载,就暂停。若暂停,则開始下载)
	 * 
	 * @param url
	 *            下载地址
	 */
	public void changeState(String name, String url) {
		Downloader loader = downloaders.get(url);
		if (loader != null) {
			if (loader.isDownloading()) {// 正在下载
				loader.setPause();
			} else if (loader.isPause()) {// 暂停
				loader.reset();
				this.startDownload(name, url, false);
			}
		} else {
			startDownload(name, url, false);
		}
	}
}

package com.wpy.multithreadeddownload.util;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

import com.wpy.multithreadeddownload.dao.SqliteDao;
import com.wpy.multithreadeddownload.entity.DownloadInfo;
import com.wpy.multithreadeddownload.entity.DownloaderInfo;

/**
 * 
 * 项目名称:MultithreadedDownload 类名称:Downloader 类描写叙述: 下载器 创建人:wpy 创建时间:2014-10-11
 * 上午9:24:01
 * 
 */
public class Downloader {
	private String fileName;// 文件名
	private String downloadPath;// 下载地址
	private String localPath;// 本地保存的地址
	private int threadCount;// 下载线程的数量
	private int fileSize;// 下载文件的大小
	private Context context;// 上下文
	private Handler mHandler;// 消息处理器
	private List<DownloadInfo> infos;// 存放下载信息的集合

	// 定义三种下载状态:初始化、下载中、暂停
	private static final int INIT = 1;
	private static final int DOWNLOADING = 2;
	private static final int PAUSE = 3;
	private int state = INIT;// 设置状态为初始化

	private SqliteDao dao;

	/**
	 * 构造函数
	 * 
	 * @param fileName
	 *            文件名
	 * @param downloadPath
	 *            下载地址
	 * @param localPath
	 *            本地存储地址
	 * @param threadCount
	 *            线程数量
	 * @param context
	 *            上下文
	 * @param mHandler
	 *            消息处理器
	 */
	public Downloader(String fileName, String downloadPath, String localPath,
			int threadCount, Context context, Handler mHandler) {
		this.fileName = fileName;
		this.downloadPath = downloadPath;
		this.localPath = localPath;
		this.threadCount = threadCount;
		this.context = context;
		this.mHandler = mHandler;

		dao = new SqliteDao(context);
	}

	/**
	 * 获取下载器信息。 首先推断是否是第一次下载, 是第一次下载的话要进行初始化操作,并将下载器的信息保存到数据库中;
	 * 假设不是。就从数据库中读取之前下载的信息(起始位置,结束位置。文件大小等),并将下载信息返回给下载器。
	 * 
	 * @return 下载器信息(文件的大小、下载的完毕度、下载器标识/下载地址)
	 */
	public DownloaderInfo getDownloaderInfos() {
		if (isFirstDownload(downloadPath)) {// 第一次下载

			init();
			if (fileSize > 0) {
				int range = fileSize / threadCount;

				Log.e("test>>", "每一个线程下载的大小:" + range);

				infos = new ArrayList<DownloadInfo>();

				for (int i = 0; i < threadCount - 1; i++) {
					DownloadInfo info = new DownloadInfo(i, i * range, (i + 1)
							* range - 1, 0, downloadPath);

					Log.e("test>>", "线程<" + i + ">下载的大小:" + i * range + "---"
							+ ((i + 1) * range - 1));

					infos.add(info);
				}
				DownloadInfo info = new DownloadInfo(threadCount - 1,
						(threadCount - 1) * range, fileSize - 1, 0,
						downloadPath);

				Log.e("test>>", "线程<" + (threadCount - 1) + ">下载的大小:"
						+ (threadCount - 1) * range + "---" + (fileSize - 1));

				infos.add(info);
				// 保存下载器信息到数据库
				dao.saveDownloadInfos(infos);

			}
			// 创建一个DownloaderInfo记录下载器的详细信息
			return new DownloaderInfo(fileSize, 0, downloadPath);

		} else {
			// 不是第一次下载。从数据库中获取已有的downloadPath下载地址的下载器的详细信息
			infos = dao.getDownloadInfos(downloadPath);
			int size = 0;// 文件总大小
			int completeSize = 0;// 下载的总长度
			for (DownloadInfo info : infos) {
				size = size + (info.getEndPos() - info.getStartPos() + 1);
				completeSize = completeSize + info.getCompeleteSize();
			}
			return new DownloaderInfo(size, completeSize, downloadPath);
		}

	}

	/**
	 * 初始化下载器(获取要下载文件的大小;依据本地地址,在本地创建一个同样大小的文件)
	 */
	private void init() {
		try {
			URL url = new URL(downloadPath);// 通过给定的下载地址得到一个url
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();// 得到一个http连接
			conn.setConnectTimeout(5 * 1000);// 设置连接超时为5秒钟
			conn.setRequestMethod("GET");// 设置连接方式为GET

			Log.e("test>>", "获取前  文件的大小:" + fileSize);

			int code = conn.getResponseCode();
			Log.e("test>>", "网络请求的返回码:" + code);
			// 假设http返回的代码是200或者206则为连接成功
			if (conn.getResponseCode() == 200 || conn.getResponseCode() == 206) {
				fileSize = conn.getContentLength();// 得到文件的大小

				Log.e("test>>", "文件的大小:" + fileSize);

				if (fileSize <= 0) {
					Toast.makeText(context, "网络故障,无法获取文件大小", Toast.LENGTH_SHORT)
							.show();
				}
				File dir = new File(localPath);
				if (!dir.exists()) {// 文件不存在
					if (dir.mkdirs()) {
						System.out.println("mkdirs success.");
					}
				}
				File file = new File(localPath, fileName);
				RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
				accessFile.setLength(fileSize);
				accessFile.close();
			}

			conn.disconnect();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 開始下载数据
	 */
	public void download() {
		if (null != infos) {
			if (DOWNLOADING == state) {
				return;
			}
			state = DOWNLOADING;// 将下载状态设置为下载中
			for (DownloadInfo info : infos) {
				new DownloadThread(info.getThreadId(), info.getStartPos(),
						info.getEndPos(), info.getCompeleteSize(),
						info.getUrl()).start();
			}
		}
	}

	/**
	 * 
	 * 项目名称:MultithreadedDownload 类名称:DownloadThread 类描写叙述: 一个内部类,建立一个下载线程 创建人:wpy
	 * 创建时间:2014-10-11 下午1:08:12
	 * 
	 */
	private class DownloadThread extends Thread {

		private int threadId;
		private int startPos;
		private int endPos;
		private int completeSize;
		private String urlPath;

		/**
		 * 下载线程类的构造函数
		 * 
		 * @param threadId
		 *            线程id
		 * @param startPos
		 *            開始下载的节点
		 * @param endPos
		 *            停止下载的节点
		 * @param completeSize
		 *            下载完毕的进度
		 * @param urlPath
		 *            下载的地址
		 */
		public DownloadThread(int threadId, int startPos, int endPos,
				int completeSize, String urlPath) {
			this.threadId = threadId;
			this.startPos = startPos;
			this.endPos = endPos;
			this.completeSize = completeSize;
			this.urlPath = urlPath;
		}

		@Override
		public void run() {
			HttpURLConnection connection = null;
			RandomAccessFile accessFile = null;
			InputStream inputStream = null;
			File file = new File(localPath, fileName);
			try {
				URL url = new URL(urlPath);
				connection = (HttpURLConnection) url.openConnection();
				connection.setConnectTimeout(5 * 1000);
				connection.setRequestMethod("GET");

				// 设置http头中的Range字段。格式为:Range: bytes x - y
				// Range: 用于client到server端的请求,可通过该字段指定下载文件的某一段大小,及其单位。典型的格式如:
				// Range: bytes=0-499 下载第0-499字节范围的内容
				connection.setRequestProperty("Range", "bytes="
						+ (startPos + completeSize) + "-" + endPos);

				connection.connect();

				if (connection.getResponseCode() == 200
						|| connection.getResponseCode() == 206) {
					accessFile = new RandomAccessFile(file, "rwd");
					accessFile.seek(startPos + completeSize);// 设置从哪个位置写入数据

					inputStream = connection.getInputStream();
					byte[] buffer = new byte[4096];
					int length = -1;
					while ((length = inputStream.read(buffer)) != -1) {
						// 写入数据
						accessFile.write(buffer, 0, length);
						// 累加已经下载的长度
						completeSize = completeSize + length;
						// 更新数据中的信息
						dao.updataDownloadInfos(threadId, completeSize, urlPath);
						// 用消息将下载信息传给进度条,对进度条进行更新
						Message msg = Message.obtain();
						msg.what = 1;
						msg.obj = urlPath;
						msg.arg1 = length;
						mHandler.sendMessage(msg);// 给DownloadService发送消息

						// Log.e("test>>", "Downloader当前进度:" + completeSize);

						// 暂停
						if (PAUSE == state) {
							return;
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					// 关闭该关闭的东西
					inputStream.close();
					accessFile.close();
					connection.disconnect();
					// dao.closeDB();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 推断是否是第一次下载
	 * 
	 * @param downloadPath
	 *            下载地址
	 * @return true 第一次下载 false 再次下载
	 */
	private boolean isFirstDownload(String downloadPath) {
		return dao.isHasDownloadInfos(downloadPath);
	}

	/**
	 * 推断是否正在下载
	 * 
	 * @return true 是 false 否
	 */
	public boolean isDownloading() {
		return state == DOWNLOADING;
	}

	/**
	 * 推断是否暂停
	 * 
	 * @return true 是 false 否
	 */
	public boolean isPause() {
		return state == PAUSE;
	}

	/**
	 * 设置暂停
	 */
	public void setPause() {
		state = PAUSE;
	}

	/**
	 * 依据urlPath删除数据库中相应的下载器信息
	 * 
	 * @param urlPath
	 *            下载地址
	 */
	public void delete(String urlPath) {
		dao.delete(urlPath);
	}

	/**
	 * 重置下载状态
	 */
	public void reset() {
		state = INIT;
	}
}




我是源代码
原文地址:https://www.cnblogs.com/gcczhongduan/p/5037339.html