重游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;}@Overridepublic 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;}}// 下载完成设为truethis.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();}}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); }@Overridepublic 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();}@Overridepublic 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() {@Overridepublic 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);}}}