android多線程斷點續傳下載

來源:互聯網
上載者:User

標籤:android   多線程   斷點續傳   

最近在研究下載檔案的斷點續傳,主要是能夠記錄下載位置,退出應用下次再進來的時候也能從當前的位置開始下載,代碼為多線程+資料庫,代碼能夠正常運行,但是我在開發的過程中遇到了幾個問題,有的沒找到解決方案,分享出來,希望有的大神能夠指點一下:

1.使用HttpURLConnection 擷取檔案大小的時候,速度在4.x手機上非常慢,但是找了許多中文網站上的代碼基本都沒有解決,後來google一下,發現添加conn.setRequestMethod("HEAD")就可以了,擷取包頭就可以了,這樣conn.disconnect()關閉串連就能夠快很多了


2.同樣的使用HttpURLConnection下載,使用者暫停下載或者下載完成conn.disconnect()的調用有時候會花費十幾秒的時間,這個找了很久,4.X的bug,解決方案有,但是挺麻煩的,google一下就能找到,我也去參考了其他軟體的斷點續傳,發現也有這問題,不知道有沒有方便一點的解決方案,望大神解答


3.RandomAccessFile的多線程低效率問題,看網上說在低配置手機上RandomAccessFile的效率低,網路支援3M/s的下載,但是讀寫速度最多隻有1M/s,那麼不管網速多快,下載速度就是1M/s,不知道能不能找到一個代替的方案

4.非常偶現的情況,下載一個超過100M的檔案,有時候會莫名其妙的下載到90多M,下載線程HttpURLConnection就自動關閉了連結,但是我只是偶現了一兩次,不知道是不是我代碼的問題,估計是吧

代碼我貼出來吧,主體就是一個類,附加一個資料庫類,主要是線程之間的互動吧,有一個開始線程,主要功能是當資料庫中沒有該下載檔案資訊的時候去建立,如果有,就去資料庫中擷取,並且開始數個下載線程開始下載檔案;當使用者暫停時候會啟動停止線程去結束開始線程(如果沒有執行完成)和下載線程,這時候第2個問題就會出現,關閉下載線程時,4.x的手機關閉http串連的速度慢的驚人
RandomAccessFile類

package com.android.libcore.download;import android.os.Handler;import android.os.Message;import com.android.libcore.utils.FileUtils;import java.io.File;import java.io.InputStream;import java.io.RandomAccessFile;import java.lang.ref.WeakReference;import java.net.HttpURLConnection;import java.net.URL;import java.util.ArrayList;import java.util.HashMap;/** * Description: 單個檔案下載,支援斷點續傳 * * @author zzp([email protected]) * @since 2015-08-05 */public class RandomAccessFile {    /** 下載狀態,正在擷取檔案大小 */    public static final int STATE_GETSIZE = 1;    /** 下載狀態,開始下載 */    public static final int STATE_STARTING = 2;    /** 下載狀態,正在停止 */    public static final int STATE_STOPING = 3;    /** 下載狀態,停止成功 */    public static final int STATE_STOPED = 4;    /** 下載狀態,下載完成 */    public static final int STATE_FINISH = 5;    /** 當前檔案的下載狀態,預設為停止成功,即為下載完成,且隨時可以開始下載 */    private static int currentState = STATE_STOPED;    /** 下載一個檔案所開啟的線程數量 */    private final int THREAD_NUM = 4;    /** 下載一個檔案的所有線程資訊 */    private ArrayList<DownloadInfo> infos;    /** 開始下載線程 */    private Thread startDownloadThread;    /** 結束下載線程,用來檢測下載線程的完成程度,更新狀態 */    private Thread stopDownloadThread;    /** 下載一個檔案的線程隊列 */    private ArrayList<DownloadThread> threads;    /** 更新下載資訊,更新介面線程 */    private UpdateThread updateThread;    /** 資料庫操作對象 */    private DownloadDBHelper helper;    /** 該檔案下載的url */    private String url;    /** 該檔案下載路徑,預設為SD卡file目錄 */    private String path = FileUtils.getExternalStorageFilePath();    /** 該檔案下載的檔案大小 */    private long fileSize = 0;    /** 該檔案下載的完成度 */    private long completeSize = 0;    /** 通知更新進度handler */    private ProgressChangeHandler progressChangeHandler;    /** 檔案下載進度更新 */    private IDownloadProgressChangedListener listener;    /** 檔案的下載狀態 */    private boolean downloadState = false;    /** 檔案是否下載完成 */    private boolean isDownloadFinish = false;    /**     * @param url 檔案下載url     * @param fileName 檔案名稱     */    public FileDownloadManager(String url, String fileName){        this(url, fileName, null);    }    /**     * @param url 檔案下載url     * @param fileName 檔案名稱     * @param path 檔案下載路徑,不需要帶檔案名稱     */    public FileDownloadManager(String url, String fileName, String path){        this.url = url;        if (path != null)            this.path = path;        if (!this.path.substring(this.path.length()-1).equals("/")){            this.path += "/";        }        this.path += fileName;        helper = new DownloadDBHelper();        infos = new ArrayList<>();        threads = new ArrayList<>();        progressChangeHandler = new ProgressChangeHandler(this);        checkFileFinish();    }    /**     * 檢測該檔案是否已經下載完成     */    public boolean checkFileFinish(){        if (isDownloadFinish || isFileDownloadFinish(helper.getInfo(url))){            isDownloadFinish = true;            progressChangeHandler.sendEmptyMessage(STATE_FINISH);            return true;        }        return false;    }    /**     * 開啟下載     */    public void start(){        if (checkFileFinish()){            return;        }        if (downloadState){            return;        }        if (currentState == STATE_STOPING){            return;        }        downloadState = true;        //開啟下載任務        startDownload(helper.getInfo(url));    }    /**     * 停止下載     */    public void stop(){        downloadState = false;        //停止更新介面線程,一定要保證最多隻有一個更新線程在執行        if (updateThread != null && updateThread.isAlive())            updateThread.canRun = false;        if (checkFileFinish()){            return;        }        if (currentState == STATE_STOPING){            return;        }        if (currentState == STATE_STOPED){            return;        }        stopDownload();    }    private void startDownload(final ArrayList<HashMap<String, String>> maps){        startDownloadThread = new Thread(new Runnable() {            @Override            public void run() {                //如果沒有下載資訊,則需要建立                if (infos==null || infos.size()==0) {                    if (maps == null || maps.size() == 0) {                        createDownloadInfos();                    } else {                        revertDownloadInfos(maps);                    }                }                //更新檔案狀態為正在下載                progressChangeHandler.sendEmptyMessage(STATE_STARTING);                //上次的線程完成之後才能開啟新的下載線程開始下載                threads.clear();                for (DownloadInfo info : infos){                    DownloadThread thread = new DownloadThread(info);                    threads.add(thread);                }                progressChangeHandler.sendEmptyMessage(-1);            }        });        startDownloadThread.start();    }    private void stopDownload(){        stopDownloadThread = new Thread(new Runnable() {            @Override            public void run() {                //如果開始線程還未停止,比如使用者摁完開始下載之後快速摁停止下載,                // 這時狀態更新為正在停止下載,直到開始線程完成                boolean state = (startDownloadThread!=null&&startDownloadThread.isAlive());                while (state){                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    progressChangeHandler.sendEmptyMessage(STATE_STOPING);                    state = (startDownloadThread!=null&&startDownloadThread.isAlive());                }                //接著開始檢測下載線程,確保下載線程要全部執行完成                state = threads.size()>0;                while(state){                    state = false;                    for (DownloadThread thread : threads){                        if (thread.isAlive()){                            state = true;                            break;                        }                    }                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    //還有線程在執行,所以狀態還為正在停止中                    progressChangeHandler.sendEmptyMessage(STATE_STOPING);                }                //確保開始線程和下載線程都已經執行完成之後才能將狀態修改為停止成功                progressChangeHandler.sendEmptyMessage(STATE_STOPED);            }        });        stopDownloadThread.start();    }    /**     * 第一次下載檔案,無下載記錄,重新建立     */    private void createDownloadInfos(){        try {            //更新狀態為正在擷取檔案大小            progressChangeHandler.sendEmptyMessage(STATE_GETSIZE);            URL url = new URL(FileDownloadManager.this.url);            HttpURLConnection conn = (HttpURLConnection) url.openConnection();            conn.setConnectTimeout(5 * 1000);            conn.setRequestMethod("GET");            //添加這句話,要不然disconnect()會花費很多時間            conn.setRequestMethod("HEAD");            conn.setAllowUserInteraction(true);            conn.connect();            if (conn.getResponseCode()==200) {                fileSize = conn.getContentLength();                File file = FileUtils.checkAndCreateFile(path);                // 本地訪問檔案                RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");                accessFile.setLength(fileSize);                accessFile.close();            }            conn.disconnect();        } catch (Exception e) {            e.printStackTrace();            return;        }        //開始計算每個線程下載的位元組範圍        long startPos = 0;        //最後一個線程所下載的位元組數一定要小於等於前面的線程保證檔案完整性        long perSize = (long) Math.ceil((fileSize*1.0) / (THREAD_NUM*1.0));        for (int i=0; i<THREAD_NUM; i++){            DownloadInfo info = new DownloadInfo();            info.id = i;            info.startPos = startPos;            startPos += perSize;            startPos -= 1;            if (startPos >= fileSize)                startPos = fileSize-1;            info.endPos = startPos;            info.completeSize = 0;            //下一個任務的開始位置要+1            startPos ++;            infos.add(info);        }        helper.insertInfos(url, infos);    }    /**     * 恢複以前的下載資訊     */    private void revertDownloadInfos(ArrayList<HashMap<String, String>> maps){        ArrayList<String> columns = DownloadDB.TABLES.DOWNLOAD.getTableColumns();        for (HashMap<String, String> map : maps){            DownloadInfo info = new DownloadInfo();            info.id = Integer.parseInt(map.get(columns.get(0)));            info.startPos = Long.parseLong(map.get(columns.get(2)));            info.endPos = Long.parseLong(map.get(columns.get(3)));            info.completeSize = Long.parseLong(map.get(columns.get(4)));            completeSize += info.completeSize;            fileSize = fileSize>info.endPos ? fileSize:info.endPos;            infos.add(info);        }    }    /**     * 擷取該檔案的總下載位元組數     */    private long getCompleteSize(){        completeSize = 0;        for (DownloadThread thread : threads)            completeSize += thread.getCompleteSize();        return completeSize;    }    /**     * 檢測該檔案是否下載完成     */    private boolean isFileDownloadFinish(ArrayList<HashMap<String, String>> maps){        boolean result = true;        if (maps==null || maps.size() == 0){            return false;        }        ArrayList<String> columns = DownloadDB.TABLES.DOWNLOAD.getTableColumns();        for (HashMap<String, String> map : maps){            //如果完成位元組數不足end-start,代表該線程未完成,所以需要繼續下載            if (Long.parseLong(map.get(columns.get(4))) <                    (Long.parseLong(map.get(columns.get(3)))- Long.parseLong(map.get(columns.get(2))))){                result = false;                break;            }        }        return result;    }    public void setListener(IDownloadProgressChangedListener listener){        this.listener = listener;    }    /**     * 下載進度更新介面     */    public interface IDownloadProgressChangedListener{        void onProgressChanged(long completeSize, long totalSize);        void onStateChanged(int state);    }    /**     * 開啟更新介面線程和開啟下載線程     */    private void startThreads(){        //準備開啟線程        updateThread = new UpdateThread();        updateThread.start();        for (Thread thread : threads)            thread.start();    }    private void finishDownload(){        downloadState = false;        isDownloadFinish = true;        progressChangeHandler.sendEmptyMessage(STATE_FINISH);        if (updateThread!=null && updateThread.isAlive())            updateThread.canRun = false;    }    private static class ProgressChangeHandler extends Handler{        private WeakReference<FileDownloadManager> activityWeakReference;        public ProgressChangeHandler(FileDownloadManager manager){            activityWeakReference = new WeakReference<>(manager);        }        @Override        public void handleMessage(Message msg) {            if (msg.what == -1){                activityWeakReference.get().startThreads();            }            //下載進度更新            else if (msg.what == 0) {                if (activityWeakReference.get().getCompleteSize() >= activityWeakReference.get().fileSize) {                    activityWeakReference.get().finishDownload();                }                if (activityWeakReference.get().listener != null)                    activityWeakReference.get().listener.onProgressChanged                            (activityWeakReference.get().getCompleteSize(), activityWeakReference.get().fileSize);            }            //下載狀態更新            else {                if (currentState != msg.what){                    currentState = msg.what;                    if (activityWeakReference.get().listener != null)                        activityWeakReference.get().listener.onStateChanged(currentState);                }            }        }    }    /**     * 單獨一個線程下載的資訊     */    public class DownloadInfo {        public int id;        public long startPos;        public long endPos;        public volatile long completeSize;    }    /**     * 下載線程     */    private class DownloadThread extends Thread{        private DownloadInfo info;        public DownloadThread(DownloadInfo info){            this.info = info;        }        public long getCompleteSize(){            return info.completeSize;        }        @Override        public void run() {            HttpURLConnection connection = null;            RandomAccessFile randomAccessFile = null;            InputStream is = null;            try {                URL url = new URL(FileDownloadManager.this.url);                connection = (HttpURLConnection) url.openConnection();                connection.setConnectTimeout(2000);                connection.setRequestMethod("GET");                connection.setAllowUserInteraction(true);                // 設定範圍,格式為Range:bytes x-y;                connection.setRequestProperty("Range", "bytes=" + (info.startPos + info.completeSize) + "-" + info.endPos);                randomAccessFile = new RandomAccessFile(path, "rwd");                randomAccessFile.seek(info.startPos + info.completeSize);                // 將要下載的位元組寫到上次寫的末尾                is = connection.getInputStream();                byte[] buffer = new byte[1024 * 8];                int length;                while ((length = is.read(buffer)) != -1) {                    randomAccessFile.write(buffer, 0, length);                    info.completeSize += length;                    if (!downloadState) {                        break;                    }                }            } catch (Exception e) {                e.printStackTrace();            } finally {                try {                    long time = System.currentTimeMillis();                    //android 4.x disconnect或者close會耗費很長的時間,解決了很長時間,暫未找到方法,有的聯絡我                    is.close();                    randomAccessFile.close();                    connection.disconnect();                } catch (Exception e) {                    e.printStackTrace();                }            }        }    }    /**     * 更新資料庫和介面線程     */    private class UpdateThread extends Thread{        public boolean canRun = true;        @Override        public void run() {            try {                while (canRun) {                    // 更新資料庫中的下載資訊                    helper.updateInfos(url, infos);                    //更新介面                    progressChangeHandler.sendEmptyMessage(0);                    //每隔1秒操作資料庫和更新介面,防止頻繁的更新                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }catch (Exception e){                e.printStackTrace();            }        }    }}

戳我下載GitHub整個項目下載,小弟不才寫的一個android 架構,裡面也有相關代碼


著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

android多線程斷點續傳下載

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.