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;
	ProgressDialog dialog;
	// 下载的进度
	private int process;
	// 下载完成的百分比
	private int done;
	private int filelength;
	// 本次下载开始之前,已经完成的下载量
	private int completed;
	// 用线程池是为了能够优雅的中断线程下载
	ExecutorService pool;
	@SuppressLint("HandlerLeak")
	private Handler handler = new Handler() {
		public void handleMessage(android.os.Message msg) {

			process += msg.arg1;
			done = (int) ((1.0 * process / filelength) * 100);
			Log.i("process", "process" + done);
			dialog.setProgress(done);
			// 第一次没有显示dialog的时候显示dialog
			if (done == 100) {// 提示用户下载完成
				// 线程下载完成以后就删除在数据库的缓存数据
				DBService.getInstance(getApplicationContext()).delete(path);
				// 做一个延时的效果,可以让用户多看一会100%
				Timer timer = new Timer();
				timer.schedule(new TimerTask() {
					@Override
					public void run() {
						dialog.dismiss();
					}
				}, 1000);
			}
		};
	};

download方法触发下载事件,先检查有没有sd卡,然后才开始开线程下载

public void download(View v) {
		completed = 0;
		process = 0;
		done = 0;
		pool = Executors.newFixedThreadPool(threadNum);
		initProgressDialog();
		new Thread() {
			public void run() {
				try {
					if (Environment.getExternalStorageState().equals(
							Environment.MEDIA_MOUNTED)) {
						sdcardPath = Environment.getExternalStorageDirectory()
								.getAbsolutePath();
					} else {
						toast("没有内存卡");
						return;
					}
					download(path, threadNum);
				} catch (Exception e) {
					e.printStackTrace();
				}
			};
		}.start();

	}

在真正开始下载之前,我们得先做一次http请求,为的是获取下载文件的大小和文件名,好预先准备好本地文件的大小以及各个线程应该下载的区域。这个时候我们请求的信息在响应头里面都有,只需要请求head就行了,既缩短了响应时间,也能节省流量

public void download(String path, int threadsize) throws Exception {
		long startTime = System.currentTimeMillis();
		URL url = new URL(path);
		// HttpHead head = new HttpHead(path);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		// 这里只需要获取httphead,至请求头文件,不需要body,
		// 不仅能缩短响应时间,也能节省流量
		// conn.setRequestMethod("GET");
		conn.setRequestMethod("HEAD");
		conn.setConnectTimeout(5 * 1000);

		Map<String, List<String>> headerMap = conn.getHeaderFields();
		Iterator<String> iterator = headerMap.keySet().iterator();
		while (iterator.hasNext()) {
			String key = iterator.next();
			List<String> values = headerMap.get(key);
			System.out.println(key + ":" + values.toString());
		}
		filelength = conn.getContentLength();// 获取要下载的文件的长度

		long endTime = System.currentTimeMillis();
		Log.i("spend", "spend time = " + (endTime - startTime));

		String filename = getFilename(path);// 从路径中获取文件名称
		File File = new File(sdcardPath + "/download/");
		if (!File.exists()) {
			File.mkdirs();
		}
		File saveFile = new File(sdcardPath + "/download/" + filename);
		RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");
		accessFile.setLength(filelength);// 设置本地文件的长度和下载文件相同
		accessFile.close();
		// 计算每条线程下载的数据长度
		<strong>int block = filelength % threadsize == 0 ? filelength / threadsize
				: filelength / threadsize + 1;</strong>
		// 判断是不是第一次下载,不是就计算已经下载了多少
		if (!DBService.getInstance(getApplicationContext()).isHasInfors(path)) {

			for (int threadid = 0; threadid < threadNum; threadid++) {
				completed += DBService.getInstance(getApplicationContext())
						.getInfoByIdAndUrl(threadid, path);
			}
		}
		 Message msg = handler.obtainMessage();
                 msg.arg1 = completed;
		handler.sendMessage(msg);
		for (int threadid = 0; threadid < threadsize; threadid++) {
			pool.execute(new DownloadThread(getApplicationContext(), path,
					saveFile, block, threadid, threadNum)
					.setOnDownloadListener(this));
		}
	}

DownloadThread.java

有两点:1、谷歌推荐httpurlconnection,我试了下下载速度确实比httpclient快

2、下载的时候用来缓存的byte数组,他的长度影响到下载速度的快慢

@Override
	public void run() {
		Log.i("download", "线程id:" + threadid + "开始下载");
		// 计算开始位置公式:线程id*每条线程下载的数据长度+已下载完成的(断点续传)= ?
		// 计算结束位置公式:(线程id +1)*每条线程下载的数据长度-1 =?
		completed = DBService.getInstance(context).getInfoByIdAndUrl(threadid, url);
		int startposition = threadid * block+completed;
		int endposition = (threadid + 1) * block - 1;
		try {
			RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");
			accessFile.seek(startposition);// 设置从什么位置开始写入数据
			// 我测试的时候,用httpurlconnection下载速度比httpclient快了10倍不止
			HttpURLConnection conn = (HttpURLConnection) new URL(url)
					.openConnection();
			conn.setRequestMethod("GET");
			conn.setConnectTimeout(5 * 1000);
			conn.setRequestProperty("Accept-Language", "zh-CN");
			conn.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, */*");
			conn.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)");
			conn.setRequestProperty("Referer", url);
			conn.setRequestProperty("Connection", "Keep-Alive");
			conn.setRequestProperty("RANGE", "bytes=" + startposition + "-"
					+ endposition);// 设置获取实体数据的范围
			// HttpClient httpClient = new DefaultHttpClient();
			// HttpGet httpGet = new HttpGet(url);
			// httpGet.addHeader("Range",
			// "bytes="+startposition+"-"+endposition);
			// HttpResponse response = httpClient.execute(httpGet);

			InputStream inStream = conn.getInputStream();
			// 这里需要注意,数组的长度其实代表了每次下载的流的大小
			// 如果太小的话,例如1024,每次就都只会下载1024byte的内容,速度太慢了,
			// 对于下载十几兆的文件来说太难熬了,太小了相当于限速了
			// 但也不能太大,如果太大了,那么缓冲区中的数据会过大,从而造成oom
			// 为了不oom又能开最大的速度,这里可以获取应用可用内容,动态分配
			int freeMemory = ((int) Runtime.getRuntime().freeMemory());// 获取应用剩余可用内存
			byte[] buffer = new byte[freeMemory / threadNum];// 可用内存得平分给几个线程
			// byte[] buffer = new byte[1024];
			int len = 0;
			int total = 0;
			boolean isInterrupted=false;
			while ((len = inStream.read(buffer)) != -1) {
				accessFile.write(buffer, 0, len);
				total += len;
				Log.i("download", "线程id:" + threadid + "已下载" + total  + "总共有" + block);
				// 实时更新进度
				listener.onDownload(threadid,len,total,url);
				//当线程被暗示需要中断以后,退出循环,终止下载操作
				<strong>if(Thread.interrupted()){
					isInterrupted=true;
					break;
				}</strong>
			}
			inStream.close();
			accessFile.close();
			if(isInterrupted){
				Log.i("download", "线程id:" + threadid + "下载停止");
			}else{
				Log.i("download", "线程id:" + threadid + "下载完成");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

我是在应用退到后台,就让停止下载的,不为什么,就是不想多写那个button,需要的可以自己写。

这里,我通过线程池的shutdownNow()来尝试中断所有线程的,其实也不是中断,只是在调用了这个方法之后,线程里的Thread.interrupted()方法就返回true了,然后我就通过break;来退出循环,从而达到中断下载的目的。

@Override
	protected void onStop() {
		super.onStop();
		// 应用退到后台的时候就暂停下载
		pool.shutdownNow();
		dialog.dismiss();
	}

接口回调

更新进度到数据库,理论上来说进度不应该实时更新的,sqlite本质上也是文件,频繁的打开关闭文件太耗资源了,所以在实际项目中应该在用户暂停或者断网等特殊情况才更新进度

@Override
	public void onDownload(int threadId, int process, int completed, String url) {
		// 更新进度到数据库,理论上来说进度不应该实时更新的,
		//sqlite本质上也是文件,频繁的打开关闭文件太耗资源了,
		//所以在实际项目中应该在用户暂停或者断网等特殊情况才更新进度
		DBService.getInstance(getApplicationContext()).updataInfos(threadId,
				completed, url);
		Message msg = handler.obtainMessage();
		msg.arg1 = process;
		handler.sendMessage(msg);
	}

DBService.java

package com.huxq.multhreaddownload;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

public class DBService {
	private DBHelper dbHelper;
	private static DBService instance;

	private DBService(Context context) {
		dbHelper = new DBHelper(context);
	}

	/**
	 * 单例模式,不必每次使用都重新new
	 *
	 * @param context
	 * @return
	 */
	public static DBService getInstance(Context context) {
		if (instance == null) {
			synchronized (DBService.class) {
				if (instance == null) {
					instance = new DBService(context);
					return instance;
				}
			}
		}
		return instance;
	}

	/**
	 * 查看数据库中是否有数据
	 */
	public boolean isHasInfors(String urlstr) {
		SQLiteDatabase database = dbHelper.getReadableDatabase();
		String sql = "select count(*)  from download_info where url=?";
		Cursor cursor = database.rawQuery(sql, new String[] { urlstr });
		cursor.moveToFirst();
		int count = cursor.getInt(0);
		Log.i("count", "count=" + count);
		cursor.close();
		return count == 0;
	}

	/**
	 * 保存下载的具体信息
	 */
	public void saveInfos(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.getReadableDatabase();
		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);
		}
		cursor.close();
		return list;
	}

	/**
	 * 获取特定ID的线程已下载的进度
	 *
	 * @param id
	 * @param url
	 * @return
	 */
	public synchronized int getInfoByIdAndUrl(int id, String url) {
		SQLiteDatabase database = dbHelper.getReadableDatabase();
		String sql = "select compelete_size"
				+ " from download_info where thread_id=? and url=?";
		Cursor cursor = database.rawQuery(sql, new String[] { id + "", url });
		if (cursor!=null&&cursor.moveToFirst()) {
			Log.i("count",
					"thread id="
							+ id
							+ "completed="
							+ cursor.getInt(0));
			return cursor.getInt(0);
		}
		return 0;
	}

	/**
	 * 更新数据库中的下载信息
	 */
	public synchronized void updataInfos(int threadId, int compeleteSize, String urlstr) {
		SQLiteDatabase database = dbHelper.getReadableDatabase();
		// 如果存在就更新,不存在就插入
		String sql = "replace into download_info"
				+ "(compelete_size,thread_id,url) values(?,?,?)";
		Object[] bindArgs = { compeleteSize, threadId, urlstr };
		database.execSQL(sql, bindArgs);
	}

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

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

	public void saveOrUpdateInfos() {

	}

	public synchronized void deleteByIdAndUrl(int id, String url) {
		SQLiteDatabase database = dbHelper.getReadableDatabase();
		int count = database.delete("download_info", "thread_id=? and url=?", new String[] {
				id + "", url });
		Log.i("delete", "delete id="+id+","+"count="+count);
		database.close();
	}
}

写这些东西也花了我点时间,因为牵扯到的东西也不少,最后我会贴出DEMO,有兴趣的可以看看,如有疑问,欢迎留言或者联系我,一起探讨。

时间: 2024-10-12 20:57:27

Android/java http多线程断点下载(附源码)的相关文章

java多线程核心技术梳理(附源码)

java多线程核心技术梳理(附源码) java多线程核心技术梳理附源码 写在前面 java多线程 对象及变量的并发访问 线程间通信 Lock的使用 定时器 单例模式与多线程 拾遗补增 参考资料 本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,lock的使用,定时器,单例模式,以及线程状态与线程组. 写在前面 花了一周时间阅读<java多线程编程核心技术>(高洪岩 著),本文算是此书的整理归纳,书中几乎所有示例,我都亲手敲了一遍,并上传到了我的githu

Android跟踪球-手势移动图片-自定义控件(附源码)

由于我不会制作动画图片,所以先放几及其不具备代表性的展示图片. 我以前的思路是通过动态的设置xy坐标通过手势移动来识别,但是我后来试了一下,发现运行效果极差.所以偷闲做了下这个跟踪球控件,其实实现十分简单.只要大家熟悉自定义控件的使用以及手势识别.基本上就ok了. 现在我们看下这个控件的源码TouchMoveView.java package com.fay.touchmove; import android.annotation.SuppressLint; import android.con

手把手搭建Java学生信息管理系统【附源码】

基于JavaWeb的学生信息管理系统的设计与实现 一.系统简介 本课程演示的是一套基于JavaWeb实现的学生信息管理系统,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的java人群. 详细介绍了学生信息管理系统的实现,包括:1.项目介绍2.环境搭建3.系统功能4.技术实现5.项目运行6.功能演示 以通俗易懂的方式,手把手的带你从零开始运行本套学生信息管理系统,该项目附带全部源码可作为毕设使用. 二.技术实现 ?1. 后台框架:Servlet.JSP.JDBC.DbUtils ?2

手把手搭建Java金融借贷系统【附源码】(毕设)

一.项目简介 本课程演示的是一套基于基于JavaWeb实现的金融借贷系统 或 P2P金融管理系统 或 小额贷款系统,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的java人群. 详细介绍了金融借贷系统的实现,包括:1.项目介绍2.环境搭建3.系统功能4.技术实现5.项目运行6.功能演示 以通俗易懂的方式,手把手的带你从零开始运行本套金融借贷系统,该项目附带全部源码可作为毕设使用. 二.技术实现 1.后台框架:Servlet.JDBC.FileUpload 2.UI界面:BootSt

Java之多线程断点下载的实现

RandomAccessFile类: 此类的实例支持对随机訪问文件的读取和写入.随机訪问文件的行为相似存储在文件系统中的一个大型 byte 数组. 存在指向该隐含数组.光标或索引,称为文件指针.输入操作从文件指针開始读取字节.并随着对字节的读取而前移此文件指针. 假设随机訪问文件以读取/写入模式创建,则输出操作也可用.输出操作从文件指针開始写入字节.并随着对字节的写入而前移此文件指针.写入隐含数组的当前末尾之后的输出操作导致该数组扩展.该文件指针能够通过 getFilePointer 方法读取.

Java实现多线程断点下载(下载过程中可以暂停)

线程可以理解为下载的通道,一个线程就是一个文件的下载通道,多线程也就是同时开启好几个下载通道.当服务器提供下载服务时,使用下载者是共享带宽的,在优先级相同的情况下,总服务器会对总下载线程进行平均分配.不难理解,如果你线程多的话,那下载的越快. 现流行的下载软件都支持多线程,且支持中途暂停下载,再次开始时不会从头开始下载. 两种功能的实现步骤如下: (1)连接到下载资源文件时,首先判断资源文件大小,同步的在本地创建一个大小相同的临时文件用于存储下载数据. (2)根据线程数量确定每个线程所需下载的文

(转)Android与js交互实例(附源码)

本文转载于:http://blog.csdn.net/ithomer/article/details/8737999# Android 中可以通过webview来实现和js的交互,在程序中调用js代码,只需要将webview控件的支持js的属性设置为true Android(Java)与JavaScript(HTML)交互有四种情况: 1) Android(Java)调用HTML中js代码 2) Android(Java)调用HTML中js代码(带参数) 3) HTML中js调用Android(

详解CentOS7.4搭建Tomcat构建Java Web站点(内附源码包)

Java简介 Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征.Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论.Java具有简单性.面向对象.分布式.健壮性.安全性.平台独立与可移植性.多线程.动态性等特点.Java可以编写桌面应用程序.Web应用程序.分布式系统和嵌入式系统应用程序等. Tomcat简介 Tomcat是Apache软件基金会的Jakarta项目中的一

Android侧边栏的自定义实现(附源码)

本文要实现手指在手机上向左或向右移动时,能相应的移动左右两个视图.通过自定义来实现,不借助第三方插件. 先来看看效果:(源码免费下载) 目录: 一.实现思路 二.代码清单 三.效果与说明 下面,让我们开始吧: 一.实现思路 1.思路 菜单在左,内容在右,然后菜单显示时和手机右边框有一定的间隔,内容显示一小部分.内容全部显示时,菜单全部不可见.如下面两个图 显示内容 显示菜单 2.判断逻辑 这是判断手指按着屏幕和手指抬起时要不要显示还是隐藏菜单 二.代码清单 首先来看下布局: <LinearLay