Android 基於OkHttp的下載,支援https,斷點下載,優化下載速度
package com.lenovo.smarthcc.http.okhttp; import android.text.TextUtils; import com.lenovo.smarthcc.http.Listenner; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.util.Date; import java.util.HashMap; import java.util.Map; import okhttp3.Call; import okhttp3.Headers; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okio.Buffer; import okio.Okio; import okio.Sink; import okio.Source; /** * <p>Author: shijiale</p> * <p>Date: 2017/3/14.<p> * <p>Describe: */ public class BRDownloadThread extends Thread { private static OkHttpClient mClient; //okhttp client private Builder mBuilder; //配置 private boolean isExit = false; //是否退出下載 private long mStartTime = 0; //當前這次下載起始時間 private long mDownloadLength; //當前這次下載的長度 private final int MAX_BUFF_SIZE = 4 * 1024; //每次傳送的位元組(這個數值可能沒有什麼作用,因為大部分http流都會做快取) private Call mCall; //當前請求 private DaemonThead mDaemonMsgThread; //用於進度回撥的執行緒 /*** * 退出當前下載執行緒 */ public synchronized void exit() { isExit = true; } private BRDownloadThread() { mClient = OkHttpProxy.getClient(); if (mDaemonMsgThread == null) { mDaemonMsgThread = new DaemonThead(); mDaemonMsgThread.isRunning = true; } } @Override public void run() { super.run(); try { downloadFile(); } catch (Exception e) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, e.getMessage()); } finally { if (mDaemonMsgThread != null) { mDaemonMsgThread.isRunning = false; mDaemonMsgThread = null; } } } /*** * 自動修改下載的檔名稱 * @param fileLength 檔案在伺服器的大小 */ private void autoChangeFileName(long fileLength) { File file = new File(mBuilder.mSavePath); if(file != null && file.exists() && file.length() == fileLength) { String path = mBuilder.mSavePath; path = path.substring(0,path.lastIndexOf(".") + 1); String expresion = mBuilder.mSavePath.substring(path.lastIndexOf(".") + 1,mBuilder.mSavePath.length()); path += "(" + new Date(System.currentTimeMillis()).toLocaleString() + ")" + expresion; mBuilder.mSavePath = path; } } private boolean chekDirectory() { String path = mBuilder.mSavePath; path = path.substring(0,path.lastIndexOf(File.separator) + 1); File file = new File(path); if(file != null && file.exists()) { return true; } if(file == null || !file.exists()) { boolean mkdirs = file.mkdirs(); if(!mkdirs) { return false; } } return true; } /*** * 下載檔案 * @throws IOException */ private void downloadFile() throws IOException { if(!chekDirectory()) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR,mBuilder.mSavePath,"directory create failed!"); return; } //獲取檔案長度 mBuilder.mFileAllLength = getNetFileSize(); //如果允許重複下載,則對檔案進行重新命名,修改後的檔名會會在 onStop onSuccess中體現 if(mBuilder.isAutoRename) { autoChangeFileName(mBuilder.mFileAllLength); } RandomAccessFile file = new RandomAccessFile(mBuilder.mSavePath, "rw"); //如果本地已經存在該檔案,並且大小和伺服器相同則返回下載成功 if (file != null && file.length() == mBuilder.mFileAllLength && mBuilder.mFileAllLength != 0) { mBuilder.mSuccessListenner.onChanged(Listenner.STATUS_SUCCESS, mBuilder.mSavePath,mBuilder.mFileAllLength + ""); return; } //設定檔案起始指標 if (file != null && file.length() != 0) { if (mBuilder.mStartOffset > 0 && mBuilder.mStartOffset < file.length()) { file.seek(mBuilder.mStartOffset); } else { file.seek(file.length() - 1); mBuilder.mStartOffset = file.length() - 1; } } //設定請求引數 callRequest(mBuilder.mStartOffset); if (mCall == null) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR!"); return; } //建立連線 Response res = mCall.execute(); if (!res.isSuccessful()) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR!"); return; } //更新檔案長度 mBuilder.mLengthChangeListenner.onChanged(Listenner.STATUS_LENGTH_CHANGE, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + ""); //開啟進度重新整理執行緒 mDaemonMsgThread.start(); //儲存檔案 boolean ret = saveData(res.body().byteStream(), new FileOutputStream(file.getFD())); if (ret) { mBuilder.mSuccessListenner.onChanged(Listenner.STATUS_SUCCESS, mBuilder.mSavePath,mBuilder.mFileAllLength + ""); } else { mBuilder.mStopListenner.onChanged(Listenner.STATUS_STOP, mBuilder.mSavePath, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + ""); } mBuilder.mLengthChangeListenner.onChanged(Listenner.STATUS_LENGTH_CHANGE, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + ""); } /*** * 獲取檔案在伺服器的長度,head請求 * @return * @throws IOException */ private long getNetFileSize() throws IOException { Request request = getRequestBuilder(0) .head() .build(); Call call = mClient.newCall(request); if (call == null) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR,Get File Size!"); return -1; } Response response = call.execute(); if (!response.isSuccessful()) { mBuilder.mErrorListenner.onChanged(Listenner.STATUS_ERROR, mBuilder.mSavePath, "CONNECTION ERROR,Get File Size!"); return -1; } Headers headers = response.headers(); long fileSize = 0; try { fileSize = Long.parseLong(headers.get("Content-Length")); } catch (Exception e) { fileSize = -1; } return fileSize; } /*** * 執行請求 * @param offset 起始下載偏移量,該偏移量以當前已經下載的檔案長度為準 */ private void callRequest(long offset) { Request request = getRequestBuilder(offset) .build(); mCall = mClient.newCall(request); } /*** * 初始化請求引數 * @param offset 起始下載偏移量 * @return */ private Request.Builder getRequestBuilder(long offset) { Headers.Builder builder = new Headers.Builder(); //header if (mBuilder.mHeaders != null) { for (String k : mBuilder.mHeaders.keySet()) { builder.set(k, mBuilder.mHeaders.get(k)); } } if (builder.get("Range") == null) { builder.set("Range", "bytes=" + offset + "-"); } Headers headers = builder.build(); //urlparams url後面的引數 if (mBuilder.mUrlParams != null && !mBuilder.mUrlParams.isEmpty()) { if (!mBuilder.mUrl.contains("?")) { mBuilder.mUrl += "?"; } for (String k : mBuilder.mUrlParams.keySet()) { mBuilder.mUrl += k + "=" + mBuilder.mUrlParams.get(k); mBuilder.mUrl += "&"; } } Request.Builder reBuilder = new Request.Builder() .url(mBuilder.mUrl) .headers(headers); return reBuilder; } /*** * 儲存檔案 * @param is 源輸入流 * @param os 目標輸出流 * @return 如果是下載完成返回true,如果停止導致返回false * @throws IOException */ private boolean saveData(InputStream is, OutputStream os) throws IOException { Source source = Okio.source(is); Sink sink = Okio.sink(os); Buffer buf = new Buffer(); long len = 0; while ((len = source.read(buf, MAX_BUFF_SIZE)) != -1 && !isExit) { sink.write(buf, len); mDownloadLength += len; } sink.flush(); sink.close(); source.close(); return !isExit; } /*** * 重新整理當前下載進度 */ private synchronized void changeSpeed() { long endTime = System.currentTimeMillis(); mBuilder.mSpeedChangeListenner.onChanged(Listenner.STATUS_SPEED_CHANGE, mBuilder.mFileAllLength + "", (mBuilder.mStartOffset + mDownloadLength) + "",(endTime - mStartTime) + "", mDownloadLength + ""); } /** * 用於在一個新的執行緒中重新整理下載進度,防止回撥中有耗時操作阻塞下載執行緒影響下載速度 */ private final class DaemonThead extends Thread { //是否開啟 private boolean isRunning = true; @Override public void run() { while (isRunning) { //防止下載完成以及其他狀態回撥時,進度還在重新整理 synchronized (mBuilder) { changeSpeed(); try { Thread.sleep(mBuilder.mSpeedRefreshHZ); } catch (InterruptedException e) { e.printStackTrace(); } } } } } /*** * Listtenner 的簡單封裝,可以簡單的得到狀態回撥中的數值 */ public static abstract class SimpleDownLoadListenner implements Listenner { @Override public void onChanged(int code, String... value) { switch (code) { case Listenner.STATUS_ERROR: { onDownloadError(value[0], value[1]); } break; case Listenner.STATUS_SUCCESS: { onDownloadSuccessful(value[0],getLongValue(value[1])); } break; case Listenner.STATUS_SPEED_CHANGE: { onDownloadSpeedChanged(getLongValue(value[0]), getLongValue(value[1]), getLongValue(value[2]),getLongValue(value[3])); } break; case Listenner.STATUS_STOP: { onDownloadStoped(value[0], getLongValue(value[1]), getLongValue(value[2])); } break; case Listenner.STATUS_LENGTH_CHANGE: { onDownloadLengthChanged(getLongValue(value[0]), getLongValue(value[1])); } break; } } private long getLongValue(String value) { try { return Long.parseLong(value); } catch (Exception e) { return 0; } } public void onDownloadSuccessful(String path,long allFileLength) { } public void onDownloadStoped(String path, long allLength, long allDownloadlength) { } public void onDownloadError(String path, String msg) { } public void onDownloadLengthChanged(long allLength, long allDownloadLength) { } public synchronized void onDownloadSpeedChanged(long allLength,long allDownloadLenght , long times, long nowDownloadLength) { } } /*** * BRDownloadThread以Builder模式建立,不允許new */ public static class Builder implements Listenner { private Map<String, String> mUrlParams; //url後的引數 private Map<String, String> mHeaders; //請求頭 private long mFileAllLength; //檔案總長度 private long mStartOffset = 0; //起始下載偏移量 private String mUrl; //請求地址 private String mSavePath; //儲存路徑(全路徑) private String mBasePath; //儲存資料夾名稱 private String mSaveName; //檔名稱 private Listenner mSuccessListenner; //成功回撥 private Listenner mErrorListenner; //失敗回撥 private Listenner mLengthChangeListenner; //檔案長度變化回撥(只在獲取到伺服器檔案大小時回撥,以及執行緒終止後回撥(包括完成和停止)) private Listenner mSpeedChangeListenner; //進度重新整理 重新整理頻率為(1/mSpeedRefreshHZ) private Listenner mStopListenner; //停止回撥,只在下載手動終止時回撥 private long mSpeedRefreshHZ = 500; //進度重新整理間隔 毫秒 private boolean isAutoRename = false; //是否支援檔案重新命名,如果本地已存在 false 不再重複下載 true 重新命名後繼續下載 public Builder setAutoRename(boolean auto) { isAutoRename = auto; return this; } /*** * 設定進度重新整理間隔 * @param times 間隔時間 ms * @return */ public Builder setDaemonRefreshDelayTime(long times) { mSpeedRefreshHZ = times; return this; } /*** * 設定儲存的的全路徑 * @param path * @return */ public Builder setSavePath(String path) { mSavePath = path; return this; } /** * 設定儲存資料夾名稱 * @param basePath 資料夾名稱 * @return */ public Builder setBasePath(String basePath) { mBasePath = basePath; return this; } /*** * 儲存檔名 * @param name 檔名 * @return */ public Builder setSaveName(String name) { mSaveName = name; return this; } /*** * 請求地址 * @param url */ public Builder(String url) { mUrl = url; } public Builder() { } /*** * 設定狀態監聽回撥 * @param l * @return */ public Builder setListenners(Listenner l) { mSuccessListenner = l; mErrorListenner = l; mLengthChangeListenner = l; mSpeedChangeListenner = l; mStopListenner = l; return this; } /*** * 成功監聽 * @param l * @return */ public Builder setSuccessListenner(Listenner l) { mSuccessListenner = l; return this; } /*** * 失敗監聽 * @param l * @return */ public Builder setErrorListenner(Listenner l) { mErrorListenner = l; return this; } public Builder setStopListenner(Listenner l) { mStopListenner = l; return this; } /*** * 長度變化監聽 * @param l * @return */ public Builder setLengthChangeListenner(Listenner l) { mLengthChangeListenner = l; return this; } /*** * 進度重新整理監聽 * @param l * @return */ public Builder setOnSpeedListenner(Listenner l) { mSpeedChangeListenner = l; return this; } /*** * 設定起始下載偏移量和檔案長度 * @param offset 偏移量 首次下載為 0,該值只作為參考實際已檔案長度為準 * @param fileAllLength 檔案長度,可有可無 * @return */ public Builder setOffset(long offset, long fileAllLength) { mStartOffset = offset; mFileAllLength = fileAllLength; return this; } public Builder setUrl(String url) { mUrl = url; return this; } /*** * 請求頭(如果header頭中包含Range 則會替換預設的Range屬性,建議不要包含) * @param headers * @return */ public Builder setHeaders(Map<String, String> headers) { mHeaders = headers; return this; } /*** * 請求頭 * @param headers * @return */ public synchronized Builder addHeaders(Map<String, String> headers) { if (mHeaders == null) { mHeaders = headers; } else { mHeaders.putAll(headers); } return this; } /*** * 請求頭 * @param k * @param v * @return */ public synchronized Builder addHeader(String k, String v) { if (mHeaders == null) { mHeaders = new HashMap<>(); } return this; } /*** * 如果url後面包含引數,可以使用該方法為url新增引數 * @param params * @return */ public synchronized Builder setUrlParams(Map<String, String> params) { mUrlParams = params; return this; } /*** * url引數 * @param params * @return */ public synchronized Builder addUrlParams(Map<String, String> params) { if (mUrlParams == null) { mUrlParams = params; } else { mUrlParams.putAll(mUrlParams); } return this; } /** * url 引數 * @param k * @param v * @return */ public synchronized Builder addUrlParams(String k, String v) { if (mUrlParams == null) { mUrlParams = new HashMap<>(); } mUrlParams.put(k, v); return this; } public BRDownloadThread build() throws RuntimeException { if (mErrorListenner == null) { mErrorListenner = this; } if (mSpeedChangeListenner == null) { mSpeedChangeListenner = this; } if (mSuccessListenner == null) { mSuccessListenner = this; } if (mStopListenner == null) { mStopListenner = this; } if (mLengthChangeListenner == null) { mLengthChangeListenner = this; } //檔案儲存路徑錯誤 if (TextUtils.isEmpty(mSavePath) && (TextUtils.isEmpty(mBasePath) || TextUtils.isEmpty(mSaveName))) { throw new RuntimeException("can not build BRDownloadthread,because savepath or (saveBasePasth and saveName is null)!"); } //起始下載偏移量大於檔案長度 if (mStartOffset > mFileAllLength) { throw new RuntimeException("can not continue to download form startOffset,Because file length is null!"); } if (TextUtils.isEmpty(mSavePath) && !TextUtils.isEmpty(mBasePath) && !TextUtils.isEmpty(mSaveName)) { mSavePath = mBasePath + File.separator + mSaveName; } BRDownloadThread thread = new BRDownloadThread(); thread.mBuilder = this; return thread; } @Override public void onChanged(int code, String... value) { } } }
示例:
mDownloadThread = new BRDownloadThread.Builder(downloadPath) .setSavePath(savePasth) .setOffset(0,0) .setListenners(new BRDownloadThread.SimpleDownLoadListenner() { @Override public void onChanged(int code, String... value) { switch (code) { case Listenner.STATUS_ERROR: { LogUtil.i("ERROR == > " + getString(value)); }break; case Listenner.STATUS_LENGTH_CHANGE: { LogUtil.i("LENGTH == >" + getString(value)); }break; case Listenner.STATUS_SPEED_CHANGE: { LogUtil.i("SPEED == >" + getString(value)); }break; case Listenner.STATUS_STOP: { LogUtil.i("STOP == >" + getString(value)); }break; case Listenner.STATUS_SUCCESS: { LogUtil.i("SUCCESSFUL == >" + getString(value)); }break; } } }) .build(); mDownloadThread.start();
相關推薦
Android 基於OkHttp的下載,支援https,斷點下載,優化下載速度
package com.lenovo.smarthcc.http.okhttp; import android.text.TextUtils; import com.lenovo.smarthcc.http.Listenner; import java.io.File
Android實現網路多執行緒斷點續傳下載
本示例介紹在Android平臺下通過HTTP協議實現斷點續傳下載。 我們編寫的是Andorid的HTTP協議多執行緒斷點下載應用程式。直接使用單執行緒下載HTTP檔案對我們來說是一件非常簡單的事。那麼,多執行緒斷點需要什麼功能? 1.多執行緒下載,
Android 基於Zxing的二維碼掃描優化
最近公司專案App中要整合二維碼掃描來適應在戶外工作的時候,對碼頭集裝箱等上面貼的A4紙張列印的二維碼進行識別, 一般App二維碼整合後,能掃出來就不管了,但是我們在整合成功後,根據使用者反饋,在戶外的環境下,很多二維碼識別不了,或者識別速度慢,我們自己也是適用了一下,發現也確實是這樣. &nb
Android網路程式設計 --斷點續傳下載檔案
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
Android中OkHttp使用(包括上傳與下載)
OkHttp介紹 OkHttp是Square公司的出品,一個Http請求庫,Google官方文件不推薦人們使用HttpClient,可是HttpURLConnection 實在是太難用了,因此很多人使用了OkHttp 來解決這問題, Andro
installgithub-支援斷點續傳下載GitHubDesktop離線安裝檔案
用GitHub賬號提交程式碼的都希望下載本地客戶端克隆倉庫 https://desktop.github.com/ 可是在天朝用GitHub.exe線上下載安裝這個客戶端實在是太難了 由於不支援斷點續傳 公司千M光纖外帶翻牆都無法成功下載這個玩意
基於OkHttp網路通訊工具類(傳送get、post請求、檔案上傳和下載)
一、為什麼要用OkHttp? okhttp是專注於提升網路連線效率的http客戶端。 優點: 1、它能實現同一ip和埠的請求重用一個socket,這種方式能大大降低網路連線的時間,和每次請求都建立socket,再斷開socket的方式相比,降低了伺服器伺服器的壓力。 2、okhttp 對
yunBT:一個基於TP3.1的多使用者BT離線下載程式,支援線上播放
說明:yunBT這個專案其實很早就有了,只是老沒更新,現在作者基於ThinkCMS重做該程式,支援多使用者註冊下載,Magnet和HTTP下載。每個單獨使用者支援10個任務,預設下載檔案最大為10GB,可以在後臺修改。下載完成後使用者可以直接檢視下載的檔案僅支援mp4檔案線上播放。管理員可以新增使用者的下載量
基於OkHttp Retrofit RxJava 多執行緒下載。請求、快取、自動更新.限制佇列數.封裝庫
XDownload介紹 本庫封裝基於Okhttp3,Retrofit2,RxJava2.0,Greendao3.2 ps : 當然當然,都封裝好了,你也可以無視 GitHub地址 如果你覺得好用,對你有幫助,請給個star 介面
Android下載-實時進度-斷點續傳
概述 帶顯示進度和斷點續傳的下載示例 使用Okhttp框架 思路 獲取下載檔案的總大小,獲取本地檔案,如果不存在,就下載,存在,就獲取檔案的大小, 如果本地檔案的大小與網路上檔案的大小,就提示下載完成。 如果本地檔案大小 < 網路上檔案的大小,
Android使用okhttp封裝多檔案批量下載 (帶進度條,取消下載)
在網上搜索了很多關於okhttp封裝的網路框架,唯獨沒找到完美實現了多個檔案批量下載的案例,當前使用的最多的也就是okhttp了,所以,我學習了各位大神的封裝後,自己也試著封裝了一個關於okhttp的網路請求框架,方便專案中的使用。 實現的功能基本如下:
WinHttp支援HTTPS下載
#include "windows.h" #include "winhttp.h" #include "wchar.h" #pragma comment(lib,"Winhttp.lib") // SSL (Secure Sockets Layer) example //
OkHttp實現多執行緒斷點續傳下載,單例模式下多工下載管理器,一起拋掉sp,sqlite的輔助吧
最近專案需要使用到斷點下載功能,筆者比較喜歡折騰,想方設法拋棄SharedPreferences,尤其是sqlite作記錄輔助,改用臨時記錄檔案的形式記錄下載進度,本文以斷點下載為例。先看看demo執行效果圖: 斷點續傳:記
Android okHttp網路請求之檔案上傳下載
前言: 前面介紹了基於okHttp的get、post基本使用(http://www.cnblogs.com/whoislcj/p/5526431.html),今天來實現一下基於okHttp的檔案上傳、下載。 okHttp相關文章地址: 檔案上傳: 1.)不帶引數上傳檔案
Android FTP 多執行緒斷點續傳下載\上傳
最近在給我的開源下載框架Aria增加FTP斷點續傳下載和上傳功能,在此過程中,爬了FTP的不少坑,終於將功能實現了,在此把一些核心功能點記錄下載。 FTP下載原理 FTP單執行緒斷點續傳 FTP和傳統的HTTP協議有所不同,由於FTP沒有所謂的標頭
Android檔案下載(實現斷點續傳)
http://www.ideasandroid.com/archives/328#more-328 本文將介紹在android平臺下如何實現多執行緒下載,大家都知道,android平臺使用java做為開發語言,所以java中支援的多執行緒下載方式在android平臺下都支援,
android 多執行緒斷點續傳下載 三
今天跟大家一起分享下android開發中比較難的一個環節,可能很多人看到這個標題就會感覺頭很大,的確如果沒有良好的編碼能力和邏輯思維,這塊是很難搞明白的,前面2次總結中已經為大家分享過有關技術的一些基本要領,我們先一起簡單回顧下它的基本原理。什麼是多執行緒下載?多執行緒下載其
Android-斷點續傳下載
工作找完了,玩也玩完了,該好好學習了,最近我把《Java併發程式設計的藝術》這本書給讀完了,對於併發程式設計以及執行緒池的使用還是不嫻熟,我就在imooc上找到一個專案“Android-Service系列之斷點續傳下載“,這是我對這個專案在編寫的時候記錄。 涉及
Android基於Okhttp3的檔案下載工具類
需求中有需要簡易的下載檔案的,例如圖片,音訊,視訊等。首先這個下載工具類沒有斷點下載,也就是說沒有暫停,快取。不過解決日常工作中的小檔案下載是綽綽有餘的。下面可以看一看 一、新增okhttp3的遠端依賴 compile 'com.squareup.okhttp3:ok
【Android開發經驗】關於“多執行緒斷點續傳下載”功能的一個簡單實現和講解
上班第一天,在技術群裡面和大家閒扯,無意中談到了關於框架的使用,一個同學說為了用xUtils的斷線續傳下載功能,把整個庫引入到了專案中,在google的官方建議中,是非常不建議這種做法的,集合框架雖然把很多功能整合起來,但是程式碼越多,出現問題的可能越大,而且無形之中