Android學習記錄(6)—將java中的多線程下載移植到Android中(即多線程下載在Android中的使用)③

在這一節中,我們就來講多線程下載以及斷點續傳在android中怎麼使用,前兩節是為本節做準備的,沒有看前兩節的同學,最好看完前面的兩篇文章再來看這篇。其實在android端的應用和java基本上是差不多的,隻不過在android端我建議對於斷點續傳的記錄的保存放在android的sqlite3的數據庫中,這樣是最好的,當然保存在sd卡中也行,我為瞭方便起見,我沒有建立數據庫,而是直接保存到瞭sd卡中。先看一下我在android中運行的效果圖,如下:

我在這裡的代碼加上瞭進度條的顯示和下載進度百分比的顯示,為瞭讓進度條和百分比不混亂,我為下載線程中計算下載進度的代碼加上瞭鎖。如下:

還有就是下邊的一段代碼我把記錄下載進度的文件保存到瞭SD卡中,我建議最好用數據庫。這段代碼如下:

其它的我倒是沒有什麼好解釋的瞭,跟上一篇文章的內容差不多,而且我在代碼中已經寫得很詳細瞭,那麼就請大傢看代碼吧!

MainActivity中的代碼如下:

 

package net.loonggg.android.downloader;

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

import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

@SuppressLint(HandlerLeak)
public class MainActivity extends Activity {
	public int currentProcess = 0;// 下載文件的當前進度
	// 開啟的線程的個數
	public static final int THREAD_COUNT = 3;
	public static int runningThread = 3;// 記錄正在運行的下載文件的線程數
	private EditText et;
	private Button btn;
	private TextView tv;
	private ProgressBar pb;// 下載進度條

	private Handler handler = new Handler() {
		public void handleMessage(android.os.Message msg) {
			switch (msg.arg1) {
			case 0:
				Toast.makeText(getApplicationContext(), 下載失敗!,
						Toast.LENGTH_SHORT).show();
				break;
			case 1:
				Toast.makeText(getApplicationContext(), 下載完成!,
						Toast.LENGTH_SHORT).show();
				break;
			case 2:
				tv.setText(下載進度: + pb.getProgress() * 100 / pb.getMax() + %);
				break;
			default:
				break;
			}
		};
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main);
		et = (EditText) findViewById(R.id.et);
		pb = (ProgressBar) findViewById(R.id.pb);
		btn = (Button) findViewById(R.id.btn);
		tv = (TextView) findViewById(R.id.tv_process);
		btn.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				downLoad();
			}
		});
	}

	/**
	 * 下載文件的方法
	 */
	private void downLoad() {
		final String path = et.getText().toString();
		new Thread() {
			public void run() {
				try {
					// 1、連接服務器,獲取一個文件,獲取文件的長度,在本地創建一個大小跟服務器文件大小一樣的臨時文件
					URL url = new URL(path);
					HttpURLConnection conn = (HttpURLConnection) url
							.openConnection();
					conn.setConnectTimeout(5000);
					conn.setRequestMethod(GET);
					int code = conn.getResponseCode();
					if (code == 200) {
						// 服務器返回的數據的長度,實際就是文件的長度
						int length = conn.getContentLength();
						pb.setMax(length);// 為進度條設置最大值
						System.out.println(----文件總長度---- + length);
						// 在客戶端本地創建出來一個大小跟服務器端文件一樣大小的臨時文件
						RandomAccessFile raf = new RandomAccessFile(
								/sdcard/temp.apk, rwd);
						// 指定創建的這個文件的長度
						raf.setLength(length);
						// 關閉raf
						raf.close();
						// 假設是3個線程去下載資源
						// 平均每一個線程下載的文件的大小
						int blockSize = length / THREAD_COUNT;
						for (int threadId = 1; threadId <= THREAD_COUNT; threadId++) {
							// 第一個線程開始下載的位置
							int startIndex = (threadId - 1) * blockSize;
							int endIndex = threadId * blockSize - 1;
							if (threadId == THREAD_COUNT) {
								endIndex = length;
							}
							System.out.println(----threadId---
									+ --startIndex-- + startIndex
									+ --endIndex-- + endIndex);
							new DownloadThread(path, threadId, startIndex,
									endIndex).start();
						}
					}
				} catch (Exception e) {
					e.printStackTrace();
					Message msg = new Message();
					msg.arg1 = 0;
					handler.sendMessage(msg);
				}
			};
		}.start();
	}

	/**
	 * 下載文件的子線程,每一個線程下載對應位置的文件
	 * 
	 * @author loonggg
	 * 
	 */
	public class DownloadThread extends Thread {
		private int threadId;
		private int startIndex;
		private int endIndex;
		private String path;

		/**
		 * @param path
		 *            下載文件在服務器上的路徑
		 * @param threadId
		 *            線程id
		 * @param startIndex
		 *            線程下載的開始位置
		 * @param endIndex
		 *            線程下載的結束位置
		 */
		public DownloadThread(String path, int threadId, int startIndex,
				int endIndex) {
			this.path = path;
			this.threadId = threadId;
			this.startIndex = startIndex;
			this.endIndex = endIndex;
		}

		@Override
		public void run() {
			try {
				// 檢查是否存在記錄下載長度的文件,如果存在讀取這個文件的數據
				// -------------------------替換成數據庫----------------------------
				File tempFile = new File(/sdcard/ + threadId + .txt);
				if (tempFile.exists() && tempFile.length() > 0) {
					FileInputStream fis = new FileInputStream(tempFile);
					byte[] temp = new byte[1024 * 10];
					int leng = fis.read(temp);
					// 已經下載的長度
					String downloadLen = new String(temp, 0, leng);
					int downloadInt = Integer.parseInt(downloadLen);
					// ------------------這兩行代碼是關於斷點續傳時,設置進度條的起點時的關鍵代碼-------------------
					int alreadyDownloadInt = downloadInt - startIndex;
					currentProcess += alreadyDownloadInt;// 計算每個線程上次斷點已經下載的文件的長度
					// ---------------------------------------------------------------------------------
					startIndex = downloadInt;
					fis.close();
				}
				// ---------------------------------------------------------------

				URL url = new URL(path);
				HttpURLConnection conn = (HttpURLConnection) url
						.openConnection();
				conn.setRequestMethod(GET);
				// 重要:請求服務器下載部分的文件 指定文件的位置
				conn.setRequestProperty(Range, bytes= + startIndex + -
						+ endIndex);
				conn.setConnectTimeout(5000);
				// 從服務器請求全部資源的狀態碼200 ok 如果從服務器請求部分資源的狀態碼206 ok
				int code = conn.getResponseCode();
				System.out.println(---code--- + code);
				InputStream is = conn.getInputStream();// 已經設置瞭請求的位置,返回的是當前位置對應的文件的輸入流
				RandomAccessFile raf = new RandomAccessFile(/sdcard/temp.apk,
						rwd);
				// 隨機寫文件的時候從哪個位置開始寫
				raf.seek(startIndex);// 定位文件
				int len = 0;
				byte[] buffer = new byte[1024];
				int total = 0;// 記錄已經下載的數據的長度
				while ((len = is.read(buffer)) != -1) {
					RandomAccessFile recordFile = new RandomAccessFile(
							/sdcard/ + threadId + .txt, rwd);// 記錄每個線程的下載進度,為斷點續傳做標記
					raf.write(buffer, 0, len);
					total += len;
					recordFile.write(String.valueOf(startIndex + total)
							.getBytes());
					recordFile.close();
					// 同步加鎖,防止混亂
					synchronized (MainActivity.this) {
						currentProcess += len;// 獲取當前的總進度
						// 特殊情況,ProgressBarH和ProgressDialog進度條和對話框可以在子線程裡面更新UI,系統內部代碼特殊處理
						pb.setProgress(currentProcess);// 更改界面上進度條的進度
						Message msg = Message.obtain();
						msg.arg1 = 2;
						// 發送更新消息,更新進度的百分比
						handler.sendMessage(msg);
					}
				}
				is.close();
				raf.close();
				System.out.println(線程: + threadId + 下載完畢瞭!);
			} catch (Exception e) {
				e.printStackTrace();
				Message msg = handler.obtainMessage();
				msg.arg1 = 0;
				handler.sendMessage(msg);
			} finally {
				threadFinish();
			}
		}

		/**
		 * 我個人認為不鎖定也可以,但是鎖定可能更安全,如果誰有好的建議,到底用不用鎖定,請給我留言
		 */
		private synchronized void threadFinish() {
			runningThread--;
			if (runningThread == 0) {// 所有的線程已經執行完畢
				for (int i = 1; i <= THREAD_COUNT; i++) {// 刪除記錄下載進度的文件
					File file = new File(/sdcard/ + i + .txt);
					file.delete();
					Message msg = handler.obtainMessage();
					msg.arg1 = 1;
					handler.sendMessage(msg);
				}
			}
		}
	}

}

還有就是對應的Activity_main.xml的代碼如下:

 

 



    

    

    

當然大傢別忘瞭在清單文件中設置網絡權限和讀寫SD卡的權限:

 

 

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *