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

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

前言

在 Android 平常開發中,咱們會常常遇到下載文件需求,這裏咱們也能夠用系統自帶的 api DownloadManager 來解決這個問題,固然咱們也能夠本身來寫。在這裏我將教你們如何在 Android 使用多線程下載文件。android

實現原理

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


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


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

具體實現

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 類來表示這些信息app

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;
    }
}

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

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 這個類的性能很是的差,很是的差,很是的差!重要的是說三遍!由於這緣由,我在我司的平板是下載文件的速度很慢很慢!都要哭了!ide


我哭了

固然以後我瞭解了一下 能夠用 RandomAccessFile+ nio 來提高文件的寫入速度!!性能


好啦!如今開始介紹下載類啦this

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)! 這樣就能夠指定下載文件的位置了呢!到了這裏核心的部分已經說完了!url

其餘

上面已經把核心的都說完了,其實還有其餘的能夠說呢:

  1. 實現斷點下載(保存下載的長度,用數據庫或者文件保存均可以)
  2. 多線程下載的管理 (須要讀者實現管理器了)
  3. 能夠把下載這個模塊放到另一個進程中,這樣能夠是主進程更加的流暢。固然這涉及到了進程見通訊的問題啦
  4. 通常下載的時候都會有下載進度條,這裏要注意下更新的頻率的問題,在 listview 中更新太快會形成頁面卡頓的哦!
相關文章
相關標籤/搜索