作者:碼小白
文/CSDN 部落格
本文出自:http://blog.csdn.net/sk719887916/article/details/51988507 碼小白
通過前幾篇系統的介紹和綜合運用,忘記介紹檔案下載下傳功能了,有朋友問到,目前APP檔案下載下傳主要有斷點續傳,多線程并發下載下傳,多類型下載下傳,今天就介紹下其Retrofit下載下傳檔案功能。
-
Retrofit 2.0
超能實踐,完美支援Https傳輸
-
Retrofit2.0
完美同步Cookie實作免登入
- Retrofit 2.0 超能實踐(三),輕松實作檔案/圖檔上傳
- 基于Retrofit2.0 封裝的超好用的RetrofitClient工具類
- 玩轉IOC,教你徒手實作自定義的Retrofit架構
ApiService
編寫API,執行下載下傳接口功能。
public interface ApiService {
@Streaming
@GET
Observable<ResponseBody> downloadFile(@Url String fileUrl);
}
url由于是可變的,是以用
@URL
注解符号來進行指定,大檔案官方建議用
@Streaming
來進行注解,不然會出現IO異常,小檔案可以忽略不注入。如果想進行斷點續傳的話 可以在此加入header,但不建議直接在api中寫死,每個下載下傳的請求大小是不同的,在攔截器加入更為妥善。
DownLoadManager
實作一個下載下傳管理者 來進行檔案寫入,類型判斷等,如果想做完善,可以判斷下http的請求頭
content-length
,
content- type
,
RANGE
第一個用來判斷檔案大小,第二個檔案類型,第三個是檔案從哪兒開始下載下傳,如果對下載下傳來源校驗可以加入
referer
, 不是目标來源的可以不予下載下傳權限。
public class DownLoadManager {
private static final String TAG = "DownLoadManager";
private static String APK_CONTENTTYPE = "application/vnd.android.package-archive";
private static String PNG_CONTENTTYPE = "image/png";
private static String JPG_CONTENTTYPE = "image/jpg";
private static String fileSuffix="";
public static boolean writeResponseBodyToDisk(Context context, ResponseBody body) {
Log.d(TAG, "contentType:>>>>"+ body.contentType().toString());
String type = body.contentType().toString();
if (type.equals(APK_CONTENTTYPE)) {
fileSuffix = ".apk";
} else if (type.equals(PNG_CONTENTTYPE)) {
fileSuffix = ".png";
}
// 其他同上 自己判斷加入
String path = context.getExternalFilesDir(null) + File.separator + System.currentTimeMillis() + fileSuffix;
Log.d(TAG, "path:>>>>"+ path);
try {
// todo change the file location/name according to your needs
File futureStudioIconFile = new File(path);
InputStream inputStream = null;
OutputStream outputStream = null;
try {
byte[] fileReader = new byte[4096];
long fileSize = body.contentLength();
long fileSizeDownloaded = 0;
inputStream = body.byteStream();
outputStream = new FileOutputStream(futureStudioIconFile);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
outputStream.write(fileReader, 0, read);
fileSizeDownloaded += read;
Log.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
}
outputStream.flush();
return true;
} catch (IOException e) {
return false;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
}
} catch (IOException e) {
return false;
}
}
}
上面隻是簡單的對不同類型檔案進行了判斷,其他類型參考
http
協定描述類型自行加入,斷點續傳目前暫時沒實作,基于HttpClinet下載下傳的介紹很多,關鍵思想是記錄一條下載下傳産生後的
RANGE
到資料庫裡,每條下載下傳行對應一條資料,下載下傳ID是區分唯一的主鍵, 包含
filename
,
fileSize, fileType, downTime, downRange
status
基本可滿足多線程下載下傳資料要求,在Retrofit結合RXJava後 解決了線程安全問題,實作多線程下載下傳就更so yi z, 這裡我也不做多的介紹。
#調用下載下傳
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.build();
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(url)
.build();
Api Service apiService = retrofit.create(ApiService.class);
apiService..download(url1,
new Subscriber<ResponseBody>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(ResponseBody responseBody) {
if (DownLoadManager.writeResponseBodyToDisk(MainActivity.this, responseBody)) {
Toast.makeText(MainActivity.this, "Download is sucess", Toast.LENGTH_LONG).show();
}
}
});
}
});
當 然我們可以在header中加入上次下載下傳的進度大小,就可以實作基本的斷點下載下傳了,多線程下載下傳目前業内主要由下面思想實作,我們可以請求到檔案總長度的時候,均分三段大小區間,來開啟三個新線程去下載下傳,下載下傳完後再以唯一的檔案ID,将三段檔案以此追加到一個檔案就可以了,當然在retrofit結合RxJava,你無需開啟新線程,隻需訂閱一下 Subscriber到IO線程即可,這裡注意的是你需要判斷最後的檔案大小和HashCode來進行驗證檔案完整性,不然視訊檔案丢包了沒多大差別,最多會卡幀,但是apk丢包了卻無法解析安裝了。
斷點下載下傳姿勢開啟!
#斷點下載下傳
##apiServiece
@GET
@Streaming
Observable<Response<ResponseBody>> download(@Header("Range") String range, @Url String url);
##API
DownLoadInfos裡面包含name,lenth,.url,等,這裡不再貼了。直接看API
DownloadType 裡面記錄要有stats,mLastModify,downloded ,savepath等
public Observable<DownloadInfos> download(@NonNull final String url, @NonNull final String saveName,
@Nullable final String savePath) {
return downloadDispatcher(url, saveName, savePath);
}
private Observable<DownloadStatus> downloadDispatcher(final String url, final String saveName, final String savePath) {
return getDownloadType(url)
.flatMap(new Func1<DownloadType, Observable<DownloadStatus>>() {
@Override
public Observable<DownloadInfos> call(DownloadType type) {
try {
type.prepare();
} catch (IOException | ParseException e) {
return Observable.error(e);
}
try {
return type.start();
} catch (IOException e) {
return Observable.error(e);
}
}
})
.doOnCompleted(new Action0() {
@Override
public void call() {
mDownloadManager.delete(url);
}
})
.doOnError(new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
mDownloadManager.delete(url);
}
})
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
mDownloadManager.delete(url)
});
}
開始下載下傳:
@Override
Observable<DownloadInfos> start() {
Log.i(TAG, "Normal download start!!");
return mDownloadHelper.getDownloadApi().download(null, mUrl)
.subscribeOn(Schedulers.io())
.flatMap(new Func1<Response<ResponseBody>, Observable<DownloadStatus>>() {
@Override
public Observable<DownloadStatus> call(final Response<ResponseBody> response) {
return normalSave(response);
}
}).onBackpressureLatest().retry(new Func2<Integer, Throwable, Boolean>() {
@Override
public Boolean call(Integer integer, Throwable throwable) {
return mDownloadHelper.retry(integer, throwable);
}
});
}
##調用
DownloadAgenti.getInstance(mContext)
.observeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<DownloadInfos>() {
@Override
public void onCompleted() {
// todo
}
@Override
public void onError(Throwable e) {
todo
}
@Override
public void onNext(final DownloadStatus status) {
todo
}
});
#總結
步驟:
請求檔案總大小
根據機型高低,配置設定多個線程下載下傳
記錄下載下傳進度,大小,類型等到資料庫
同時更新UI和通知欄,提示使用者
下載下傳結束後更新資料庫下載下傳資料,追加組合檔案
判斷檔案大小,檢驗檔案大小
下載下傳功能是每個應用必須的功能,是以下載下傳還是比較重要的一個子產品,好的下載下傳架構 會考慮到裝置的CPU大小,網絡狀态,以及外部sdcard的大小,動态進行性能,流量配置設定,友善使用者更好的體驗和使用你的APP.
具體可以閱讀 - 基于Retrofit2.0 封裝的超好用的RetrofitClient工具類 中的寫法
項目github:https://github.com/Tamicer/FastDownloader
作者:Tamic 更多原創關注開發者技術前線