天天看點

Android實作網絡下載下傳二(多任務下載下傳--支援斷點續傳)

Android實作網絡下載下傳二(多任務下載下傳–支援斷點續傳)

上文中說了單任務的斷點續傳,這篇文章就說說多任務下載下傳,不啰嗦了,直接進入正題。

附上demo源碼,GitHub代碼後續上傳,這裡的連結還是csdn的。

點這裡下載下傳源碼,快,戳我戳我…

q:486789970

email:[email protected]

下圖是一個多任務下載下傳的動态圖:

效果圖如下(單任務下載下傳在上篇文章)https://blog.csdn.net/qq_35840038/article/details/90239354

在上篇部落格基礎上,新增了一個ListView用來顯示多條下載下傳内容,這個很簡單,這裡就不多說啦

Android實作網絡下載下傳二(多任務下載下傳--支援斷點續傳)

.上面的效果圖就是多任務下載下傳(使用線程池管理),支援斷點續傳、實時進度更新、下載下傳暫停、下載下傳繼續,下載下傳完成自動安裝等功能;同時包括網絡下載下傳請求和本地檔案的存儲。

實作原理

  • 首先通過Service裡面的代碼獲得下載下傳檔案的長度,然後設定本地檔案的長度
  • 根據檔案長度和線程數計算每條線程下載下傳的資料長度和下載下傳位置(這裡用了三條線程,當然也可改成讓使用者輸入線程數)
  • 檔案的長度為6M,線程數為3,那麼,每條線程下載下傳的資料長度為2M

例如10M大小,使用3個線程來下載下傳

具體的流程在上篇文章中說過了,多任務的隻是修改了一點東西而已。直接看修改的代碼:

Service

package com.cc.downloaddemo.services;

import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.annotation.Nullable;
import com.cc.downloaddemo.info.FileInfo;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 下載下傳service
 */
public class DownloadService extends Service {

    //通過URL位址下載下傳apk檔案并儲存在本地存儲路徑
    public static final String DOWNLOAD_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";

    //定義handler的flag
    public static final int MSG_INIT = 0;

    //異步下載下傳任務集合(設定為map集合,查找友善一些)
    private Map<Integer, DownloadTask> taskMap = new LinkedHashMap<>();

    /**
     * 執行下載下傳、暫停、繼續下載下傳
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //擷取從Activity傳過去的參數,判斷intent的值。
        if(ACTION_START.equals(intent.getAction())){
            FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
            //啟動初始化子線程,聯網下載下傳檔案内容。
            InitThread initThread = new InitThread(fileInfo);
            //使用線程池來管理
            DownloadTask.executorService.execute(initThread);
        } else if (ACTION_STOP.equals(intent.getAction())) {
            //暫停下載下傳時,更改下載下傳任務類的flag便會自動暫停。
            FileInfo fileInfo = (FileInfo) intent.getSerializableExtra("fileInfo");
            //從集合中取出下載下傳任務
            DownloadTask task = taskMap.get(fileInfo.getId());
            //非空處理
            if(task != null){
                //停止下載下傳任務
                task.ispause = true;
            }
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 網絡内容下載下傳完成後
     * 通過handler啟動異步任務類開始正式下載下傳檔案。
     */
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //是否為初始化消息
            if(msg.what == MSG_INIT){
                //擷取發回來的資訊
                FileInfo fileInfo = (FileInfo) msg.obj;
                //啟動一個下載下傳任務,将檔案内容傳遞過去。
                //一個任務讓三個線程去下載下傳
                DownloadTask downloadTask = new DownloadTask(DownloadService.this, fileInfo, 3);
                //啟動下載下傳方法
                downloadTask.download();
                //把下載下傳任務添加到集合中
                taskMap.put(fileInfo.getId(), downloadTask);
            }
        }
    };

    /**
     * 定義子線程用來下載下傳
     */
    class InitThread extends Thread{

        //定義一個檔案用來接收下載下傳資訊
        private FileInfo mFileInfo = null;

        //啟動線程時傳入的對象
        public InitThread(FileInfo mFileInfo) {
            this.mFileInfo = mFileInfo;
        }

        @Override
        public void run() {
            //定義conn
            HttpURLConnection conn = null;
            //定義檔案内容通路類
            RandomAccessFile raf = null;
            try {
                //連接配接網絡檔案
                URL url = new URL(mFileInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(3000);
                conn.setRequestMethod("GET");

                //自定義一個長度,注意盡量用long(處理下載下傳消息進度百分比時好計算)
                long length = -1;
                //下載下傳完成
                if(conn.getResponseCode() == HttpURLConnection.HTTP_OK ){
                    //擷取檔案總長度指派給length
                    length = conn.getContentLength();
                }
                //長度不能小于0
                if(length <= 0){
                    return;
                }
                //判斷該路徑存在與否
                File dir = new File(DOWNLOAD_PATH);
                //檔案不存在時建立
                if(!dir.exists()){
                    dir.mkdir();
                }
                //本地建立一個檔案
                File file = new File(dir, mFileInfo.getFilename());
                //輸出流
                raf = new RandomAccessFile(file, "rwd");
                //設定本地的檔案長度(等于下載下傳檔案的總長度。杜絕浪費資源)
                raf.setLength(length);
                //指派給定義的對象長度
                mFileInfo.setLength(length);
                //啟動flag,将檔案發送給handler處理
                handler.obtainMessage(MSG_INIT, mFileInfo).sendToTarget();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                try {
                    //關閉資源
                    raf.close();
                    conn.disconnect();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

}

           
注:使用了線程池統一管理
           

DownloadTask:

package com.cc.downloaddemo.services;

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import com.cc.downloaddemo.db.dao.ThreadDao;
import com.cc.downloaddemo.db.impl.ThreadDaoImpl;
import com.cc.downloaddemo.info.FileInfo;
import com.cc.downloaddemo.info.ThreadInfo;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 任務下載下傳類
 * @author
 */
public class DownloadTask {

    //上下文
    private Context context;

    //定義資料庫線程對象檔案
    private FileInfo fileInfo;

    //執行個體化接口
    public static ThreadDao threadDao = null;

    //定義finished(目前已下載下傳的長度)
    private int finished = 0;

    //預設直接下載下傳
    public boolean ispause = false;

    //預設線程數量
    private int threadCount = 1;

    //定義線程集合
    private List<DownloadThread> threadList;

    //定義線程池
    public static ExecutorService executorService = Executors.newCachedThreadPool();

    /**
     * 構造方法
     * @param context
     * @param fileInfo
     */
    public DownloadTask(Context context, FileInfo fileInfo, int threadCount) {
        this.context = context;
        //拿到handler傳過來的檔案
        this.fileInfo = fileInfo;
        //拿到線程數量
        this.threadCount = threadCount;

        //執行個體化接口實作類
        threadDao = new ThreadDaoImpl(context);
    }

    /**
     * 下載下傳方法
     */
    public void download(){
        //下載下傳任務一開始,先查詢資料庫,看是否有檔案在等待下載下傳。
        //讀取資料庫的所有線程資訊
        List<ThreadInfo> threads = threadDao.getThreads(fileInfo.getUrl());
        //當list的size為0時,說明沒有等待下載下傳的線程,為1時則有。
        if(threads.size() == 0){
            //獲得每一個檔案
            int length = (int) (fileInfo.getLength() / threadCount);
            for (int i = 0; i < threadCount; i++){
                //建立線程資訊
                ThreadInfo threadInfo = new ThreadInfo(i, fileInfo.getUrl()
                        , length * i, (i + 1) * length - 1, 0);
                //當i是最後一個線程時,設定一個索引
                if(i == threadCount - 1){
                    threadInfo.setEnd((int) fileInfo.getLength());
                }
                //添加到線程資訊集合中
                threads.add(threadInfo);
                threadDao.insertThread(threadInfo);
            }
        }
        threadList = new ArrayList<>();
        //啟動多個線程進行下載下傳
        for (ThreadInfo info : threads){
            DownloadThread downloadThread = new DownloadThread(info);
//            downloadThread.start();
            DownloadTask.executorService.execute(downloadThread);
            //添加線程到集合中
            threadList.add(downloadThread);
        }
    }

    /**
     * 判斷是否所有線程都執行完畢
     * 保證同一時間段隻有一個線程通路此方法
     */
    private synchronized void checkAddThreadFinished(){
        //假設全部完成啦
        boolean allFinished = true;

        //周遊集合
        for (DownloadThread thread : threadList){
            if(!thread.isFinished){
                allFinished = false;
                break;
            }
        }
        if(allFinished){
            //下載下傳完成,删除資料庫所儲存的線程資訊
            threadDao.deleteThread(fileInfo.getUrl());
            //發送廣播通知,消災任務結束
            Intent intent = new Intent(DownloadService.ACTION_FINISH);
            intent.putExtra("fileInfo", fileInfo);
            context.sendBroadcast(intent);
        }
    }

    /**
     * 下載下傳線程
     */
    class DownloadThread extends Thread{

        //繼續定義一個臨時線程對象
        private ThreadInfo threadInfo;

        //标記線程是否結束
        public boolean isFinished = false;

        //構造
        public DownloadThread(ThreadInfo threadInfo) {
            this.threadInfo = threadInfo;
        }

        @Override
        public void run() {
            //定義conn、輸入流和檔案内容通路類
            HttpURLConnection conn = null;
            InputStream inputStream = null;
            RandomAccessFile raf = null;

            try{
                //開始聯網下載下傳
                URL url = new URL(threadInfo.getUrl());
                conn = (HttpURLConnection) url.openConnection();
                conn.setReadTimeout(3000);
                conn.setRequestMethod("GET");

                //設定下載下傳位置(通過目前開始值和現在值判斷下載下傳位置)
                int start = threadInfo.getStart() + threadInfo.getFinished();
                conn.setRequestProperty("Range", "bytes=" + start + "-" + threadInfo.getEnd());
                //設定檔案寫入位置
                File file = new File(DownloadService.DOWNLOAD_PATH, fileInfo.getFilename());
                raf = new RandomAccessFile(file, "rwd");
                raf.seek(start);
                //定義一個廣播,用來更新下載下傳進度
                Intent intent = new Intent(DownloadService.ACTION_UPDATE);

                //
                finished += threadInfo.getFinished();
                //開始下載下傳
                if(conn.getResponseCode() == HttpURLConnection.HTTP_PARTIAL){
                    //讀取資料
                    inputStream = conn.getInputStream();
                    byte[] buffer = new byte[1024 * 4];
                    int len = -1;
                    long time = System.currentTimeMillis();
                    while ((len = inputStream.read(buffer)) != -1){
                        //寫入檔案
                        raf.write(buffer, 0, len);
                        //累加整個檔案的下載下傳完成進度
                        finished += len;
                        //累加每個線程完成的進度
                        threadInfo.setFinished(threadInfo.getFinished() + len);
                        //間隔500毫秒更新一下進度
                        if(System.currentTimeMillis() - time > 1500){
                            time = System.currentTimeMillis();
                            intent.putExtra("finished", (int)(finished / (float)fileInfo.getLength() * 100));
                            intent.putExtra("id", fileInfo.getId());
                            context.sendBroadcast(intent);
                        }
                        //在下載下傳暫停時,儲存下載下傳進度
                        if(ispause){
                            threadDao.updateThread(threadInfo.getUrl(), threadInfo.getId(), threadInfo.getFinished());
                            return;
                        }
                    }
                    intent.putExtra("finished", 100);
                    context.sendBroadcast(intent);
                    //辨別線程執行完畢
                    isFinished = true;
                    //執行完之後檢查
                    checkAddThreadFinished();
                    openFile(file);
                }
            }catch (Exception e){
                e.printStackTrace();;
            }finally {
                conn.disconnect();
                try {
                    inputStream.close();
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 自動安裝
     * @param file
     */
    private void openFile(File file) {
        // TODO Auto-generated method stub
        Log.e("OpenFile", file.getName());
        Intent intent = new Intent();
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(android.content.Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(file),
                "application/vnd.android.package-archive");
        context.startActivity(intent);
    }

}

           

附上demo源碼,GitHub代碼後續上傳,這裡的連結還是csdn的。

點這裡下載下傳源碼,快,戳我戳我…

q:486789970

email:[email protected]

如果有什麼問題,歡迎大家指導。并互相聯系,希望能夠通過文章互相學習。

---财财親筆