教你如何在 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
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...