教你如何在 Android 使用多線程下載文件

来源:http://www.cnblogs.com/likeandroid/archive/2016/05/29/5539634.html
-Advertisement-
Play Games

教你如何在 Android 使用多線程下載文件 =============================================== 前言 在 Android 日常開發中,我們會經常遇到下載文件需求,這裡我們也可以用系統自帶的 api 來解決這個問題,當然我們也可以自己來寫。在這裡我將教大 ...


# 教你如何在 Android 使用多線程下載文件

前言

在 Android 日常開發中,我們會經常遇到下載文件需求,這裡我們也可以用系統自帶的 api DownloadManager 來解決這個問題,當然我們也可以自己來寫。在這裡我將教大家如何在 Android 使用多線程下載文件。

實現原理

  1. 獲取目標文件的文件大小
  2. 根據線程的個數以及文件大小來分配每個線程下載文件的大小


    如:文件大小:9M 線程個數:3,那麼每條線程下載的大小為 3M。


    在這裡給出計算公式:blockSize=totalSize%countThread==0?totalSize/countThread:totalSize/countThread+1 ----blockSize 為每個線程下載的大小 totalSize 文件大小 countThread 線程個數
    3.開啟線程下載(這裡要處理比較多的事)

具體實現

1.獲取文件的大小

這一步比較簡單我直接給出代碼:

     URL url = null;
    HttpURLConnection http = null;
    try {
        url = new URL(this.apk_url);
        http = (HttpURLConnection) url
                .openConnection();
        http.setConnectTimeout(5 * 1000);
        http.setReadTimeout(5 * 1000);
        http.setRequestMethod("GET");
        if (http.getResponseCode() == 200) {
            this.filesize = http.getContentLength();//文件大小
        } else {
            this.filesize = -1;
        }
    } catch (Exception e) {
        e.printStackTrace();
        this.filesize = -1;
    } finally {
        http.disconnect();
    }

2.分配線程

既然要對各個線程分配對應的下載大小,我們就有必要知道各個線程對應的信息,那麼我麽先來定義 bean 類來表示這些信息

package com.h.kidbot.download;
public class DownLoadInfo {
    private int threadid;//線程id
    private long startpos;//下載的起始位置
    private long endpos;//下載的結束位置
    private long block;//每條下載的大小
    private long downpos;//該條線程已經下載的大小
    private String downloadurl;//下載地址

    public int getThreadid() {
        return threadid;
    }

    public void setThreadid(int threadid) {
        this.threadid = threadid;
    }

    public long getStartpos() {
        return startpos;
    }

    public void setStartpos(long startpos) {
        this.startpos = startpos;
    }

    public long getEndpos() {
        return endpos;
    }   

    public void setEndpos(long endpos) {
        this.endpos = endpos;
    }

    public long getBlock() {
        return block;
    }

    public void setBlock(long block) {
        this.block = block;
    }

    public long getDownpos() {
        return downpos;
    }

    public void setDownpos(long downpos) {
        this.downpos = downpos;
    }

    public String getDownloadurl() {
        return downloadurl;
    }

    public void setDownloadurl(String downloadurl) {
        this.downloadurl = downloadurl;
    }
}

定義好了這個類我們就可以根據剛纔獲取的文件大小來分配單個線程文件下載的大小了,為了方便起見呢 我就設置一條線程吧!

 for (int i = 0; i < this.threadcount; i++) {
        DownLoadInfo info = new DownLoadInfo();
        long startpos = 0, endpos = 0;
        if (i == this.threadcount - 1) {
            startpos = i * block;
            endpos = this.filesize - 1;
        } else {
            startpos = i * block;
            endpos = (i + 1) * block - 1;
        }
        info.setBlock(block);
        info.setDownpos(0);
        info.setStartpos(startpos);
        info.setEndpos(endpos);
        info.setDownloadurl(this.apk_url);
        info.setThreadid(i);
        DownDbUtils.insert(this.context, info);
        infos.add(info);
        info = null;
  }

得到每條線程對應的數據之後,我們就可以開啟線程啦!下麵的做法我和一般的不一樣 因為我沒有用到 RandomAccessFile 這個類,而是直接用 File + FileOutputStream 這兩個類來實現的,原因呢 我發現 RandomAccessFile 這個類的性能非常的差,非常的差,非常的差!重要的是說三遍!因為這原因,我在我司的平板是下載文件的速度很慢很慢!都要哭了!


我哭了

當然之後我瞭解了一下 可以用 RandomAccessFile+ nio 來提升文件的寫入速度!!


好啦!現在開始介紹下載類啦

public class DownLoadThread extends Thread {
    private String apkurl;//下載地址
    private long startpos;//起始地址
    private long endpos;//結束地址
    private long downpos;//已經下載的大小
    private String apkpath;//保存地址
    private long block;//每塊大小
    private int threadid;//線程ID
    private boolean finish = false; // 是否已經下載完成
    private boolean error = false; // 是否出錯
    private Context context;
    private DownLoader loader;
    private int downstate;//下載狀態
    public static final int PAUSE = 2;//暫停
    public static final int RUNNING = 1;//正在下載
    public static final int STOP = 0;//停止

    public DownLoadThread(Context context, String apkurl, long startpos, long endpos, long          downpos, String apkpath, long block, int threadid, DownLoader loader) {
        this.context = context;
        this.apkurl = apkurl;
        this.startpos = startpos;
        this.endpos = endpos;
        this.downpos = downpos;
        this.apkpath = apkpath;
        this.block = block;
        this.threadid = threadid;
        this.loader = loader;
        this.downstate = RUNNING;
    }

    public DownLoadThread() {
    }

    @Override
    public void run() {
        File file=null;
        FileOutputStream fout=null;
        InputStream in = null;
        if (downpos < block) {
            try {
                URL url = new URL(apkurl);
                HttpURLConnection http = (HttpURLConnection) url
                    .openConnection();
                http.setConnectTimeout(5 * 1000);
                http.setReadTimeout(5 * 1000);
                http.setRequestMethod("GET");
                http.setRequestProperty(
                   "Accept",
                    "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-    shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap,   application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint,  application/msword, */*");
                http.setRequestProperty("Accept-Language", "zh-CN");
                http.setRequestProperty("Referer", url.toString());
                http.setRequestProperty("Charset", "UTF-8");
                http.setRequestProperty("Connection", "Keep-Alive");
                long startPos = startpos + downpos;
                long endPos = endpos;
            http.setRequestProperty("Range", "bytes=" + startPos + "-");// 設置獲取實體數據的範圍
            file=new File(apkpath);
            if (file.length()>0){
                fout=new FileOutputStream(file,true);
            }else{
                fout=new FileOutputStream(file);
            }
            byte[] bytes = new byte[2048];
            int len = 0;
            in = http.getInputStream();

            LogUtils.e("開始");
            while ((len = in.read(bytes, 0, bytes.length)) != -1) {
                if (PAUSE == this.downstate || STOP == this.downstate) {
                    DownDbUtils.update(this.context, this.threadid, this.apkurl, downpos);
                    break;
                }
                fout.write(bytes,0,len);
                downpos += len;//已下載的大小
                this.loader.setDownlength(len);
            }
            DownDbUtils.update(this.context, this.threadid, this.apkurl, downpos);
            if (!DeviceUtils.isNet(context)) {
                this.finish = false;
                this.downstate=PAUSE;
            } else {
                this.finish = true;
            }
             } catch (Exception e) {
                DownDbUtils.update(this.context, this.threadid, this.apkurl, downpos);
                LogUtils.e(e.toString());
                e.printStackTrace();
                downpos = -1;
                this.error = true;
                this.finish = false;
        } finally {
            closeIO(in,fout);
        }
    }
}
//得到每條線程已經下載的大小

public long getDownpos() {
    return downpos;
}

public int getDownstate() {
    return downstate;
}

public void setDownstate(int downstate) {
    this.downstate = downstate;
}

//是否下載完成
public boolean isFinish() {
    return finish;
}

public void setFinish(boolean finish) {
    this.finish = finish;
}

//是否下載出錯
public boolean isError() {
    return error;
}

    public void setError(boolean error) {
        this.error = error;
    }

    public void setDownpos(long downpos) {
        this.downpos = downpos;
    }

//關閉流
 public static void closeIO(Closeable... closeables) {
    if (null == closeables || closeables.length <= 0) {
        return;
    }
    for (Closeable cb : closeables) {
        try {
            if (null == cb) {
                continue;
            }
            cb.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
}

這裡大家得瞭解下 http 中這個 Range 這個欄位的含義:用戶請求頭中,指定第一個位元組的位置和最後一個位元組的位置,如(Range:200-300)! 這樣就可以指定下載文件的位置了呢!到了這裡核心的部分已經說完了!

其他

上面已經把核心的都說完了,其實還有其他的可以說呢:

  1. 實現斷點下載(保存下載的長度,用資料庫或者文件保存都可以)
  2. 多線程下載的管理 (需要讀者實現管理器了)
  3. 可以把下載這個模塊放到另外一個進程中,這樣可以是主進程更加的流暢。當然這涉及到了進程見通信的問題啦
  4. 一般下載的時候都會有下載進度條,這裡要註意下更新的頻率的問題,在 listview 中更新太快會造成頁面卡頓的哦!

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一.js的數據類型和變數 JavaScript 有六種數據類型。主要的類型有 number、string、object 以及 Boolean 類型,其他兩種類型為 null 和 undefined。 String 字元串類型:字元串是用單引號或雙引號來說明的。(使用單引號來輸入包含引號的字元串。)如 ...
  • 最近除了做業務,也在嘗試學習h5和移動端,在這個過程中,學到了很多,利用h5和canvas做了一個愛心魚的小游戲。 "點這裡去玩一下" PS: 貌似有點閃屏,親測多刷新兩下就好了==。代碼在本地跑都不會閃,放到博客里就閃了,我也不知道為什麼。。。回頭我再看看是什麼問題。 另外,我把代碼放到githu ...
  • 學習要點: 1.搜索區 2.插入大圖 3.搜索框 主講教師:李炎恢 本章主要開始使用學慣用 HTML5 和 CSS3 來構建 Web 頁面,第一個項目採用 PC 端固定佈局來實現。 一.搜索區 本節課,我們接著 header 頭部往下,來設計一塊搜索區。這個區域,可以是廣告大圖,也可以是用戶註冊,也 ...
  • 效果:http://hovertree.com/texiao/jquery/71/代碼如下: 轉自:http://hovertree.com/h/bjaf/n781jmfy.htm 特效彙總:http://www.cnblogs.com/roucheng/p/texiao.html ...
  • 大圖:http://images2015.cnblogs.com/blog/730765/201605/730765-20160529113743209-72994369.png ...
  • 1. PCH文件概述 PCH文件是一種預編譯頭文件(一般擴展名為.PCH),是把一個工程中較穩定的代碼預先編譯好放在一個文件(.PCH)里。這些預先編譯好的代碼可以是任何的C/C++代碼--甚至可以是inline函數,只它們在整個工程中是較為穩定的,即在工程開發過程中不會經常被修改的代碼。 在 Xc ...
  • 看了很多別人寫的安卓SQlite數據的操作代碼,都是浮雲,瞎弄!一點也不通俗易懂,我覺得我寫的不錯,而且安卓項目也用上了,所以在博客園裡保存分享一下! 一SQLiteHelper類是自動重載增刪改查函數的,另外一個是自己定義的類,用Context傳值。我用的是Fragment,用Activity的話 ...
  • 此前編譯過Android4.4的源碼,但是現在Android都到了7.0的版本,不禁讓我感嘆Google的步伐真心難跟上,趁這周周末時間比較充裕,於是在過去的24小時里,毅然花了9個小時編譯了一把Android6.0的源碼,但是昨天編譯完之後已經很晚了,沒來得及記錄編譯的步驟,今天才慢悠悠地來記錄一 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...