多线程下载器(不含数据库部分)

多线程下载器(不含数据库部分)

1、写在前面:

虽然demo中程序框架已搭建完成,但是由于笔者时间原因,暂时只完成了核心部分:多线程下载的部分,其他数据库、服务通知、暂停部分还未添加到项目中。

2、相关知识点:

(1)Java线程及停止线程的方式

(2)Java RandomAccessFile文件操作

(3)HttpURLConnection相关range字段的配置

(4)Sqlite同步操作

2、核心思想:

(1)通过HttpURLConnection判断服务器是否支持断电续传:

<1>否->直接开启普通的多线程下载(遇到断网等情况便会重新下载)

<2>是->开启普通的多线程下载,但是每个线程都含有自己的下载进度信息,以便断网或用户暂停开始重新下载重新开启下载。笔者在针对不同的下载尺寸智能的分配不同的线程数量去下载资源,通过设置缓冲区大小来提高下载速度。

3、核心技术:

(1)HttpURLConnection的配置

(2)RandomAccessFile随机文件的读取以及缓冲区的设置

(3)线程的暂停与启动

4、分析结果:

(1)将功能划分为三大部分:下载器(统一的外部接口)、存储器(内部的存储实现)、通知服务(用户交互部分)。

(2)项目结构:

5、核心代码:

package com.jx.downloader;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;

import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;

import com.jx.dbhelper.DownloadRecordDB;
import com.jx.model.DownloadModel;

/**
 * 自定义下载器: 1、根据即将下载的内容大小智能的分配下载线程数量,每个线程通过Downloader携带自身线程的下载信息(下载起点、终点、线程名称)
 * 2、如果服务器支持断电续传则开启数据库
 * ,在下载处于暂停的状态时(导致下载暂停的原因可能是手动暂停或者网络不佳),自动保存下载信息到数据库,在取消下载的时候自动清空数据库信息,
 * 重新开始下载的时候,读取内容重新下载(一般情况本地存储变量还未被回收,不必从数据库重新读取) 2、使用RandomAccessFile存储下载的内容
 *
 * @author J_X 2016年3月19日09:58:00
 */
public class JX_Downloader {

	/**
	 * 0~3M的下载范围默认开启1个线程
	 */
	private final static long BELOW_Three_M_SIZE = 5 * 1024 * 1024;
	/**
	 * 3~6M的下载范围默认开启2个线程
	 */
	private final static long BELOW_SIX_M_SIZE = 6 * 1024 * 1024;
	/**
	 * 10~18M的下载范围默认开启4个线程
	 */
	private final static long BELOW_EIGHTEEN_M_SIZE = 18 * 1024 * 1024;
	/**
	 * 线程是否暂停的标示
	 */
	private static volatile boolean isStopDownloading = false;
	/**
	 * 下载器的标示id,以为可以创建多个下载器
	 */
	private long downloaderID;
	/**
	 * 需要下载的资源链接
	 */
	private String resourceUrl;
	/**
	 * 下载链接的遵守URL协议的对象
	 */
	private URL resourceURL;
	/**
	 * 需要下载的资源的总字节数
	 */
	private long resourceSize;
	/**
	 * 需要的下载线程的总数量
	 */
	private int totalTreadNum;
	/**
	 * 资源所在服务器是否支持断点续传功能
	 */
	private boolean isSupportLoadingAndSaving;

	/**
	 * 下载资源存储的数据库
	 */
	private DownloadRecordDB downlaodDB;
	/**
	 * 文件存储目录
	 */
	private String savaFileName;

	/**
	 * 构造器暂无子类所以没有提供默认构造函数
	 *
	 * @param context
	 *            上下文
	 * @param downloadUrl
	 *            将要被下载的链接
	 * @param saveFileName
	 *            带后缀的下载的文件存储名称
	 */
	public JX_Downloader(Context context, String downloadUrl,
			String saveFileName) throws MalformedURLException {
		// TODO Auto-generated constructor stub
		resourceUrl = downloadUrl;
		this.resourceURL = new URL(resourceUrl);
		this.downlaodDB = new DownloadRecordDB(context);
		// 如果用户没有设置下载后缀,提供默认存储文件夹
		if (TextUtils.isEmpty(saveFileName)) {
			this.savaFileName = "JX_DownLoader.txt";
		} else {
			this.savaFileName = saveFileName;
		}
	}

	/**
	 * 开启下载任务,对外提供方便下载方法
	 */
	public void startDownload() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
					try {
						readyDownload();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						Log.e("Debug", "downloading fail!!!");
					}
			}
		}).start();
	}

	/**
	 * 核心下载程序(采用类似门面模式方法处理,方法有严格的执行顺序要求)
	 *
	 * @throws IOException
	 *
	 */
	private void readyDownload() throws IOException {
		// 判断当前连接所在服务器是否支持断点续传下载
		// this.isSupportLoadingAndSaving = false;
		// /** 不支持的给出友好提示,只进行多线程下载的任务 */
		// if (!isSupportLoadingAndSaving) {
		// Log.e("Debug", "Server don't support pause_save download!");
		// } else {
		// /** 支持的情况:1、开启数据库存储各个线程进度 */
		// }

		/* 多线程下载流程 */
		// 1、计算需要下载的资源大小
		HttpURLConnection httpURLConnection = settingRequestHttp(null);
		if (httpURLConnection.getResponseCode() == 200) {
			// TODO: 需要进一步优化网络
			Log.e("Debug", "Coonected sucessfully");
		}
		resourceSize = httpURLConnection.getContentLength();
		if (resourceSize <= 0) {
			Log.e("Debug", "unkown file Length and return");
			return;
		} else {
			Log.e("Debug", "file length: " + resourceSize + "bytes");
		}
		// 2、按照用户设置或者资源大小智能的设置下载线程总数量
		int tempThreadNum = setTotalTreadNum(resourceSize);
		// 3、根据分配的线程数量,来将资源“等分”,并设置header
		ArrayList<DownloadModel> downloadArray = new ArrayList<DownloadModel>();
		if (isSupportLoadingAndSaving) {
			// 从数据库读取
			downloadArray = downlaodDB.getAllInfo();
		} else {
			// 从本地方法读取
			downloadArray = initResoureSize(tempThreadNum, resourceSize);
		}
		// 4、划分线程进行下载
		for (int i = 0; i < downloadArray.size(); ++i) {
			DownloadModel tempModel = downloadArray.get(i);
			MyRunnable runnable = new MyRunnable(tempModel);
			Thread thread = new Thread(runnable);
			thread.start();
		}
	}

	/**
	 * 配置Http信息,准备开始下载数据
	 *
	 * @throws IOException
	 */
	private HttpURLConnection settingRequestHttp(DownloadModel model)
			throws IOException {
		HttpURLConnection coon = (HttpURLConnection) this.resourceURL
				.openConnection();
		coon.setConnectTimeout(3 * 1000);
		coon.setRequestMethod("GET");
		coon.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, */*");
		coon.setRequestProperty("Accept-Language", "zh-CN");
		coon.setRequestProperty("Referer", resourceUrl);
		coon.setRequestProperty("Charset", "UTF-8");
		coon.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)");
		coon.setRequestProperty("Connection", "Keep-Alive");
		// 断点续传的核心代码,设置下载区间,注意是bytes=在这吃过亏
		if (model != null) {
			coon.setRequestProperty(
					"Range",
					"bytes=" + model.getDownloadedLength() + "-"
							+ model.getDownloadLengthSum());
		}
		return coon;
	}

	/**
	 * 多线程下载共享的run方法
	 *
	 * @author Administrator
	 *
	 */
	private class MyRunnable implements Runnable {

		private DownloadModel rModel;

		public MyRunnable(DownloadModel model) {
			this.rModel = model;
		}

		@Override
		public void run() {
			try {
				Log.i("Debug", rModel.getThreadName());
				HttpURLConnection coon = settingRequestHttp(rModel);
				if (coon.getResponseCode() == 200) {
					Log.e("Debug", "Coonected sucessfully");
				}
				resourceSize = coon.getContentLength();
				if (resourceSize <= 0) {
					Log.e("Debug", "unkown file Length and return");
					return;
				} else {
					readResourceAndSave(coon, rModel);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	/**
	 * 将网络数据从网上读取下来
	 *
	 * @throws IOException
	 */
	private void readResourceAndSave(HttpURLConnection coon, DownloadModel model)
			throws IOException {
		//创建文件夹
		File savDir = Environment.getExternalStorageDirectory();
		File file = null;
		if (Environment.getExternalStorageState().equals(
				Environment.MEDIA_MOUNTED)) {
			if (!savDir.exists()) {
				savDir.mkdirs();
			}
			file = new File(savDir, this.savaFileName);
		}
		RandomAccessFile rFile = new RandomAccessFile(file, "rwd");
		// 跳转到当前线程对文件的读写起始位置
		rFile.seek(model.getDownloadedLength());
		InputStream finput = coon.getInputStream();
		if (finput != null) {
			Log.e("Debug", "finput=>" + finput.toString());
		} else {
			Log.e("Debug", "finput is null and return");
		}
		Log.e("Debug", "rFile.getFilePointer=>" + rFile.getFilePointer());
		BufferedInputStream bInput = new BufferedInputStream(finput);
		// 设置3KB的缓冲区,加快下载速度,这里后期改成按字符或者整行读取方式优化
		int size = 3 * 1024;
		byte[] readBytes = new byte[size];
		int readNum = 0;
		long sum = 0;
		// 当还有数据可读且线程没有暂停
		long needReadNum = model.getDownloadLengthSum()
				- model.getDownloadedLength();

		while ((readNum = bInput.read(readBytes, 0, size)) > 0
				&& sum < needReadNum && !isStopDownloading) {
			sum += readNum;
			// 如果请求的尺寸下一次读取即将超过总需求数量时,修正读取内容的大小保证不多读取
			if ((sum + size) > needReadNum) {
				size = (int) (needReadNum - sum);
			}
			// 写入到文件中
			rFile.write(readBytes, 0, readNum);
		}
		Log.e("Debug",
				model.getThreadName() + "finish downloading:" + rFile.length()
						+ "-bytes");
		rFile.close();
		finput.close();
	}

	/**
	 * 计算需要下载的资源大小
	 *
	 * @param url
	 *            待下载的资源内容链接
	 * @return 返回资源所占的字节数
	 */
	private ArrayList<DownloadModel> initResoureSize(int tNum,
			long contentLength) {
		long tempLastSize = contentLength;// 剩余的下载内容大小
		ArrayList<DownloadModel> downloadArray = new ArrayList<DownloadModel>();
		long commonSize = contentLength / tNum;
		for (int i = 0; i < tNum; ++i) {
			DownloadModel downloadModel = new DownloadModel();
			// 还未开始下载,所以已下载的大小为0byte,他的长度代表下一个线程的下载的起点
			downloadModel.setDownloadedLength(i * commonSize);
			long loadEnding = tempLastSize;
			// 剩下的最后一个下载线程的下载大小=总线程-其他线程下载的大小和
			if (i == tNum - 1) {
				loadEnding = contentLength;
			} else {
				loadEnding = commonSize * i + commonSize;
			}
			tempLastSize = contentLength - commonSize;

			downloadModel.setDownloadLengthSum(loadEnding);
			downloadModel.setThreadName("JX_Download_Thread" + i);
			// 如果支持断点续传,就存储到数据库中,否则暂时存储到ArrayList中
			if (isSupportLoadingAndSaving) {
				downlaodDB.insert(downloadModel);
			} else {
				downloadArray.add(downloadModel);
			}
		}
		return downloadArray;
	}

	/**
	 * 根据资源大小智能的设置下载线程总数量
	 *
	 * @param totalTreadNum
	 *            设置的下载数据量
	 */
	private int setTotalTreadNum(long contentLength) {
		// 如果用户没有设置下载的线程数量则根据大小智能设置
		if (0 == this.totalTreadNum) {
			if (contentLength <= 0) {
				Log.i("Debug", "下载的内容太小!");
				return 0;
			} else if (contentLength < BELOW_Three_M_SIZE) {
				this.totalTreadNum = 1;
			} else if (contentLength < BELOW_SIX_M_SIZE) {
				this.totalTreadNum = 2;
			} else if (contentLength < BELOW_EIGHTEEN_M_SIZE) {
				this.totalTreadNum = 4;
			} else {
				this.totalTreadNum = 5;
			}
		}
		return this.totalTreadNum;
	}

	/**
	 * 判断当前连接所在服务器是否支持断点续传下载
	 *
	 * @return 默认不支持断电续传
	 */
	public boolean isSupportLoadingAndSaving() {
		return isSupportLoadingAndSaving;
	}

	public long getDownloaderID() {
		return downloaderID;
	}

	public void setDownloaderID(long downloaderID) {
		this.downloaderID = downloaderID;
	}

	public int getTotalTreadNum() {
		return totalTreadNum;
	}

	public String getResourceUrl() {
		return resourceUrl;
	}

	public long getResourceSize() {
		return resourceSize;
	}
}

下载器使用:

package com.jx.main;

import java.net.MalformedURLException;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

import com.example.mutilthreaddownloader.R;
import com.jx.downloader.JX_Downloader;

/**
 * 2016年3月19日09:41:00
 *
 * @author J_X 多线程测试类
 */
public class MainActivity extends Activity {
	JX_Downloader downloader;
    Button btnDownlaod;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		btnDownlaod = (Button)findViewById(R.id.btn_start_downlaoding);
		btnDownlaod.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				try {
					downloader = new JX_Downloader(MainActivity.this,
							"****.apk""****.apk); downloader.startDownload();
				} catch (MalformedURLException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
		});

	}
}

小结:

下载器的速度,出来外部因素网速,服务器传输速度,内部因素主要是线程的数量和缓冲区能够影响下载速度,但是笔者在魅族酷派小米虚拟机上同一wifi相同线程数量以及同样大小缓冲区下,下载速度小米的下载速度奇慢,还有待考证具体原因。

时间: 2024-11-08 22:06:22

多线程下载器(不含数据库部分)的相关文章

Java多线程下载器FileDownloader(支持断点续传、代理等功能)

前言 在我的任务清单中,很早就有了一个文件下载器,但一直忙着没空去写.最近刚好放假,便抽了些时间完成了下文中的这个下载器. 介绍 同样的,还是先上效果图吧. Jar包地址位于 FileDownloader 目前实现的主要功能有: 多线程下载 断点续传 自定义头部等 即将完成的包括: 添加代理功能 ... 感觉做了回标题党,代理功能由于时间关系,将在下次更新加入. 关于设置代理,我这篇文章 Java实现Ip代理池 中有具体的设置方法. 另外除了这个代理功能,我也实在不知道下载器能加些啥功能了..

Android版多线程下载器核心代码分享

首先给大家分享多线程下载核心类: 1 package com.example.urltest; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.RandomAccessFile; 6 import java.net.HttpURLConnection; 7 import java.net.MalformedURLException; 8 import java.net.URL; 9 im

bigemap地图下载器安装

软件下载 本产品支持主流winodws操作系统(xp sp3,vista,windows 7,windows 8及windows 10), 可通过访问大地图官网(http://www.bigemap.com/)获取本产品,用户可根据需求下载相应的版本. 如下图所示为软件下载. 本产品所有版本都具备在线自动升级,以保证用户实时获取到最新的地图数据和增值软件服务. 软件下载 下载完成后,双击安装包按照安装向导即可完成安装.温馨提示:请尽量不要把软件装在系统盘,如:C盘. 安装过程如下所示:     

【Android-011】【多线程下载】

Android学习目录 项目源码下载 多线程下载 原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源 确定每条线程下载多少数据 发送http请求至下载地址 String path = "http://192.168.1.102:8080/editplus.exe"; URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) ur

Java之多线程下载

多线程下载的原理在于,每个线程下载文件的一部分,每个线程将自己下载的一部分写入文件中它应该的位置,所有线程下载完成时,文件下载完成.其关键点在于:RandomAccessFile.seek(beginIndex)和URLConnection.setRequestProperty("Range", "bytes=" + beginIndex + "-" + endIndex). 转载请注明原创地址,请尊重原创,谢谢. 代码如下,以下代码copy后可

JavaSE多线程下载的实现

本文中主要提供的是java多线程下载文件方案,以及java多线程将临时进度保存到文件,多线程断点续传的实现: 1.多线程下载 2.将下载进度保存到临时文件,多线程的断定续传 1.多线程下载 本例中首先在Tomcat服务器中的WEBAPP/ROOT/文件夹下面放置了SoftwareOffer.exe的二进制可执行文件,如果放置图片的话,中间数据如果出错,不容易用肉眼识别,但是如果是二进制文件的话,如果中间任何二进制的一位数据出错,必然造成二进制可执行文件无法运行!所以测试选择二进制可执行文件比较妥

LINUX MAC Axel —— 一款比 wget 更强大的多线程下载工具

前言 最近使用 wget 下载百度云资源,速度比较缓慢,在朋友推荐多线程下载后发现 wget 其实仅仅是一个单线程下载工具,在面对文件时会显得十分鸡肋,并且有许多诟病,比如无断点重连等等功能. Axel 介绍 经过一些搜索后发现,有一个非常好用的下载工具名为 Axel ,和 wget 一样是命令行下的下载工具,但是支持多线程下载,断点重连等等强大的功能. 以下是 man 中它的英文介绍以及翻译: axel - light command line download accelerator. Ax

Android中多线程下载列表的封装实现(含进度反馈)

来源:http://blog.csdn.net/u011638883/article/details/17347015 实现了一下Android中的文件多线程下载模块,支持自定义线程数.断点续传.下载任务的删除,添加等功能,这里封装了一下,功能已全部实现.不过由于使用的是最简单的手动线程数组及消息通知实现,可能还存在某些小问题.笔者会在后面的使用过程中再进行优化完善.先看一下程序测试效果,这里指定了5个下载任务,以及2个下载线程,具体如下: 要运行以上Demo需要自己搭建服务器,和简单,只需要把

Python实现多线程HTTP下载器

本文将介绍使用Python编写多线程HTTP下载器,并生成.exe可执行文件. 环境:windows/Linux + Python2.7.x 单线程 在介绍多线程之前首先介绍单线程.编写单线程的思路为: 解析url: 连接web服务器: 构造http请求包: 下载文件. 接下来通过代码进行说明. 解析url 通过用户输入url进行解析.如果解析的路径为空,则赋值为'/':如果端口号为空,则赋值为"80":下载文件的文件名可根据用户的意愿进行更改(输入'y'表示更改,输入其它表示不需要更