android多線程斷點續傳下載

jopen 9年前發布 | 4K 次閱讀 Java 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(zhao_zepeng@hotmail.com) 
 * @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();  
            }  
        }  
    }  
}  </pre> 


GitHub整個項目下載,小弟不才寫的一個android 框架,里面也有相關代碼
 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!