本案例在於實現檔案的多線程斷點下載,即檔案在下載一部分中斷後,可繼續接著已有進度下載,並通過進度條顯示進度。也就是說在檔案開始下載的同時,自動建立每個線程的下載進度的本地檔案,下載中斷後,重新進入應用點擊下載,程式檢查有沒有本地檔案的存在,若存在,擷取本地檔案中的下載進度,繼續進行下載,當下載完成後,自動刪除本地檔案。
1. 定義布局檔案需要用到的屬性名稱及內容
2. 設定使用者的Internet許可權和關於SD卡的許可權
3. 開始介面的布局
基本如下:
用到兩個TextView控制項,一個EditText控制項,一個Button控制項,一個ProgressBar控制項<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+0OjSqtei0uK1xMrHo7q9+LbIzPXTwzxQcm9ncmVzc0JhciAvPr/YvP6jrMno1sNzeXRsZcr00NSjunN0eWxlPQ=="?android:attr/progressBarStyleHorizontal"
4.MainActivity的主要程式如下,代碼中有注釋詳解:
package www.csdn.net.download;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.InputStream;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;import www.csdn.net.utils.StreamTools;import android.R.integer;import android.app.Activity;import android.os.Bundle;import android.os.Environment;import android.text.TextUtils;import android.view.View;import android.widget.EditText;import android.widget.ProgressBar;import android.widget.TextView;import android.widget.Toast;public class DownloadActivity extends Activity {// 線程開啟的數量private int threadNum = 3;private int threadRunning = 3;private EditText et_url;private ProgressBar progressBar;private TextView tv_pb;private int currentProgress;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_download);// 擷取控制項對象et_url = (EditText) findViewById(R.id.et_url);progressBar = (ProgressBar) findViewById(R.id.pb_down);tv_pb = (TextView) findViewById(R.id.tv_pb);File sdDir = Environment.getExternalStorageDirectory();File pbFile = new File(sdDir,"pb.txt");InputStream is = null;try {//判斷檔案是否存在if (pbFile.exists()) {is = new FileInputStream(pbFile);}} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}if (is != null) {String value = StreamTools.streamToStr(is);String[] arr = value.split(";");progressBar.setMax(Integer.valueOf(arr[0]));//最大值currentProgress = Integer.valueOf(arr[1]);//當前值progressBar.setProgress(currentProgress);tv_pb.setText("當前的進度是:"+arr[2]);//顯示百分比}}// 下載檔案(得到伺服器端檔案的大小)public void downLoadFile(View v) {// 擷取下載路徑final String spec = et_url.getText().toString();if (TextUtils.isEmpty(spec)) {Toast.makeText(this, "下載的地址不可為空", Toast.LENGTH_LONG).show();} else {new Thread() {public void run() {// HttpURLConnectiontry {// 根據下載的地址構建URL對象URL url = new URL(spec);// 通過URL對象的openConnection()方法開啟串連,返回一個連線物件HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();// 佈建要求的頭httpURLConnection.setRequestMethod("GET");httpURLConnection.setReadTimeout(5000);httpURLConnection.setConnectTimeout(5000);// 判斷是否響應成功if (httpURLConnection.getResponseCode() == 200) {// 擷取下載檔案的長度int fileLength = httpURLConnection.getContentLength();//設定進度條的最大值progressBar.setMax(fileLength);//判斷sd卡是否管用if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {// 儲存檔案// 外部存放裝置的路徑File sdFile = Environment.getExternalStorageDirectory();//擷取檔案的名稱String fileName = spec.substring(spec.lastIndexOf("/")+1);//建立儲存的檔案File file = new File(sdFile, fileName);//建立可以隨機訪問對象RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");// 儲存檔案的大小accessFile.setLength(fileLength);// 關閉accessFile.close();// 計算出每個線程的下載大小int threadSize = fileLength / threadNum;// 計算出每個線程的開始位置,結束位置for (int threadId = 1; threadId <= 3; threadId++) {int startIndex = (threadId - 1) * threadSize;int endIndex = threadId * threadSize - 1;if (threadId == threadNum) {// 最後一個線程endIndex = fileLength - 1;}System.out.println("當前線程:" + threadId+ " 開始位置:" + startIndex + " 結束位置:"+ endIndex + " 線程大小:" + threadSize);// 開啟線程下載new DownLoadThread(threadId, startIndex,endIndex, spec).start();}}else {DownloadActivity.this.runOnUiThread(new Runnable() {public void run() {Toast.makeText(DownloadActivity.this, "SD卡不管用", Toast.LENGTH_LONG).show();}});}}else {//在主線程中運行DownloadActivity.this.runOnUiThread(new Runnable() {public void run() {Toast.makeText(DownloadActivity.this, "伺服器端返回錯誤", Toast.LENGTH_LONG).show();}});}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}};}.start();}}class DownLoadThread extends Thread {private int threadId;private int startIndex;private int endIndex;private String path;/** * 建構函式 * * @param threadId * 線程的序號 * @param startIndex * 線程開始位置 * @param endIndex * @param path */public DownLoadThread(int threadId, int startIndex, int endIndex,String path) {super();this.threadId = threadId;this.startIndex = startIndex;this.endIndex = endIndex;this.path = path;}@Overridepublic void run() {try {File sdFile = Environment.getExternalStorageDirectory();//擷取每個線程下載的記錄檔案File recordFile = new File(sdFile, threadId + ".txt");if (recordFile.exists()) {// 讀取檔案的內容InputStream is = new FileInputStream(recordFile);// 利用工具類轉換String value = StreamTools.streamToStr(is);// 擷取記錄的位置int recordIndex = Integer.parseInt(value);// 將記錄的位置賦給開始位置startIndex = recordIndex;}// 通過path路徑構建URL對象URL url = new URL(path);// 通過URL對象的openConnection()方法開啟串連,返回一個連線物件HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();// 佈建要求的頭httpURLConnection.setRequestMethod("GET");httpURLConnection.setReadTimeout(5000);// 設定下載檔案的開始位置結束位置httpURLConnection.setRequestProperty("Range", "bytes="+ startIndex + "-" + endIndex);// 擷取的狀態代碼int code = httpURLConnection.getResponseCode();// 判斷是否成功if (code == 206) {// 擷取每個線程返回的流對象InputStream is = httpURLConnection.getInputStream();//擷取檔案的名稱String fileName = path.substring(path.lastIndexOf("/")+1);// 根據路徑建立檔案File file = new File(sdFile, fileName);// 根據檔案建立RandomAccessFile對象RandomAccessFile raf = new RandomAccessFile(file, "rwd");raf.seek(startIndex);// 定義讀取的長度int len = 0;// 定義緩衝區byte b[] = new byte[1024 * 1024];int total = 0;// 迴圈讀取while ((len = is.read(b)) != -1) {RandomAccessFile threadFile = new RandomAccessFile(new File(sdFile, threadId + ".txt"), "rwd");threadFile.writeBytes((startIndex + total) + "");threadFile.close();raf.write(b, 0, len);// 已經下載的大小total += len;//解決同步問題synchronized (DownloadActivity.this) {currentProgress += len;progressBar.setProgress(currentProgress);//計算百分比的操作 l表示long型final String percent = currentProgress*100l/progressBar.getMax()+"%";DownloadActivity.this.runOnUiThread(new Runnable() {public void run() {tv_pb.setText("當前的進度是:"+percent);}});//建立儲存當前進度和百分比的操作RandomAccessFile pbFile = new RandomAccessFile(new File(sdFile, "pb.txt"), "rwd");pbFile.writeBytes(progressBar.getMax()+";"+currentProgress+";"+percent);pbFile.close();}}raf.close();is.close();runOnUiThread(new Runnable() {public void run() {Toast.makeText(DownloadActivity.this, "當前線程--" + threadId + "--下載完畢", Toast.LENGTH_LONG).show();}});deleteRecordFiles();} else {runOnUiThread(new Runnable() {public void run() {Toast.makeText(DownloadActivity.this, "伺服器端下載錯誤", Toast.LENGTH_LONG).show();}});}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}}// synchronized避免線程同步public synchronized void deleteRecordFiles() {File sdFile = Environment.getExternalStorageDirectory();threadRunning--;if (threadRunning == 0) {for (int i = 1; i <= 3; i++) {File recordFile = new File(sdFile, i + ".txt");if (recordFile.exists()) {// 刪除檔案recordFile.delete();}File pbFile = new File(sdFile,"pb.txt");if (pbFile.exists()) {pbFile.delete();}}}}}
對於流的輸出可以封裝一個StreamTools方法,在主程式中可以應用,代碼如下:
package www.csdn.net.utils;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;public class StreamTools {public static String streamToStr(InputStream is){String value = null;try {ByteArrayOutputStream baos = new ByteArrayOutputStream();// 定義讀取的長度int len = 0;// 定義緩衝區byte b[] = new byte[1024];// 迴圈讀取while ((len = is.read(b)) != -1) {baos.write(b, 0, len);}baos.close();is.close();value = new String(baos.toByteArray());} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}return value;}}5. 程式運行結果
sd卡中出現的臨時檔案,當下載完成會自動刪除:
6. 出現的bug原因可能有:
Internet許可權沒加,伺服器沒啟動,訪問下載路徑有錯,沒有擷取控制項對象等。
如果檔案下載中,進度條顯示的進度是負數,可能原因是檔案大小進行百分比計算時超出記憶體空間,解決辦法:在定義百分比的時候,在100後面加上l,表示long型,即String percent = currentProgress*100l/progressBar.getMax()+"%"。