Javaシリーズのスレッド2を再読み込み(マルチスレッドダウンロードとブレークポイント継続)


この文章はあなたのファイルのダウンロード中のマルチスレッドのダウンロードとブレークポイントの継続を問題の出発点として、主にマルチスレッドの実際の開発における応用と具体的な実現を振り返ってみましょう.
    マルチスレッドダウンロードのキーは、1つのダウンロードタスクを分割することです.すなわち、各タスクスレッドに対応する実際のファイルの開始点と終了点を計算します.各スレッドにデータストリーム方式でリモートファイルを接続します.ここには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);
		}
	}
	
}