`
ilikeido
  • 浏览: 26738 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

重游java系列一 线程二(多线程下载和断点续传)

阅读更多
   本篇文章以你文件下载中的多线程下载以及断点续传为问题出发点,主要回顾一下多线程在实际开发中的应用和具体实现。

    多线程下载的关键点在于对一个下载任务进行切分,即计算每个任务线程对应的实际文件中的起始点和终止点。在每个线程中采用数据流方式对远程文件进行连接,这里有个知识点,即http头Rander参数,详见http://guoba6688-sina-com.iteye.com/blog/786036,通过该参数可以实现读取远程文件的指定部分。

    下面的实例的应用环境为android,具体看代码:

   
package com.fsti.android.foyer.net;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;

import android.util.Log;

/**
 * 文件下载线程
 * 
 * @author ilikeido
 * @modifyAuthor 
 * 
 * @creatTime 2011-4-7 下午02:59:04
 */
public class FileDownloadThread extends Thread {
	
	private static final int BUFFER_SIZE = 1024;
	private URL url;//地址
	private File file;//保存文件
	private int startPosition;//开始位置
	private int endPosition;//结束位置
	private int curPosition;//当前位置
	
	private ThreadCutter cutter;
	
	// 用于标识当前线程是否下载完成
	private boolean finished = false;
	private int downloadSize = 0;//下载的文件大小

	public FileDownloadThread(URL url, File file, int startPosition,
			int endPosition,ThreadCutter cutter) {
		this.url = url;
		this.file = file;
		this.startPosition = startPosition;
		this.curPosition = startPosition;
		this.endPosition = endPosition;
		this.cutter = cutter;
	}

	@Override
	public void run() {
		BufferedInputStream bis = null;
		RandomAccessFile fos = null;
		byte[] buf = new byte[BUFFER_SIZE];
		URLConnection con = null;
		try {
			fos = new RandomAccessFile(file, "rw");
			downloadSize = (int) fos.length();
			con = url.openConnection();
			con.setAllowUserInteraction(true);
			// 设置当前线程下载的起点,终点
			con.setRequestProperty("Range", "bytes=" + (startPosition+downloadSize) + "-"
					+ endPosition);
			// 使用java中的RandomAccessFile对文件进行随机读写操作
			
			curPosition = startPosition + downloadSize;
			// 设置开始写文件的位置
			fos.seek(downloadSize);
			bis = new BufferedInputStream(con.getInputStream());
			// 开始循环以流的形式读写文件
			while (cutter.getStatu() == 1 && curPosition < endPosition) {
				int len = bis.read(buf, 0, BUFFER_SIZE);
				if (len == -1) {
					break;
				}
				fos.write(buf, 0, len);
				curPosition = curPosition + len;
				if (curPosition > endPosition) {
					downloadSize += len - (curPosition - endPosition) + 1;
				} else {
					downloadSize += len;
				}
			}
			// 下载完成设为true
			this.finished = true;
			bis.close();
			fos.close();
		} catch (IOException e) {
			Log.d(getName() + "Error:", e.getMessage());
		}
	}

	public boolean isFinished() {
		return finished;
	}

	public int getDownloadSize() {
		return downloadSize;
	}
	
	public File getFile(){
		return file;
	}
}

以上为线程下载任务,主要负责下载任务,下面看一下管理器
package com.fsti.android.foyer.net;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import android.util.Log;

/**
 * 文件分割器(用于多线程下载)
 * 
 * @author ilikeido
 * @modifyAuthor
 * 
 * @creatTime 2011-4-7 下午03:05:53
 */
public class DownThreadManager {

	private static final String TAG = "ThreadCutter";

	private int threadSize;// 线程数

	private URL url;// 地址

	private String fileName;

	private int totalProgress;// 整体进度

	private int filelength;// 文件大小

	File dir;
	File saveFile;

	private int statu;// 状态 0:停止 1:正常 2:暂停 3:完成 -1:错误

	private List<FileDownloadThread> threads;

	public DownThreadManager(int threadSize, String urlStr, File dir)
			throws IOException {
		this.dir = dir;
		this.threadSize = threadSize;
		this.url = new URL(urlStr);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		filelength = conn.getContentLength();
		if(filelength>0){
			initFileName(conn,urlStr);
			cutThreads();
		}else{
			throw new RuntimeException("file not find");
		}
		
	}

	/**
	 * 分割下载任务
	 */
	private void cutThreads() {
		threads = new ArrayList<FileDownloadThread>();
		int cutsize = filelength / threadSize;
		for (int i = 0; i < threadSize; i++) {
			File tempFile = new File(dir, fileName + i + ".temp");
			int startPosition = cutsize * i;
			int endPosition = 0;
			FileDownloadThread thread = null;
			if (i < threadSize - 1) {
				endPosition = cutsize * i + cutsize - 1;
				thread = new FileDownloadThread(url, tempFile, startPosition,
						endPosition, this);
			} else {
				thread = new FileDownloadThread(url, tempFile, startPosition,
						filelength, this);
			}
			threads.add(thread);
		}
	}

	/**
	 * 开始下载任务
	 */
	public void start() {
		Iterator<FileDownloadThread> itertor = threads.iterator();
		statu = 1;
		while (itertor.hasNext())
			itertor.next().start();
	}

	/**
	 * 获取当前下载的文件大小
	 * 
	 * @return
	 */
	public int getDownloadSize() {
		Iterator<FileDownloadThread> itertor = threads.iterator();
		int downloadSize = 0;
		while (itertor.hasNext()) {
			downloadSize += itertor.next().getDownloadSize();
		}
		if (downloadSize > 0) {
			this.totalProgress = (int) (((float) downloadSize / filelength) * 100);
		}
		if (downloadSize == filelength) {
			this.statu = 3;
		}
		return downloadSize;
	}

	public void merge() throws IOException {
		RandomAccessFile ok = new RandomAccessFile(saveFile,"rw");
		Iterator<FileDownloadThread> itertor = threads.iterator();
		while (itertor.hasNext()) {
			File file = itertor.next().getFile();
			RandomAccessFile read = new RandomAccessFile(file, "r");
			byte[] b = new byte[1024];
			int n = 0;
			while ((n = read.read(b)) != -1) {
				ok.write(b, 0, n);
			}
			read.close();
			file.delete();
		}
		ok.close();
	}

	public void stop() {
		this.statu = 2;
	}

	public int getStatu() {
		return statu;
	}

	public void setStatu(int statu) {
		this.statu = statu;
	}

	public int getTotalProgress() {
		return totalProgress;
	}

	/**
	 * 获取文件名
	 * 
	 * @param http
	 */
	public void initFileName(HttpURLConnection http,String urlStr) {
		String filename = urlStr.substring(urlStr.lastIndexOf('/') + 1);
		if(filename.indexOf(".")>=0){
			fileName = filename;
		}else{
			Map<String, String> header = getHttpResponseHeader(http);
			for (Map.Entry<String, String> entry : header.entrySet()) {
				String key = entry.getKey() != null ? entry.getKey() + ":" : "";
				print(key + entry.getValue());
			}
			String dispostion  = header.get("content-disposition");
			int index = 0;
			if((index = dispostion.indexOf("filename")) >-1){
				String filenametemp = dispostion.substring(index,dispostion.length());
				String[] temp = filenametemp.split("\"");
				fileName = temp[1];
			}
		}
		saveFile = new File(dir, fileName);
	}

	/**
	 * 获取Http响应头字段
	 * 
	 * @param http
	 * @return
	 */
	public static Map<String, String> getHttpResponseHeader(
			HttpURLConnection http) {
		Map<String, String> header = new LinkedHashMap<String, String>();
		for (int i = 0;; i++) {
			String mine = http.getHeaderField(i);
			if (mine == null)
				break;
			header.put(http.getHeaderFieldKey(i), mine);
		}
		return header;
	}

	private static void print(String msg) {
		Log.i(TAG, msg);
	}
	
	public File getSaveFile(){
		return this.getSaveFile();
	}

}

该管理器主要的任务是分割下载任务,对线程的管理(通过statu参数)、以及各线程下载完成后文件的合并等。
下面是实际中的调用:
package com.fsti.android.foyer;

import java.io.File;
import java.io.IOException;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

import com.fsti.android.foyer.net.ThreadCutter;

public class MainActivity extends Activity implements OnClickListener,DialogInterface.OnClickListener{
    /** Called when the activity is first created. */
	
	ProgressDialog dialog = null;
	int threadNum = 2;
	String urlStr= "http://www.piaoao.com/resources/updatefiles/alldown/WingLetter_GPiaoao.apk";//"http://nutla.googlecode.com/files/Nuta9.pdf";//"http://dl_dir.qq.com/qqfile/qq/Android/Tencent_Wblog%20V2.0.1.apk";//

	ThreadCutter cutter = null;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button button = (Button) findViewById(R.id.button1);
        button.setOnClickListener(this);
    }

	@Override
	public void onClick(View arg0) {
		dialog = new ProgressDialog(this);
		dialog.setProgress(59);
		dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
		dialog.setTitle("下载中");
		dialog.setMessage("正在下载应用程序");
		dialog.setButton("取消", this);
		dialog.show();
		download();
	}

	@Override
	public void onClick(DialogInterface arg0, int arg1) {
		cutter.stop();
		dialog.dismiss();
	}
	
	public void download(){
		final File dir = Environment.getExternalStorageDirectory();
		int cutterNum = 3;
		try {
			cutter = new ThreadCutter(cutterNum, urlStr, dir);
			Runnable runnable = new Runnable() {
				@Override
				public void run() {
					cutter.start();
					while(cutter.getTotalProgress() < 100){
						cutter.getDownloadSize();
						try {
							Thread.sleep(900);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						dialog.setProgress(cutter.getTotalProgress());
					}
					try {
						cutter.merge();
						cutter.setStatu(3);
					} catch (IOException e) {
						e.printStackTrace();
					}
					dialog.dismiss();
					try {
						if(cutter.getSaveFile().getAbsolutePath().endsWith(".apk")){
							Intent localIntent1 = new Intent("android.intent.action.VIEW");
						    Uri localUri = Uri.fromFile(cutter.getSaveFile());
						    localIntent1.setDataAndType(localUri, "application/vnd.android.package-archive");
						    startActivity(localIntent1);
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			};
			new Thread(runnable).start();
		} catch (IOException e) {
			cutter.setStatu(4);
		}
	}
	
}



分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics