在上一集中,我們簡單介紹了如何建立多任務下載,但那種還不能拿來實用,這一集我們重點通過代碼為大家展示如何建立多線程斷點續傳下載,這在實際項目中很常用.
main.xml:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" ><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/editText"android:text="http://gongxue.cn/yingyinkuaiche/UploadFiles_9323/201008/2010082909434077.mp3"/><LinearLayoutandroid:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/downButton"android:text="Download"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/pauseButton"android:enabled="false"android:text="Pause"/></LinearLayout><ProgressBarandroid:layout_width="match_parent"android:layout_height="18dp"style="?android:attr/progressBarStyleHorizontal"android:id="@+id/progressBar"/><TextViewandroid:layout_width="fill_parent"android:layout_height="wrap_content"android:id="@+id/textView"android:gravity="center"/></LinearLayout>
String.xml:
<?xml version="1.0" encoding="utf-8"?><resources> <string name="hello">Hello World, Main!</string> <string name="app_name">多線程斷點續傳下載</string></resources>
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sms.multithreaddownload" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".Main" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <uses-library android:name="android.test.runner" /> </application> <uses-sdk android:minSdkVersion="8" /> <instrumentation android:targetPackage="sms.multithreaddownload" android:name="android.test.InstrumentationTestRunner" /> <!-- 訪問網路的許可權 --> <uses-permission android:name="android.permission.INTERNET"/> <!-- SDCard寫資料的許可權 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> </manifest>
activity程式:
package sms.multithreaddownload;import java.io.File;import sms.multithreaddownload.bean.DownloadListener;import sms.multithreaddownload.service.DownloadService;import android.app.Activity;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.Message;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.EditText;import android.widget.ProgressBar;import android.widget.TextView;import android.widget.Toast;public class Main extends Activity {private EditText path;private TextView progress;private ProgressBar progressBar;private Handler handler = new UIHandler();private DownloadService servcie;private Button downButton;private Button pauseButton;private final class UIHandler extends Handler {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:int downloaded_size = msg.getData().getInt("size");progressBar.setProgress(downloaded_size);int result = (int) ((float) downloaded_size / progressBar.getMax() * 100);progress.setText(result + "%");if (progressBar.getMax() == progressBar.getProgress()) {Toast.makeText(getApplicationContext(), "下載完成", Toast.LENGTH_LONG).show();}}}}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);path = (EditText) this.findViewById(R.id.editText);progress = (TextView) this.findViewById(R.id.textView);progressBar = (ProgressBar) this.findViewById(R.id.progressBar);downButton = (Button) this.findViewById(R.id.downButton);pauseButton = (Button) this.findViewById(R.id.pauseButton);downButton.setOnClickListener(new DownloadButton());pauseButton.setOnClickListener(new PauseButton());}private final class DownloadButton implements View.OnClickListener {@Overridepublic void onClick(View v) {DownloadTask task;try {task = new DownloadTask(path.getText().toString());servcie.isPause = false;v.setEnabled(false);pauseButton.setEnabled(true);new Thread(task).start();} catch (Exception e) {e.printStackTrace();}}}public class PauseButton implements OnClickListener {@Overridepublic void onClick(View v) {servcie.isPause = true;v.setEnabled(false);downButton.setEnabled(true);}}public void pause(View v) {}private final class DownloadTask implements Runnable {public DownloadTask(String target) throws Exception {if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {File destination = Environment.getExternalStorageDirectory();servcie = new DownloadService(target, destination, 3, getApplicationContext());progressBar.setMax(servcie.fileSize);} else {Toast.makeText(getApplicationContext(), "SD卡不存在或防寫保護!", Toast.LENGTH_LONG).show();}}@Overridepublic void run() {try {servcie.download(new DownloadListener() {@Overridepublic void onDownload(int downloaded_size) {Message message = new Message();message.what = 1;message.getData().putInt("size", downloaded_size);handler.sendMessage(message);}});} catch (Exception e) {e.printStackTrace();}}}}
工具類:
package sms.multithreaddownload.bean;import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;public class DBHelper extends SQLiteOpenHelper {public DBHelper(Context context) {super(context, "MultiDownLoad.db", null, 1);}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL("CREATE TABLE fileDownloading(_id integer primary key autoincrement,downPath varchar(100),threadId INTEGER,downLength INTEGER)");}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {// TODO Auto-generated method stub}}
package sms.multithreaddownload.bean;public interface DownloadListener {public void onDownload(int downloaded_size);}
package sms.multithreaddownload.bean;import java.io.File;import java.io.InputStream;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;import sms.multithreaddownload.service.DownloadService;import android.util.Log;public final class MultiThreadDownload implements Runnable {public int id;private RandomAccessFile savedFile;private String path;/* 當前已下載量 */public int currentDownloadSize = 0;/* 下載狀態 */public boolean finished;/* 用於監視下載狀態 */private final DownloadService downloadService;/* 線程下載任務的起始點 */public int start;/* 線程下載任務的結束點 */private int end;public MultiThreadDownload(int id, File savedFile, int block, String path, Integer downlength, DownloadService downloadService) throws Exception {this.id = id;this.path = path;if (downlength != null) this.currentDownloadSize = downlength;this.savedFile = new RandomAccessFile(savedFile, "rwd");this.downloadService = downloadService;start = id * block + currentDownloadSize;end = (id + 1) * block;}@Overridepublic void run() {try {HttpURLConnection conn = (HttpURLConnection) new URL(path).openConnection();conn.setConnectTimeout(5000);conn.setRequestMethod("GET");conn.setRequestProperty("Range", "bytes=" + start + "-" + end); // 設定擷取資料的範圍InputStream in = conn.getInputStream();byte[] buffer = new byte[1024];int len = 0;savedFile.seek(start);while (!downloadService.isPause && (len = in.read(buffer)) != -1) {savedFile.write(buffer, 0, len);currentDownloadSize += len;}savedFile.close();in.close();conn.disconnect();if (!downloadService.isPause) Log.i(DownloadService.TAG, "Thread " + (this.id + 1) + "finished");finished = true;} catch (Exception e) {e.printStackTrace();throw new RuntimeException("File downloading error!");}}}
service類:
package sms.multithreaddownload.service;import java.io.File;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Map.Entry;import java.util.UUID;import java.util.concurrent.ConcurrentHashMap;import java.util.regex.Matcher;import java.util.regex.Pattern;import sms.multithreaddownload.bean.DBHelper;import sms.multithreaddownload.bean.DownloadListener;import sms.multithreaddownload.bean.MultiThreadDownload;import android.content.Context;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;public class DownloadService {public static final String TAG = "tag";/* 用於查詢資料庫 */private DBHelper dbHelper;/* 要下載的檔案大小 */public int fileSize;/* 每條線程需要下載的資料量 */private int block;/* 儲存檔案地目錄 */private File savedFile;/* */private String path;/* 是否停止下載 */public boolean isPause;/* 線程數 */private MultiThreadDownload[] threads;/* 各線程已經下載的資料量 */private Map<Integer, Integer> downloadedLength = new ConcurrentHashMap<Integer, Integer>();public DownloadService(String target, File destination, int thread_size, Context context) throws Exception {dbHelper = new DBHelper(context);this.threads = new MultiThreadDownload[thread_size];this.path = target;URL url = new URL(target);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5000);conn.setRequestMethod("GET");if (conn.getResponseCode() != 200) {throw new RuntimeException("server no response!");}fileSize = conn.getContentLength();if (fileSize <= 0) {throw new RuntimeException("file is incorrect!");}String fileName = getFileName(conn);if (!destination.exists()) destination.mkdirs();// 構建一個同樣大小的檔案this.savedFile = new File(destination, fileName);RandomAccessFile doOut = new RandomAccessFile(savedFile, "rwd");doOut.setLength(fileSize);doOut.close();conn.disconnect();// 計算每條線程需要下載的資料長度this.block = fileSize % thread_size == 0 ? fileSize / thread_size : fileSize / thread_size + 1;// 查詢已經下載的記錄downloadedLength = this.getDownloadedLength(path);}private Map<Integer, Integer> getDownloadedLength(String path) {SQLiteDatabase db = dbHelper.getReadableDatabase();String sql = "SELECT threadId,downLength FROM fileDownloading WHERE downPath=?";Cursor cursor = db.rawQuery(sql, new String[] { path });Map<Integer, Integer> data = new HashMap<Integer, Integer>();while (cursor.moveToNext()) {data.put(cursor.getInt(0), cursor.getInt(1));}db.close();return data;}private String getFileName(HttpURLConnection conn) {String fileName = path.substring(path.lastIndexOf("/") + 1, path.length());if (fileName == null || "".equals(fileName.trim())) {String content_disposition = null;for (Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {if ("content-disposition".equalsIgnoreCase(entry.getKey())) {content_disposition = entry.getValue().toString();}}try {Matcher matcher = Pattern.compile(".*filename=(.*)").matcher(content_disposition);if (matcher.find()) fileName = matcher.group(1);} catch (Exception e) {fileName = UUID.randomUUID().toString() + ".tmp"; // 預設名}}return fileName;}public void download(DownloadListener listener) throws Exception {this.deleteDownloading(); // 先刪除上次的記錄,再重新添加for (int i = 0; i < threads.length; i++) {threads[i] = new MultiThreadDownload(i, savedFile, block, path, downloadedLength.get(i), this);new Thread(threads[i]).start();}this.saveDownloading(threads);while (!isFinish(threads)) {Thread.sleep(900);if (listener != null) listener.onDownload(getDownloadedSize(threads));this.updateDownloading(threads);}if (!this.isPause) this.deleteDownloading();// 完成下載之後刪除本次下載記錄}private void saveDownloading(MultiThreadDownload[] threads) {SQLiteDatabase db = dbHelper.getWritableDatabase();try {db.beginTransaction();for (MultiThreadDownload thread : threads) {String sql = "INSERT INTO fileDownloading(downPath,threadId,downLength) values(?,?,?)";db.execSQL(sql, new Object[] { path, thread.id, 0 });}db.setTransactionSuccessful();} finally {db.endTransaction();db.close();}}private void deleteDownloading() {SQLiteDatabase db = dbHelper.getWritableDatabase();String sql = "DELETE FROM fileDownloading WHERE downPath=?";db.execSQL(sql, new Object[] { path });db.close();}private void updateDownloading(MultiThreadDownload[] threads) {SQLiteDatabase db = dbHelper.getWritableDatabase();try {db.beginTransaction();for (MultiThreadDownload thread : threads) {String sql = "UPDATE fileDownloading SET downLength=? WHERE threadId=? AND downPath=?";db.execSQL(sql, new String[] { thread.currentDownloadSize + "", thread.id + "", path });}db.setTransactionSuccessful();} finally {db.endTransaction();db.close();}}private int getDownloadedSize(MultiThreadDownload[] threads) {int sum = 0;for (int len = threads.length, i = 0; i < len; i++) {sum += threads[i].currentDownloadSize;}return sum;}private boolean isFinish(MultiThreadDownload[] threads) {try {for (int len = threads.length, i = 0; i < len; i++) {if (!threads[i].finished) {return false;}}return true;} catch (Exception e) {return false;}}}
運行效果:
源碼
http://blog.csdn.net/shimiso/article/details/8448544 android 多線程斷點續傳下載 三
java技術交流 Q群:173711587