首页 > 代码库 > Android实现多线程断点续传

Android实现多线程断点续传

前言: 项目都快交付阶段了,客户说要改个需求,添加一个断点续传功能。在版本更新,杂志下载或者视频下载的时候实现断点续传。由于时间紧迫,想起了之前研究过一个demo代码,就直接修改使用了,根据自己的方式实现,但是核心代码没变。以后或许会用到,于是就专门写了个demo。


先看一下项目目录结构:


  1. db--->操作数据库的(创建数据库表,数据的增删改查。)
  2. util--->工具类
  3. download--->实现下载(下载器以及自定义线程。)

这里以易信客户端的下载为例,简要介绍。

String downloadPath = "http://gdown.baidu.com/data/wisegame/653346a13ab69081/yixin_146.apk";
	String fileName = "易信.apk";

在这里我添加了通知栏,查看进度。


大致思路:根据需要下载文件的总大小,可以自定义几个线程同时下载动作(在这里我给下载器构造方法重载了,默认不设定线程数量的话默认是3个)。

内部使用map键值对的方式缓存线程下载数据,key是线程id,value:是下载量。同时还有个线程数组,这个线程数组和前面的Map是对应的。

在下载器中,有个循环会不断检查每个线程的下载任务是否完成,完成了则将其T掉,这样到最后就都完成后,退出循环,整个下载任务就完成。删除数据库表中的下载记录。


然后每个线程分配下载文件大小值,执行下载。下载过程中,通过回调接口,回调到MainActivity画面中,利用handler实现更新UI。

......

这里具体的问题细节,啊还有很多,大致思路就是这样了。

代码里面我都写了详细的注释,贴点代码把。希望你也能看懂。


package com.example.download;

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

import com.example.util.Common;

import android.util.Log;

/**
 * 用于下载文件的线程。 该线程有被分配的工作量:block
 * 
 */
public class DownloadThread extends Thread {
	private static final String TAG = "DownloadThread";
	/**
	 * 目标文件
	 */
	private File saveFile;
	/**
	 * 文件下载路径
	 */
	private URL downUrl;
	/**
	 * 当前线程需要下载的文件大小
	 */
	private int block;

	/**
	 * 线程身份识别id
	 */
	private int threadId = -1;
	/**
	 * 之前已经下载的位置
	 */
	private int downLength;
	/**
	 * 判断当前线程是否结束
	 */
	private boolean isFinished = false;
	/**
	 * 文件下载器
	 */
	private FileDownloader downloader;

	/**
	 * @param downloader
	 *            下载器
	 * @param downUrl
	 *            下载的网络路径
	 * @param saveFile
	 *            保存的目标文件
	 * @param block
	 *            下载任务量
	 * @param downLength
	 *            已经下载的大小
	 * @param threadId
	 *            线程id
	 */
	public DownloadThread(FileDownloader downloader, URL downUrl,
			File saveFile, int block, int downLength, int threadId) {
		this.downUrl = downUrl;
		this.saveFile = saveFile;
		this.block = block;
		this.downloader = downloader;
		this.threadId = threadId;
		this.downLength = downLength;
	}

	@Override
	public void run() {
		if (downLength < block) {// 未下载完成
			try {
				// 使用Get方式下载
				HttpURLConnection http = Common.getHttpParams(downUrl);

				// 开始下载位置
				int startPosition = block * (threadId - 1) + downLength;
				// 结束下载位置
				int endPosition = block * threadId - 1;
				// 设置获取实体数据的范围
				http.setRequestProperty("Range", "bytes=" + startPosition + "-"
						+ endPosition);
				// 获取文件输入流
				InputStream inStream = http.getInputStream();
				byte[] buffer = new byte[1024];
				int offset = 0;
				print("Thread " + this.threadId
						+ " start download from position " + endPosition);

				RandomAccessFile threadfile = new RandomAccessFile(
						this.saveFile, "rwd");
				threadfile.seek(endPosition);

				while ((offset = inStream.read(buffer, 0, 1024)) != -1) {
					threadfile.write(buffer, 0, offset);
					// 下载量累加
					downLength += offset;
					// 更新下载数据库
					downloader.update(this.threadId, downLength);
					downloader.append(offset);
				}

				threadfile.close();
				inStream.close();
				print("Thread " + this.threadId + " download finish");
				// 设置下载完成标志位。
				this.isFinished = true;
			} catch (Exception e) {
				this.downLength = -1;
				print("Thread " + this.threadId + ":" + e);
			}
		}
	}

	/**
	 * 打印日志信息
	 * 
	 * @param msg
	 */
	private static void print(String msg) {
		Log.i(TAG, msg);
	}

	/**
	 * 下载是否完成
	 * 
	 * @return
	 */
	public boolean isFinish() {
		return isFinished;
	}

	/**
	 * 已经下载的内容大小
	 * 
	 * @return 如果返回值为-1,代表下载失败
	 */
	public long getDownLength() {
		return downLength;
	}
}

下载器


package com.example.download;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.content.Context;
import android.util.Log;

import com.example.db.DBTools;
import com.example.util.Common;

/**
 * 
 * 文件下载
 * 
 * 
 * 
 */
public class FileDownloader {
	private static final String TAG = "FileDownloader";
	private Context context;
	/**
	 * 操作数据库的类
	 */
	private DBTools dbTools;

	/**
	 * 已下载文件长度
	 */
	private int downloadSize = 0;

	/**
	 * 原始文件长度
	 */
	private int fileSize = 0;

	/**
	 * 线程数
	 */
	private DownloadThread[] threadArr;

	/**
	 * 本地保存文件
	 */
	private File saveFile;

	/**
	 * 默认下载线程数量
	 */
	private static final int defaultThreadSize = 3;

	/**
	 * 缺省文件下载存放位置
	 */
	private static String defaultDestPath = "";

	/**
	 * 用于缓存各个线程下载的长度
	 * <p>
	 * Key:对应线程id;Value:对应线程已经下载的长度
	 * </p>
	 */
	private Map<Integer, Integer> cacheDownMap = new ConcurrentHashMap<Integer, Integer>();

	/**
	 * 每条线程应该下载的长度 
	 */
	private int block;

	/**
	 * 文件下载路径
	 */
	private String downloadUrl;

	/**
	 * 获取线程数
	 */
	public int getThreadSize() {
		return threadArr.length;
	}

	/**
	 * 获取文件大小
	 * 
	 * @return 需要下载文件的总长度
	 */
	public int getFileSize() {
		return fileSize;
	}

	/**
	 * 累加文件已下载的大小
	 * 
	 * @param size
	 *            此刻下载量
	 */
	protected synchronized void append(int size) {
		downloadSize += size;
	}

	/**
	 * 更新指定线程最后下载的位置
	 * 
	 * @param threadId
	 *            线程id
	 * @param pos
	 *            最后下载的位置
	 */
	protected synchronized void update(int threadId, int pos) {
		this.cacheDownMap.put(threadId, pos);
		this.dbTools.update(this.downloadUrl, this.cacheDownMap);
	}

	/**
	 * 构建文件下载器
	 * 
	 * @param downloadUrl
	 *            下载路径
	 * @param fileSaveDir
	 *            文件保存目录
	 * @param threadNum
	 *            下载线程数
	 */
	public FileDownloader(Context context, String downloadUrl, String destPath,
			int threadNum) {
		this.context = context;
		this.downloadUrl = downloadUrl;
		dbTools = new DBTools(this.context);
		checkPath(destPath);
		this.threadArr = new DownloadThread[threadNum];

		setRequest(downloadUrl, destPath);
	}

	/**
	 * 检查下载路径是否存在,不存在则创建。。
	 * @param destPath
	 * @return 是否存在该目录
	 */
	public boolean checkPath(String destPath) {

		File file = new File(destPath);

		if (!file.exists()) {

			boolean bol = file.mkdirs();

			return bol;
		}
		return true;
	}

	/**
	 * @param context
	 * @param downloadUrl
	 */
	public FileDownloader(Context context, String downloadUrl) {

		this.context = context;
		this.downloadUrl = downloadUrl;
		this.threadArr = new DownloadThread[defaultThreadSize];
		this.dbTools = new DBTools(this.context);

		dbTools = new DBTools(this.context);
		checkPath(defaultDestPath);
		this.threadArr = new DownloadThread[defaultThreadSize];
		setRequest(downloadUrl, defaultDestPath);
	}

	/**
	 * 封装请求
	 */
	public void setRequest(String downUrl, String destPath) {

		URL url = null;
		try {
			url = new URL(downUrl);

			HttpURLConnection conn = Common.getHttpParams(url);
			conn.connect();
			// 打印头部信息
			printResponseHeader(conn);

			// 200==》成功连接...
			if (conn.getResponseCode() == 200) {
				// 获取文件总大小
				this.fileSize = conn.getContentLength();
				if (this.fileSize <= 0)
					throw new RuntimeException("该文件很可能不存在...");

				// 获取当前需要下载文件名称
				String filename = getFileName(conn);
				// 目标保存文件
				this.saveFile = new File(destPath, filename);
				// 获取当前路径下载记录包括已经下载数量。
				Map<Integer, Integer> logdata = http://www.mamicode.com/dbTools.getData(downUrl);>

package com.example.util;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * <p>
 * </p>
 * 
 * @author xiangxm
 */
public class Common {

	/**
	 * 格式化文件大
	 * 
	 * @param volume
	 *            文件大小
	 * @return 格式化的字符
	 */
	public static String getVolume(long volume) {

		float num = 1.0F;

		String str = null;

		if (volume < 1024) {
			str = volume + "B";
		} else if (volume < 1048576) {
			num = num * volume / 1024;
			str = String.format("%.1f", num) + "K";
		} else if (volume < 1073741824) {
			num = num * volume / 1048576;
			str = String.format("%.1f", num) + "M";
		} else if (volume < 1099511627776L) {
			num = num * volume / 1073741824;
			str = String.format("%.1f", num) + "G";
		}

		return str;
	}

	/**
	 * 
	 * 设置网络参数
	 */
	public static HttpURLConnection getHttpParams(URL httpUrl) {

		HttpURLConnection http;
		try {
			http = (HttpURLConnection) httpUrl.openConnection();

			http.setConnectTimeout(5 * 1000);
			http.setRequestMethod("GET");
			http.setRequestProperty(
					"Accept",
					"image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
			http.setRequestProperty("Accept-Language", "zh-CN");
			http.setRequestProperty("Referer", httpUrl.toString());
			http.setRequestProperty("Charset", "UTF-8");

			http.setRequestProperty(
					"User-Agent",
					"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
			http.setRequestProperty("Connection", "Keep-Alive");

			return http;
		} catch (IOException e) {

			e.printStackTrace();
		}

		return null;
	}

}


源代码下载地址:http://download.csdn.net/detail/xxm282828/7701071


看一下效果图:



下一篇打算:  使用downloadManager实现下载,断点续传。