【Android开发经验】关于【多线程断点续传下载】功能的一个简单实现和讲解

转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992

上班第一天,在技术群里面和大家闲扯,无意中谈到了关于框架的使用,一个同学说为了用xUtils的断线续传下载功能,把整个库引入到了项目中,在google的官方建议中,是非常不建议这种做法的,集合框架虽然把很多功能集成起来,但是代码越多,出现问题的可能越大,而且无形之中增加了APK的大小,因此,得不偿失。所以,这篇文章主要就“断线续传”下载功能,简单的说下思路和代码实现,因为这类代码比较多,所以找了一个写的不错的demo,简单优化了一下。

在贴代码之前,我们先分析一下需求和解决思路。首先是下载功能,我们简单的使用HttpURLConnection就可以了,没有引入框架的必要,然后就是断点续传了,其实断点续传指的就是我们可以随时停止我们的下载任务,当下次再次开始的时候,可以从上次下载到的位置继续下载,节省下载时间,很方便也很实用,做法无非就是在下载的过程中,纪录下下载到的位置,当再次开始下载的时候,我们从上一次的位置继续请求服务器即可。说到这里,有个类不得不提,那就是RandomAccessFile,这个类是实现断点续传功能的核心类,RandomAccessFile允许我们从我们想要的位置进行读写操作,因此,我们可以把我们要下载的文件切分成几部分,然后开启多个线程,分别从文件不同的位置进行下载,这样等所有的部分都下载完成之后,我们就能够得到一个完整的文件了,这就是多线程下载的原理,完成上面几个步骤,我们的多线程断线续传下载功能就基本完成了,下面是在网上找的一个Demo,我对代码进行了部分修改,从代码里面,我们看一下如何进行代码的实现。

首先,如果要实现断点续传,我们就要纪录每个线程下载的文件的位置,可以使用文件,也可以使用sp,也可以使用DB,这个Demo里面使用的DB,我们首先看一下数据库的Helper实现类,里面存储主键、线程号、开始位置、结束位置、完成位置和下载地址即可。

DownLoadHelper.java

public class DownLoadHelper extends SQLiteOpenHelper {

	private static final String SQL_NAME = "download.db";
	private static final int DOWNLOAD_VERSION = 1;

	public DownLoadHelper(Context context) {
		super(context, SQL_NAME, null, DOWNLOAD_VERSION);
	}

	/**
	 * 在download.db数据库下创建一个download_info表存储下载信息
	 */
	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, "
				+ "start_pos integer, end_pos integer, compelete_size integer,url char)");
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

	}

}

有了Helper之后,我们在创建一个Sql工具类,完成对表的数据操作

DownlaodSqlTool.java

public class DownlaodSqlTool {

	private DownLoadHelper dbHelper;

	public DownlaodSqlTool(Context context) {
		dbHelper = new DownLoadHelper(context);
	}

	/**
	 * 创建下载的具体信息
	 */
	public void insertInfos(List<DownloadInfo> infos) {
		SQLiteDatabase database = dbHelper.getWritableDatabase();
		for (DownloadInfo info : infos) {
			String sql = "insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)";
			Object[] bindArgs = { info.getThreadId(), info.getStartPos(),
					info.getEndPos(), info.getCompeleteSize(), info.getUrl() };
			database.execSQL(sql, bindArgs);
		}
	}

	/**
	 * 得到下载具体信息
	 */
	public List<DownloadInfo> getInfos(String urlstr) {
		List<DownloadInfo> list = new ArrayList<DownloadInfo>();
		SQLiteDatabase database = dbHelper.getWritableDatabase();
		String sql = "select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";
		Cursor cursor = database.rawQuery(sql, new String[] { urlstr });
		while (cursor.moveToNext()) {
			DownloadInfo info = new DownloadInfo(cursor.getInt(0),
					cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),
					cursor.getString(4));
			list.add(info);
		}
		return list;
	}

	/**
	 * 更新数据库中的下载信息
	 */
	public void updataInfos(int threadId, int compeleteSize, String urlstr) {
		SQLiteDatabase database = dbHelper.getWritableDatabase();
		String sql = "update download_info set compelete_size=? where thread_id=? and url=?";
		Object[] bindArgs = { compeleteSize, threadId, urlstr };
		database.execSQL(sql, bindArgs);
	}

	/**
	 * 关闭数据库
	 */
	public void closeDb() {
		dbHelper.close();
	}

	/**
	 * 下载完成后删除数据库中的数据
	 */
	public void delete(String url) {
		SQLiteDatabase database = dbHelper.getWritableDatabase();
		database.delete("download_info", "url=?", new String[] { url });
	}
}

数据库相关的类就这些,断点续传的功能已经完成,下面看下载如何实现。

首先,为了操作方便,我们对下载的文件抽取实体类

DownloadInfo.java

public class DownloadInfo {

	private int threadId;// 下载器id
	private int startPos;// 开始点
	private int endPos;// 结束点
	private int compeleteSize;// 完成度
	private String url;// 下载文件的URL地址

	public DownloadInfo(int threadId, int startPos, int endPos,
			int compeleteSize, String url) {
		this.threadId = threadId;
		this.startPos = startPos;
		this.endPos = endPos;
		this.compeleteSize = compeleteSize;
		this.url = url;
	}

	public DownloadInfo() {
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public int getThreadId() {
		return threadId;
	}

	public void setThreadId(int threadId) {
		this.threadId = threadId;
	}

	public int getStartPos() {
		return startPos;
	}

	public void setStartPos(int startPos) {
		this.startPos = startPos;
	}

	public int getEndPos() {
		return endPos;
	}

	public void setEndPos(int endPos) {
		this.endPos = endPos;
	}

	public int getCompeleteSize() {
		return compeleteSize;
	}

	public void setCompeleteSize(int compeleteSize) {
		this.compeleteSize = compeleteSize;
	}

	@Override
	public String toString() {
		return "DownloadInfo [threadId=" + threadId + ", startPos=" + startPos
				+ ", endPos=" + endPos + ", compeleteSize=" + compeleteSize
				+ "]";
	}
}

实体类抽取之后,我们就可以实现下载功能了,DownloadHttpTool是实现下载功能的主要类

DownloadHttpTool.java

public class DownloadHttpTool {

	private static final String TAG = DownloadHttpTool.class.getSimpleName();
	// 线程数量
	private int threadCount;
	// URL地址
	private String urlstr;
	private Context mContext;
	private Handler mHandler;
	// 保存下载信息的类
	private List<DownloadInfo> downloadInfos;
	// 目录
	private String localPath;
	// 文件名
	private String fileName;
	private int fileSize;
	// 文件信息保存的数据库操作类
	private DownlaodSqlTool sqlTool;

	// 利用枚举表示下载的三种状态
	private enum Download_State {
		Downloading, Pause, Ready, Delete;
	}

	// 当前下载状态
	private Download_State state = Download_State.Ready;
	// 所有线程下载的总数
	private int globalCompelete = 0;

	public DownloadHttpTool(int threadCount, String urlString,
			String localPath, String fileName, Context context, Handler handler) {
		super();
		this.threadCount = threadCount;
		this.urlstr = urlString;
		this.localPath = localPath;
		this.mContext = context;
		this.mHandler = handler;
		this.fileName = fileName;
		sqlTool = new DownlaodSqlTool(mContext);
	}

	// 在开始下载之前需要调用ready方法进行配置
	public void ready() {
		Log.w(TAG, "ready");
		globalCompelete = 0;
		downloadInfos = sqlTool.getInfos(urlstr);
		if (downloadInfos.size() == 0) {
			initFirst();
		} else {
			File file = new File(localPath + "/" + fileName);
			if (!file.exists()) {
				sqlTool.delete(urlstr);
				initFirst();
			} else {
				fileSize = downloadInfos.get(downloadInfos.size() - 1)
						.getEndPos();
				for (DownloadInfo info : downloadInfos) {
					globalCompelete += info.getCompeleteSize();
				}
				Log.w(TAG, "globalCompelete:::" + globalCompelete);
			}
		}
	}

	public void start() {
		Log.w(TAG, "start");
		if (downloadInfos != null) {
			if (state == Download_State.Downloading) {
				return;
			}
			state = Download_State.Downloading;
			for (DownloadInfo info : downloadInfos) {
				Log.v(TAG, "startThread");
				new DownloadThread(info.getThreadId(), info.getStartPos(),
						info.getEndPos(), info.getCompeleteSize(),
						info.getUrl()).start();
			}
		}
	}

	public void pause() {
		state = Download_State.Pause;
		sqlTool.closeDb();
	}

	public void delete() {
		state = Download_State.Delete;
		compelete();
		new File(localPath + File.separator + fileName).delete();
	}

	public void compelete() {
		sqlTool.delete(urlstr);
		sqlTool.closeDb();
	}

	public int getFileSize() {
		return fileSize;
	}

	public int getCompeleteSize() {
		return globalCompelete;
	}

	/**
	 * 第一次下载初始化
	 */
	private void initFirst() {
		Log.w(TAG, "initFirst");
		try {
			URL url = new URL(urlstr);
			HttpURLConnection connection = (HttpURLConnection) url
					.openConnection();
			connection.setConnectTimeout(5000);
			connection.setRequestMethod("GET");
			fileSize = connection.getContentLength();
			Log.w(TAG, "fileSize::" + fileSize);
			File fileParent = new File(localPath);
			if (!fileParent.exists()) {
				fileParent.mkdir();
			}
			File file = new File(fileParent, fileName);
			if (!file.exists()) {
				file.createNewFile();
			}
			// 本地访问文件
			RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
			accessFile.setLength(fileSize);
			accessFile.close();
			connection.disconnect();
		} catch (Exception e) {
			e.printStackTrace();
		}
		int range = fileSize / threadCount;
		downloadInfos = new ArrayList<DownloadInfo>();
		for (int i = 0; i < threadCount - 1; i++) {
			DownloadInfo info = new DownloadInfo(i, i * range, (i + 1) * range
					- 1, 0, urlstr);
			downloadInfos.add(info);
		}
		DownloadInfo info = new DownloadInfo(threadCount - 1, (threadCount - 1)
				* range, fileSize - 1, 0, urlstr);
		downloadInfos.add(info);
		sqlTool.insertInfos(downloadInfos);
	}

	/**
	 * 自定义下载线程
	 *
	 * @author zhaokaiqiang
	 * @time 2015-2-25下午5:52:28
	 */
	private class DownloadThread extends Thread {

		private int threadId;
		private int startPos;
		private int endPos;
		private int compeleteSize;
		private String urlstr;
		private int totalThreadSize;

		public DownloadThread(int threadId, int startPos, int endPos,
				int compeleteSize, String urlstr) {
			this.threadId = threadId;
			this.startPos = startPos;
			this.endPos = endPos;
			totalThreadSize = endPos - startPos + 1;
			this.urlstr = urlstr;
			this.compeleteSize = compeleteSize;
		}

		@Override
		public void run() {
			HttpURLConnection connection = null;
			RandomAccessFile randomAccessFile = null;
			InputStream is = null;
			try {
				randomAccessFile = new RandomAccessFile(localPath
						+ File.separator + fileName, "rwd");
				randomAccessFile.seek(startPos + compeleteSize);
				URL url = new URL(urlstr);
				connection = (HttpURLConnection) url.openConnection();
				connection.setConnectTimeout(5000);
				connection.setRequestMethod("GET");
				connection.setRequestProperty("Range", "bytes="
						+ (startPos + compeleteSize) + "-" + endPos);
				is = connection.getInputStream();
				byte[] buffer = new byte[1024];
				int length = -1;
				while ((length = is.read(buffer)) != -1) {
					randomAccessFile.write(buffer, 0, length);
					compeleteSize += length;
					Message message = Message.obtain();
					message.what = threadId;
					message.obj = urlstr;
					message.arg1 = length;
					mHandler.sendMessage(message);
					Log.w(TAG, "Threadid::" + threadId + "    compelete::"
							+ compeleteSize + "    total::" + totalThreadSize);
					// 当程序不再是下载状态的时候,纪录当前的下载进度
					if ((state != Download_State.Downloading)
							|| (compeleteSize >= totalThreadSize)) {
						sqlTool.updataInfos(threadId, compeleteSize, urlstr);
						break;
					}
				}

			} catch (Exception e) {
				e.printStackTrace();
				sqlTool.updataInfos(threadId, compeleteSize, urlstr);
			} finally {
				try {
					if (is != null) {
						is.close();
					}
					randomAccessFile.close();
					connection.disconnect();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
}

在上面的代码中,我们定义了一个线程,进行文件的多线程下载,并且在退出下载状态和完成下载的时候,纪录下载的位置,存到数据库中。在原来的代码中,是没获取一次数据,存取一次数据库,大大的增加了数据库的操作频率,降低了效率,原先下载420k左右的文件,需要操作420次数据库,现在只要一次即可。

在initFirst()里面,首先进行了初始化,根据下载文件的大小和开启线程的数量,对下载实体类进行了初始化和赋值。在RandomAccessFile创建完毕,DownloadInfo初始化完毕之后,就可以通过start()进行文件的下载了。

其实到这里,基本的功能已经实现了。为了使得我们的操作更加的方便,同时可以监控到下载的进度,我们对下载类进行一次封装,代码如下:

DownloadUtil.java

public class DownloadUtil {

	private DownloadHttpTool mDownloadHttpTool;
	private OnDownloadListener onDownloadListener;

	private int fileSize;
	private int downloadedSize = 0;

	@SuppressLint("HandlerLeak")
	private Handler mHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			int length = msg.arg1;
			synchronized (this) {// 加锁保证已下载的正确性
				downloadedSize += length;

			}
			if (onDownloadListener != null) {
				onDownloadListener.downloadProgress(downloadedSize);
			}
			if (downloadedSize >= fileSize) {
				mDownloadHttpTool.compelete();
				if (onDownloadListener != null) {
					onDownloadListener.downloadEnd();
				}
			}
		}

	};

	public DownloadUtil(int threadCount, String filePath, String filename,
			String urlString, Context context) {

		mDownloadHttpTool = new DownloadHttpTool(threadCount, urlString,
				filePath, filename, context, mHandler);
	}

	// 下载之前首先异步线程调用ready方法获得文件大小信息,之后调用开始方法
	public void start() {
		new AsyncTask<Void, Void, Void>() {

			@Override
			protected Void doInBackground(Void... arg0) {
				mDownloadHttpTool.ready();
				return null;
			}

			@Override
			protected void onPostExecute(Void result) {
				fileSize = mDownloadHttpTool.getFileSize();
				downloadedSize = mDownloadHttpTool.getCompeleteSize();
				Log.w("Tag", "downloadedSize::" + downloadedSize);
				if (onDownloadListener != null) {
					onDownloadListener.downloadStart(fileSize);
				}
				mDownloadHttpTool.start();
			}
		}.execute();
	}

	public void pause() {
		mDownloadHttpTool.pause();
	}

	public void delete() {
		mDownloadHttpTool.delete();
	}

	public void reset() {
		mDownloadHttpTool.delete();
		start();
	}

	public void setOnDownloadListener(OnDownloadListener onDownloadListener) {
		this.onDownloadListener = onDownloadListener;
	}

	// 下载回调接口
	public interface OnDownloadListener {
		public void downloadStart(int fileSize);

		public void downloadProgress(int downloadedSize);

		public void downloadEnd();
	}
}

通过对外暴露接口,我们可以实现下载进度的监听了!

用的时候也很简单,像下面这样就ok了

String urlString = "http://bbra.cn/Uploadfiles/imgs/20110303/fengjin/013.jpg";
		final String localPath = Environment.getExternalStorageDirectory()
				.getAbsolutePath() + "/ADownLoadTest";
		mDownloadUtil = new DownloadUtil(2, localPath, "abc.jpg", urlString,
				this);
		mDownloadUtil.setOnDownloadListener(new OnDownloadListener() {

			@Override
			public void downloadStart(int fileSize) {
				max = fileSize;
				mProgressBar.setMax(fileSize);
			}

			@Override
			public void downloadProgress(int downloadedSize) {
				mProgressBar.setProgress(downloadedSize);
				total.setText((int) downloadedSize * 100 / max + "%");
			}

			@Override
			public void downloadEnd() {
				Bitmap bitmap = decodeSampledBitmapFromResource(localPath
						+ File.separator + "abc.jpg", 200, 200);
				image.setImageBitmap(bitmap);
			}
		});
时间: 2024-12-14 20:35:41

【Android开发经验】关于【多线程断点续传下载】功能的一个简单实现和讲解的相关文章

Android实现网络多线程断点续传下载(转)

本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多线程断点需要什么功能? 1.多线程下载, 2.支持断点. 使用多线程的好处:使用多线程下载会提升文件下载的速度.那么多线程下载文件的过程是:  (1)首先获得下载文件的长度,然后设置本地文件的长度. HttpURLConnection.getContentLength();//获取下载文件的长度 R

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

本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多线程断点需要什么功能? 1.多线程下载, 2.支持断点. 使用多线程的好处:使用多线程下载会提升文件下载的速度.那么多线程下载文件的过程是: (1)首先获得下载文件的长度,然后设置本地文件的长度. HttpURLConnection.getContentLength();//获取下载文件的长度 Ra

Android多线程断点续传下载原理及实现

这段时间看了看工作室的工具库的下载组件,发现其存在一些问题: 1.下载核心逻辑有 bug,在暂停下载或下载失败等情况时有概率无法顺利完成下载.2.虽然原来的设计是采用多线程断点续传的设计,但打了一下日志发现其实下载任务都是在同一个线程下串行执行,并没有起到加快下载速度的作用. 考虑到原来的代码并不复杂,因此对这部分下载组件进行了重写.这里记录一下里面的多线程断点续传功能的实现. 请查看完整的PDF版(更多完整项目下载.未完待续.源码.图文知识后续上传github.)可以点击关于我联系我获取完整P

Android多线程断点续传下载

这个月接到一个项目,要写一个像360助手一样的对于软件管理的APP:其中,遇到了一个问题:多线程断点下载 这个 ,由于之前没有写过这方面的应用功能.所以,不免要自学了.然后就在各个昂站上收索并整理了一下.跟大家分享一下,也能加深我在这方面的理解. 什么是多线程下载? 多线程下载其实就是迅雷,BT一些下载原理,通过多个线程同时和服务器连接,那么你就可以榨取到较高的带宽了,大致做法是将文件切割成N块,每块交给单独一个线程去下载,各自下载完成后将文件块组合成一个文件,程序上要完成做切割和组装的小算法

android 多线程断点续传下载 四 - 仿下载助手

我们先一起简单回顾下它的基本原理. http://blog.csdn.net/shimiso/article/details/6763664  android 多线程断点续传下载 一 http://blog.csdn.net/shimiso/article/details/6763986  android 多线程断点续传下载 二 http://blog.csdn.net/shimiso/article/details/8448544  android 多线程断点续传下载 三 界面效果 线程池 T

Android/java http多线程断点下载(附源码)

先看下项目结构: http多线程断点下载涉及到 数据库,多线程和http请求等几个模块,东西不是很多,想弄清楚也不是很困难,接下来我和大家分享下我的做法. 一.先看MainActivity.java 成员变量,主要是一些下载过程的变量和handler private String path = "http://192.168.1.3:8080/wanmei/yama.apk"; private String sdcardPath; private int threadNum = 5;

基于ibcurl的跨平台多线程断点续传下载库

之前写过一个多线程断点续传的下载库,不过那个是基于一个linux的下载程序.windows下运行还好,android下就各种问题,调试起来还麻烦.后面开发游戏的时候,一方面对下载要求不高,另一方面也精力有限,所以就没有继续研究. 趁现在有时间,我希望实现一个自己满意的下载库,满足以下需求: 1.多线程下载,根据文件大小和下载的文件数目进行调度.一般情况下是一个文件一个文件按照顺序下载,如果文件比较多的情况下可以多个文件同时下载,这个是可以设置的. 2.断点续传.下载进度记录到一个配置文件中,要求

【Android开发经验】来,咱们自己写一个Android的IOC框架!

到眼下位置.afinal开发框架也是用了好几个月了,还记得第一次使用凝视完毕控件的初始化和事件绑定的时候,当时的心情是多么的兴奋- -代码居然能够这样写!然后随着不断的学习,也慢慢的对IOC框架和注解反射等东西有了一点简单的了解.之前的一篇文章简单的介绍了一下Java的反射机制.今天的文章.就完毕一个简单的,基于IOC的小Demo.让大家慢慢的对IOC有一点简单的了解. 首先.什么是IOC呢? 控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来

Android 多线程断点续传下载

通过HttpURLConnection的setRequestProperty和RandomAccessFile结合使用实现文件多线程下载和断点续传. xml布局: 1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools=&quo