1. 程式人生 > >Android實現網路多執行緒檔案下載

Android實現網路多執行緒檔案下載

實現原理

(1)首先獲得下載檔案的長度,然後設定本地檔案的長度。

(2)根據檔案長度和執行緒數計算每條執行緒下載的資料長度和下載位置。

如:檔案的長度為6M,執行緒數為3,那麼,每條執行緒下載的資料長度為2M,每條執行緒開始下載的位置如下圖所示:

(網上找的圖)

 例如10M大小,使用3個執行緒來下載,

執行緒下載的資料長度   (10%3 == 0 ? 10/3:10/3+1) ,第1,2個執行緒下載長度是4M,第三個執行緒下載長度為2M
下載開始位置:執行緒id*每條執行緒下載的資料長度 = ?
下載結束位置:(執行緒id+1)*每條執行緒下載的資料長度-1=?

之前練習時的一個demo,不多說了,直接上程式碼吧,有關斷點續傳,需要使用資料庫,不再加了,網上有很多成熟的專案可以直接用。

例項

MainApp:

package com.amos.app;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import com.amos.download.R;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

/**
 * @author yangxiaolong
 * @2014-5-6
 */
public class MainApp extends Activity implements OnClickListener {

	private static final String TAG = MainApp.class.getSimpleName();

	/** 顯示下載進度TextView */
	private TextView mMessageView;
	/** 顯示下載進度ProgressBar */
	private ProgressBar mProgressbar;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.progress_activity);
		findViewById(R.id.download_btn).setOnClickListener(this);
		mMessageView = (TextView) findViewById(R.id.download_message);
		mProgressbar = (ProgressBar) findViewById(R.id.download_progress);
	}

	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.download_btn) {
			doDownload();
		}
	}

	/**
	 * 使用Handler更新UI介面資訊
	 */
	@SuppressLint("HandlerLeak")
	Handler mHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {

			mProgressbar.setProgress(msg.getData().getInt("size"));

			float temp = (float) mProgressbar.getProgress()
					/ (float) mProgressbar.getMax();

			int progress = (int) (temp * 100);
			if (progress == 100) {
				Toast.makeText(MainApp.this, "下載完成!", Toast.LENGTH_LONG).show();
			}
			mMessageView.setText("下載進度:" + progress + " %");

		}
	};

	/**
	 * 下載準備工作,獲取SD卡路徑、開啟執行緒
	 */
	private void doDownload() {
		// 獲取SD卡路徑
		String path = Environment.getExternalStorageDirectory()
				+ "/amosdownload/";
		File file = new File(path);
		// 如果SD卡目錄不存在建立
		if (!file.exists()) {
			file.mkdir();
		}
		// 設定progressBar初始化
		mProgressbar.setProgress(0);

		// 簡單起見,我先把URL和檔名稱寫死,其實這些都可以通過HttpHeader獲取到
		String downloadUrl = "http://gdown.baidu.com/data/wisegame/91319a5a1dfae322/baidu_16785426.apk";
		String fileName = "baidu_16785426.apk";
		int threadNum = 5;
		String filepath = path + fileName;
		Log.d(TAG, "download file  path:" + filepath);
		downloadTask task = new downloadTask(downloadUrl, threadNum, filepath);
		task.start();
	}

	/**
	 * 多執行緒檔案下載
	 * 
	 * @author yangxiaolong
	 * @2014-8-7
	 */
	class downloadTask extends Thread {
		private String downloadUrl;// 下載連結地址
		private int threadNum;// 開啟的執行緒數
		private String filePath;// 儲存檔案路徑地址
		private int blockSize;// 每一個執行緒的下載量

		public downloadTask(String downloadUrl, int threadNum, String fileptah) {
			this.downloadUrl = downloadUrl;
			this.threadNum = threadNum;
			this.filePath = fileptah;
		}

		@Override
		public void run() {

			FileDownloadThread[] threads = new FileDownloadThread[threadNum];
			try {
				URL url = new URL(downloadUrl);
				Log.d(TAG, "download file http path:" + downloadUrl);
				URLConnection conn = url.openConnection();
				// 讀取下載檔案總大小
				int fileSize = conn.getContentLength();
				if (fileSize <= 0) {
					System.out.println("讀取檔案失敗");
					return;
				}
				// 設定ProgressBar最大的長度為檔案Size
				mProgressbar.setMax(fileSize);

				// 計算每條執行緒下載的資料長度
				blockSize = (fileSize % threadNum) == 0 ? fileSize / threadNum
						: fileSize / threadNum + 1;

				Log.d(TAG, "fileSize:" + fileSize + "  blockSize:");

				File file = new File(filePath);
				for (int i = 0; i < threads.length; i++) {
					// 啟動執行緒,分別下載每個執行緒需要下載的部分
					threads[i] = new FileDownloadThread(url, file, blockSize,
							(i + 1));
					threads[i].setName("Thread:" + i);
					threads[i].start();
				}

				boolean isfinished = false;
				int downloadedAllSize = 0;
				while (!isfinished) {
					isfinished = true;
					// 當前所有執行緒下載總量
					downloadedAllSize = 0;
					for (int i = 0; i < threads.length; i++) {
						downloadedAllSize += threads[i].getDownloadLength();
						if (!threads[i].isCompleted()) {
							isfinished = false;
						}
					}
					// 通知handler去更新檢視元件
					Message msg = new Message();
					msg.getData().putInt("size", downloadedAllSize);
					mHandler.sendMessage(msg);
					// Log.d(TAG, "current downloadSize:" + downloadedAllSize);
					Thread.sleep(1000);// 休息1秒後再讀取下載進度
				}
				Log.d(TAG, " all of downloadSize:" + downloadedAllSize);

			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		}
	}

}


FileDownloadThread:

package com.amos.app;

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 yangxiaolong
 * @2014-5-6
 */
public class FileDownloadThread extends Thread {

	private static final String TAG = FileDownloadThread.class.getSimpleName();

	/** 當前下載是否完成 */
	private boolean isCompleted = false;
	/** 當前下載檔案長度 */
	private int downloadLength = 0;
	/** 檔案儲存路徑 */
	private File file;
	/** 檔案下載路徑 */
	private URL downloadUrl;
	/** 當前下載執行緒ID */
	private int threadId;
	/** 執行緒下載資料長度 */
	private int blockSize;

	/**
	 * 
	 * @param url:檔案下載地址
	 * @param file:檔案儲存路徑
	 * @param blocksize:下載資料長度
	 * @param threadId:執行緒ID
	 */
	public FileDownloadThread(URL downloadUrl, File file, int blocksize,
			int threadId) {
		this.downloadUrl = downloadUrl;
		this.file = file;
		this.threadId = threadId;
		this.blockSize = blocksize;
	}

	@Override
	public void run() {

		BufferedInputStream bis = null;
		RandomAccessFile raf = null;

		try {
			URLConnection conn = downloadUrl.openConnection();
			conn.setAllowUserInteraction(true);

			int startPos = blockSize * (threadId - 1);//開始位置
			int endPos = blockSize * threadId - 1;//結束位置
			//設定當前執行緒下載的起點、終點
			conn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
			System.out.println(Thread.currentThread().getName() + "  bytes="
					+ startPos + "-" + endPos);

			byte[] buffer = new byte[1024];
			bis = new BufferedInputStream(conn.getInputStream());

			raf = new RandomAccessFile(file, "rwd");
			raf.seek(startPos);
			int len;
			while ((len = bis.read(buffer, 0, 1024)) != -1) {
				raf.write(buffer, 0, len);
				downloadLength += len;
			}
			isCompleted = true;
			Log.d(TAG, "current thread task has finished,all size:"
					+ downloadLength);

		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (bis != null) {
				try {
					bis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (raf != null) {
				try {
					raf.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 執行緒檔案是否下載完畢
	 */
	public boolean isCompleted() {
		return isCompleted;
	}

	/**
	 * 執行緒下載檔案長度
	 */
	public int getDownloadLength() {
		return downloadLength;
	}

}


效果圖:


Log控制檯:


可以看到檔案總大小、我們建立的5個執行緒每個負責下載的區間

SD卡:


斷點續傳

這個用到了資料儲存儲存當前每個執行緒下載檔案的長度,等下一次再下載時讀取,網上有成熟的案例,就不再造輪子了,資源裡我打包了自己的專案和帶斷點續傳的專案(別人的),大家可以下載下來直接用,全部免費: