`
haierboos
  • 浏览: 436860 次
文章分类
社区版块
存档分类
最新评论

android 多线程断点续传

 
阅读更多

前端时间公司发新的版本,可是版本升级时候下载apk的时候速度特别的慢,不知道是不是服务器的原因

领导问有没有什么解决办法,于是我就写了个多线程断点续传的功能。(其实多线程对于提速帮助不大)

第一次没有下载完,用户没有耐心了,退出应用,甚至把整个应用都干掉了,在下次打开应用的时候可以继续上一次的下载

注释都在代码中,拿下去可以直接当作工具类使用,关于异常的处理都有注释

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

import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;

import com.easipass.tool.weather.NetUtils;

/***
 * 实现多线程断点下载
 * @author 徐初龙
 */
public class BreakpointDownloader {
	private static final String DIR_PATH = Environment.getExternalStorageDirectory() + "/";	// 下载目录
	private static final int THREAD_AMOUNT = 3;				// 总线程数
	
	private URL url;			// 目标下载地址
	private File dataFile;		// 本地文件
	private File tempFile;		// 用来存储每个线程下载的进度的临时文件
	private long threadLen;		// 每个线程要下载的长度
	private long totalFinish;	// 总共完成了多少
	private long totalLen;		// 服务端文件总长度
	public static long backtotalLen;
	private long begin;			// 用来记录开始下载时的时间
	private Handler handler;
	private Context context;
	private boolean allthreadisrun = true;

	public BreakpointDownloader(int newCode, String address, Handler handler,Context context) throws IOException {	
		url = new URL(address);															// 记住下载地址
		dataFile = new File(DIR_PATH, Config.UPDATE_SAVENAME.replace(".apk", newCode + ".apk"));	// 截取地址中的文件名, 创建本地文件
		tempFile = new File(dataFile.getAbsolutePath() + ".temp");						// 在本地文件所在文件夹中创建临时文件
		this.context = context;

		File oldDataFile = new File(DIR_PATH, Config.UPDATE_SAVENAME.replace(".apk", newCode-1 + ".apk"));
		File oldTempFile = new File(oldDataFile.getAbsolutePath() + ".temp");    
		if(oldDataFile.exists())
		    oldDataFile.delete();
		if(oldTempFile.exists())
		    oldTempFile.delete();
		this.handler = handler;
	}

	public void download() throws IOException {		
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setConnectTimeout(10000);	
		totalLen = conn.getContentLength();									// 获取服务端发送过来的文件长度
		threadLen = (totalLen + THREAD_AMOUNT - 1) / THREAD_AMOUNT;			// 计算每个线程要下载的长度

		Message msg = new Message();	
		if(totalLen<0){
			msg.getData().putLong("totalLen", backtotalLen);	
		}else{
			backtotalLen = totalLen;
			msg.getData().putLong("totalLen", totalLen);		
		}


		msg.what = 1;
		handler.sendMessage(msg);											// 发送文件总长度
		
		if (!dataFile.exists()) {											// 如果本地文件不存在
		try{
			RandomAccessFile raf = new RandomAccessFile(dataFile, "rws");	// 在本地创建文件
			raf.setLength(totalLen);										// 设置文件的大小和服务端相同
			raf.close();
			}catch(Exception e){
				e.printStackTrace();
			}
        }		
		
		if (!tempFile.exists()) {											// 如果临时文件不存在
			RandomAccessFile raf = new RandomAccessFile(tempFile, "rws");	// 创建临时文件, 用来记录每个线程已下载多少
			for (int i = 0; i < THREAD_AMOUNT; i++)							// 按照线程数循环
				raf.writeLong(0);											// 写入每个线程的开始位置(都是从0开始)
			raf.close();
		}
		
		for (int i = 0; i < THREAD_AMOUNT; i++)	// 按照线程数循环
			new DownloadThread(i).start();		// 开启线程, 每个线程将会下载一部分数据到本地文件中
		
		begin = System.currentTimeMillis();		// 记录开始时间
	}
	
	private class DownloadThread extends Thread {
		private int id; 	// 用来标记当前线程是下载任务中的第几个线程
		
		public DownloadThread(int id) {
			this.id = id;
		}
	    boolean flag = true;
		
		public void run() {
		    boolean isrun = false;	//每个线程都有自己的标识位,用来控制线程的重启
			while(allthreadisrun && !isrun){
				//判断网络连接是否可用,不可用直接退出
				if (!NetUtils.getNetworkIsAvailable(context)) {
					handler.sendEmptyMessage(3);
					return;
				}
				try {
					isrun = true;
					if(tempFile.exists()&&dataFile.exists()){
						RandomAccessFile tempRaf = new RandomAccessFile(tempFile, "rws");		// 用来记录下载进度的临时文件
						tempRaf.seek(id * 8);						// 将指针移动到当前线程的位置(每个线程写1个long值, 占8字节)
						long threadFinish = tempRaf.readLong();		// 读取当前线程已完成了多少
						//判断 如果当前线程已经完成了下载,则退出该线程
						if(threadFinish == threadLen){
//							System.out.println("线程"+id+"已经退出");
							tempRaf.close();
							totalFinish +=threadFinish; 
							if (totalFinish >= totalLen && totalFinish == (totalLen + THREAD_AMOUNT - 1)) {					// 如果已完成长度等于服务端文件长度(代表下载完成)
								System.out.println("下载完成, 耗时: " + (System.currentTimeMillis() - begin));
								tempFile.delete();							// 删除临时文件
							} 
							return;
						}
						synchronized(BreakpointDownloader.this) {	// 多个下载线程之间同步
							totalFinish += threadFinish;			// 统计所有线程总共完成了多少
						}
					
//						System.out.println("totalFinish="+totalFinish);
						Message msg = new Message();
						msg.getData().putLong("totalFinish", totalFinish);
						msg.what = 2;
						handler.sendMessage(msg);			
						
						
						long start = id * threadLen + threadFinish;		// 计算当前线程的起始位置
						long end = id * threadLen + threadLen - 1;		// 计算当前线程的结束位置
						System.out.println("线程" + id + ": " + start + "-" + end);
					
//						System.out.println("------->url"+url);
						HttpURLConnection conn = (HttpURLConnection) url.openConnection();
						conn.setConnectTimeout(10000);
						conn.setRequestProperty("Range", "bytes=" + start + "-" + end);		// 设置当前线程下载的范围
						
						InputStream in = conn.getInputStream();								// 获取连接的输入流
						RandomAccessFile dataRaf = new RandomAccessFile(dataFile, "rws");	// 装载数据的本地文件(可以理解为输出流)
						dataRaf.seek(start);												// 设置当前线程保存数据的位置
						
						byte[] buffer = new byte[1024 * 100];			// 每次拷贝100KB
						int len;
						while (totalFinish<totalLen && (len = in.read(buffer)) != -1) {
							dataRaf.write(buffer, 0, len);				// 从服务端读取数据, 写到本地文件
							threadFinish += len;						// 每次写入数据之后, 统计当前线程完成了多少
							tempRaf.seek(id * 8);						// 将临时文件的指针指向当前线程的位置
							tempRaf.writeLong(threadFinish);			// 将当前线程完成了多少写入到临时文件
							synchronized(BreakpointDownloader.this) {	// 多个下载线程之间同步
								totalFinish += len;						// 统计所有线程总共完成了多少
								Message msg3 = new Message();
								msg3.getData().putLong("totalFinish", totalFinish);
								msg3.what = 2;
								handler.sendMessage(msg3);				// 发送当前进度
//								System.out.println("下载进度-----》"+totalFinish+"/"+totalLen);
							}
						}
						dataRaf.close();
						tempRaf.close();
					}
				} catch (IOException e) {					
					e.printStackTrace();
//					System.out.println("1 ---》这里异常了");
					
					if (!NetUtils.getNetworkIsAvailable(context)) {
//						System.out.println("已经发送消息");
						handler.sendEmptyMessage(3);
						return;
					}
					try {
						isrun = true;
						if(tempFile.exists()&&dataFile.exists()){
							RandomAccessFile tempRaf = new RandomAccessFile(tempFile, "rws");		// 用来记录下载进度的临时文件
							tempRaf.seek(id * 8);						// 将指针移动到当前线程的位置(每个线程写1个long值, 占8字节)
							long threadFinish = tempRaf.readLong();		// 读取当前线程已完成了多少
							//判断 如果当前线程已经完成了下载,则退出该线程
							if(threadFinish == threadLen){
								System.out.println("线程"+id+"已经退出");
								tempRaf.close();
								totalFinish +=threadFinish; 
								if (totalFinish >= totalLen && totalFinish == (totalLen + THREAD_AMOUNT - 1)) {					// 如果已完成长度等于服务端文件长度(代表下载完成)
									System.out.println("下载完成, 耗时: " + (System.currentTimeMillis() - begin));
									tempFile.delete();							// 删除临时文件
								} 
								return;
							}
							synchronized(BreakpointDownloader.this) {	// 多个下载线程之间同步
								totalFinish += threadFinish;			// 统计所有线程总共完成了多少
							}
							
							long start = id * threadLen + threadFinish;		// 计算当前线程的起始位置
							long end = id * threadLen + threadLen - 1;		// 计算当前线程的结束位置
							System.out.println("线程" + id + ": " + start + "-" + end);
						
							HttpURLConnection conn = (HttpURLConnection) url.openConnection();
							conn.setConnectTimeout(10000);
							conn.setRequestProperty("Range", "bytes=" + start + "-" + end);		// 设置当前线程下载的范围
							
							InputStream in = conn.getInputStream();								// 获取连接的输入流
							RandomAccessFile dataRaf = new RandomAccessFile(dataFile, "rws");	// 装载数据的本地文件(可以理解为输出流)
							dataRaf.seek(start);												// 设置当前线程保存数据的位置
							
							byte[] buffer = new byte[1024 * 100];			// 每次拷贝100KB
							int len;
							while (totalFinish<totalLen && (len = in.read(buffer)) != -1) {
								dataRaf.write(buffer, 0, len);				// 从服务端读取数据, 写到本地文件
								threadFinish += len;						// 每次写入数据之后, 统计当前线程完成了多少
								tempRaf.seek(id * 8);						// 将临时文件的指针指向当前线程的位置
								tempRaf.writeLong(threadFinish);			// 将当前线程完成了多少写入到临时文件
								synchronized(BreakpointDownloader.this) {	// 多个下载线程之间同步
									totalFinish += len;						// 统计所有线程总共完成了多少
									Message msg4 = new Message();
									msg4.getData().putLong("totalFinish", totalFinish);
									msg4.what = 2;
									handler.sendMessage(msg4);				// 发送当前进度
//									System.out.println("下载进度-----》"+totalFinish+"/"+totalLen);
								}
							}
							dataRaf.close();
							tempRaf.close();
						}				
					
					}catch(Exception e2){						
						e2.printStackTrace();
						isrun = false;
						System.out.println("2 ---》这里异常了");
						if(flag) {
						    flag = false;
						    handler.sendEmptyMessage(3);
						    allthreadisrun = false;
						}							
					}
					
				}

			}
				
				if (totalFinish == totalLen || totalFinish == (totalLen + THREAD_AMOUNT - 1)) {					// 如果已完成长度等于服务端文件长度(代表下载完成)
					System.out.println("下载完成, 耗时: " + (System.currentTimeMillis() - begin));
					tempFile.delete();							// 删除临时文件
				}else if(dataFile.length()<totalLen){
					//未下载完成
					System.out.println("下载未完成");
				    allthreadisrun = false;
				    handler.sendEmptyMessage(3);				
				}

		}
	}
}


和大家分享一下

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics