Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android多線程下載斷點續傳

Android多線程下載斷點續傳

編輯:關於Android編程

先上圖看卡結果:
GITHUB:Android多線程下載斷點續傳
這裡寫圖片描述
如圖所示點擊下載就開始下載,點擊停止就會停止再次點擊下載就會接著下載了。
設計思路是這樣的:
首先通過廣播將下載信息傳遞給DownService,DownService根據文件URL獲取文件大小,再通過DownTask將下載任務分配,並且通過廣播當點擊停止下載時將下載進度保存在數據庫中,當點擊開始下載時再從數據庫中獲取到保存的進度,繼續下載。
代碼結構:
這裡寫圖片描述
核心類是 DownLoadService,java 和DownTask.java將這兩個類貼出來:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;"> package com.example.downloaddemo.services; import java.io.File; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.LinkedHashMap; import java.util.Map; import org.apache.http.HttpStatus; import com.example.downloaddemo.enties.FileInfo; import android.app.Service; import android.content.Intent; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.util.Log; public class DownLoadService extends Service { // 設置存儲路勁 public static final String DOWN_PATH = Environment .getExternalStorageDirectory().getAbsolutePath() + "/downloads"; public static final String ACTION_START = "ACTION_START"; public static final String ACTION_STOP = "ACTION_STOP"; public static final String ACTION_FINISH = "ACTION_FINISH"; public static final String ACTION_UPDATE = "ACTION_UPDATE"; public static final int MSG_INIT = 0; // private DownLoadTask mDownLoadTask=null; private Map mTasks = new LinkedHashMap(); @Override public int onStartCommand(Intent intent, int flags, int startId) { // 獲取Activity傳來的數據 if (ACTION_START.equals(intent.getAction())) { FileInfo fileinfo = (FileInfo) intent .getSerializableExtra("fileinfo"); InitThread minitThread=new InitThread(fileinfo); DownLoadTask.sExecutorService.execute(minitThread); Log.i("test", "start:" + fileinfo.toString()); } else if (ACTION_STOP.equals(intent.getAction())) { FileInfo fileinfo = (FileInfo) intent .getSerializableExtra("fileinfo"); // 從集合中獲取下載任務 DownLoadTask task = mTasks.get(fileinfo.getId()); if (task != null) { // 停止下載任務 task.isPause = true; } Log.i("test", "stop:" + fileinfo.toString()); } return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub return null; } Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case MSG_INIT: FileInfo info = (FileInfo) msg.obj; Log.i("test", "init:" + info.toString()); // 開啟下載任務,默認為三個線程下載 DownLoadTask task = new DownLoadTask(DownLoadService.this,info, 3); task.downLoad(); // 把下載任務添加到集合中 mTasks.put(info.getId(), task); break; default: break; } } }; /** * * 初始化子線程 */ class InitThread extends Thread { private FileInfo mfileInfo = null; public InitThread(FileInfo mfileInfo) { super(); this.mfileInfo = mfileInfo; } @Override public void run() { HttpURLConnection conn = null; RandomAccessFile raf = null; try { // 連接網絡文件 URL url = new URL(mfileInfo.getUrl()); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(3000); conn.setRequestMethod("GET"); int length = -1; // 獲取文件長度 if (conn.getResponseCode() == HttpStatus.SC_OK) { length = conn.getContentLength(); } if (length <= 0) { return; } File dir = new File(DOWN_PATH); if (!dir.exists()) { dir.mkdir(); } // 在本地文件並設置長度 File file = new File(dir,mfileInfo.getFileName()); // 特殊的輸出流能夠 任任意位置寫入 raf = new RandomAccessFile(file,"rwd"); // 設置本地文件的長度 raf.setLength(length); mfileInfo.setLength(length); mHandler.obtainMessage(MSG_INIT, mfileInfo).sendToTarget(); } catch (Exception e) { // TODO: handle exception } finally { try { // 關閉流操作和網絡連接操作 raf.close(); conn.disconnect(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } super.run(); } } }

下面是DownLoadTask.java的代碼:

package com.example.downloaddemo.services;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.http.HttpStatus;
import com.example.downloaddemo.db.ThreadDAO;
import com.example.downloaddemo.db.ThreadDAOImpl;
import com.example.downloaddemo.enties.FileInfo;
import com.example.downloaddemo.enties.ThreadInfo;
import android.content.Context;
import android.content.Intent;
/**
 * 
 * 下載任務
 * 
 */
public class DownLoadTask {
    private Context mContext = null;
    private FileInfo mFileInfo = null;
    private ThreadDAO mDao = null;
    private int mfinished = 0;
    public boolean isPause = false;
    private int threadCount = 1;// 線程數量
    private List mThreadList = null;// 線程集合方便管理分段下載線程
//使用帶緩存型池子,先查看池中有沒有以前建立的線程,如果有,
//就reuse.如果沒有,就建一個新的線程加入池中
//緩存型池子通常用於執行一些生存期很短的異步型任務,
//能reuse的線程,必須是timeout IDLE內的池中線程,
//缺省timeout是60s,超過這個IDLE時長,線程實例將被終止及移出池。
    public static ExecutorService sExecutorService=Executors.newCachedThreadPool();
    public DownLoadTask(Context mContext, FileInfo mFileInfo, int threadCount) {
        super();
        this.mContext = mContext;
        this.mFileInfo = mFileInfo;
        mDao = new ThreadDAOImpl(mContext);
    }
    public void downLoad() {
        // 讀取數據庫的線程信息
List mThreadInfos =    mDao.getThreads(mFileInfo.getUrl());
        if (mThreadInfos.size() == 0) {
            // 獲取每個線程下載的長度
            int length = mFileInfo.getLength() / threadCount;
            // 創建線程下載信息
            for (int i = 0; i < threadCount; i++) {
            ThreadInfo threadInfo = new ThreadInfo(i, mFileInfo.getUrl(), i* length, (i + 1) * length, 0);
                if (i == threadCount - 1) {
                    threadInfo.setEnd(mFileInfo.getLength());
                }
                // 添加到線程信息集合中
                mThreadInfos.add(threadInfo);
                // 向數據庫中插入線程信息
                mDao.insertThread(threadInfo);
            }
        }
        mThreadList = new ArrayList();
        // 啟動多個線程來下載
        for (ThreadInfo info : mThreadInfos) {
            DownLoad download = new DownLoad(info);
    DownLoadTask.sExecutorService.execute(download);
            mThreadList.add(download);
        }
    }
    /**
     * 判斷下載線程是否都下載完畢
     */
    private synchronized void checkAllThreadsFinished() {
        boolean allFinished = true;
        for (DownLoad download : mThreadList) {
            if (!download.isfinished) {
            allFinished = false;
            break;
            }
        }
        if (allFinished) {
            // 下載完畢刪除線程信息
            mDao.deleteThread(mFileInfo.getUrl());
            // 發送廣播到Activity
            Intent intent = new Intent(DownLoadService.ACTION_FINISH);
            intent.putExtra("fileInfo", mFileInfo);
            mContext.sendBroadcast(intent);
        }
    }
    class DownLoad extends Thread {
        private ThreadInfo mThreadInfo = null;
        public boolean isfinished = false;// 表示線程是否下載完畢
        public DownLoad(ThreadInfo mThreadInfo) {
            super();
            this.mThreadInfo = mThreadInfo;
        }
        @Override
        public void run() {
            // 打開連接
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            InputStream ins = null;
            try {
                URL url = new URL(mThreadInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setRequestMethod("GET");
                int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
                // 設置下載位置
                conn.setRequestProperty("Range",
                        "bytes=" + "-" + mThreadInfo.getEnd());
                // 設置文件寫入位置
                File file = new File(DownLoadService.DOWN_PATH,
                        mFileInfo.getFileName());

                raf = new RandomAccessFile(file, "rwd");
                //移動到指定位置
                raf.seek(start);
                Intent intent = new Intent(DownLoadService.ACTION_UPDATE);
                mfinished += mThreadInfo.getFinished();
                // 開始下載
                if (conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
                    // 讀取數據
                    ins = conn.getInputStream();
                    byte[] buffer = new byte[1024 * 4];
                    int length = -1;
            long time = System.currentTimeMillis();
            while ((length = ins.read(buffer)) != -1) {
                        // 寫入文件
                        raf.write(buffer, 0, length);
                        // 把下載進度發送廣播更新UI
                        // 累加整文件完成進度
                        mfinished += length;
                // 累加每個線程完成的進度mThreadInfo.setFinished(mThreadInfo.getFinished()+ length);
        if (System.currentTimeMillis() - time > 1000) {
    time = System.currentTimeMillis();
    intent.putExtra("finished", mfinished * 100
    / mFileInfo.getLength());
    intent.putExtra("id", mFileInfo.getId());
        mContext.sendBroadcast(intent);
    }
        // 下載暫停保存進度
    if (isPause) {
                            mDao.updateThread(mThreadInfo.getUrl(),
                                    mThreadInfo.getId(),
                                    mThreadInfo.getFinished());
                return;
            }
}
                    isfinished = true;
                    // 檢查下載任務是否完成
                    checkAllThreadsFinished();
                }

            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                try {
                    conn.disconnect();
                    ins.close();
                    raf.close();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            super.run();
        }
    }

}

沒寫之前我在想多線程下載之後如何下載下來的刷數據拼接在一起,後來查看JavaAPI之後使用了用Java的RandomAccessFile操作就可以寫入到指定的位,DownLoadService獲取長度後,就在存儲的位置創建該文件。

HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            try {
                // 連接網絡文件
                URL url = new URL(mfileInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setRequestMethod("GET");
                int length = -1;
                // 獲取文件長度
                if (conn.getResponseCode() == HttpStatus.SC_OK) {
                    length = conn.getContentLength();
                }
                if (length <= 0) {
                    return;
                }
                File dir = new File(DOWN_PATH);
                if (!dir.exists()) {
                    dir.mkdir();
                }
                // 在本地文件並設置長度
                File file = new File(dir, mfileInfo.getFileName());
                // 特殊的輸出流能夠 任任意位置寫入
                raf = new RandomAccessFile(file, "rwd");
                // 設置本地文件的長度
                raf.setLength(length);
                mfileInfo.setLength(length);
                mHandler.obtainMessage(MSG_INIT, mfileInfo).sendToTarget();

在寫入文件時:不同的線程獲取對應的數據寫在對應的文件位置就可以了不存在拼接問題。舉個栗子:
這裡寫圖片描述
T1下載0–5;T2下載6—-10;T3下載11—-15,就可以啦下載完寫到對應得位置即可。
在寫入的時候設置麼每寫入 byte[1024 * 4],通知UI更新進度條,並進行總的進度累加,下載完後在判斷多個線程下載一個文件是不是都下載完成了,如果下載完成了就通知彈出下載完成提示。
核心代碼如下:

// 打開連接
            HttpURLConnection conn = null;
            RandomAccessFile raf = null;
            InputStream ins = null;
            try {
                URL url = new URL(mThreadInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(3000);
                conn.setRequestMethod("GET");
                int start = mThreadInfo.getStart() + mThreadInfo.getFinished();
                // 設置下載位置
                conn.setRequestProperty("Range",
                        "bytes=" + "-" + mThreadInfo.getEnd());
                // 設置文件寫入位置
                File file = new File(DownLoadService.DOWN_PATH,
                        mFileInfo.getFileName());
                raf = new RandomAccessFile(file, "rwd");
                //移動到指定位置
                raf.seek(start);
                Intent intent = new Intent(DownLoadService.ACTION_UPDATE);
                mfinished += mThreadInfo.getFinished();
                // 開始下載
                if (conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
                    // 讀取數據
                    ins = conn.getInputStream();
                    byte[] buffer = new byte[1024 * 4];
                    int length = -1;
                    long time = System.currentTimeMillis();
                    while ((length = ins.read(buffer)) != -1) {
                        // 寫入文件
                        raf.write(buffer, 0, length);
                        // 把下載進度發送廣播更新UI
                        // 累加整文件完成進度
                        mfinished += length;
                        // 累加每個線程完成的進度
                        mThreadInfo.setFinished(mThreadInfo.getFinished()
                                + length);
                        if (System.currentTimeMillis() - time > 1000) {
                            time = System.currentTimeMillis();
                            intent.putExtra("finished", mfinished * 100
                                    / mFileInfo.getLength());
                            intent.putExtra("id", mFileInfo.getId());
                            mContext.sendBroadcast(intent);
                        }
                        // 下載暫停保存進度
                        if (isPause) {
                            mDao.updateThread(mThreadInfo.getUrl(),
                                    mThreadInfo.getId(),
                                    mThreadInfo.getFinished());
                            return;
                        }
                    }
                    isfinished = true;
                    // 檢查下載任務是否完成
                    checkAllThreadsFinished();

線程通過使用線程池來管理
ExecutorService sExecutorService=Executors.newCachedThreadPool();
//使用帶緩存型池子,先查看池中有沒有以前建立的線程,如果有,就reuse.如果沒有,就建一個新的線程加入池中
//緩存型池子通常用於執行一些生存期很短的異步型任務,能reuse的線程,必須是timeout IDLE內的池中線程,缺省timeout是60s,超過這個IDLE時長,線程實例將被終止及移出池。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved