前言
文件下載在很多類型的應(yīng)用中會涉及,例如音樂、文檔、包括圖片(只是圖片可以使用一些組件完成無感知的下載)。本篇介紹使用 Dio 的下載方法完成文件的下載,涉及到的內(nèi)容如下:
- Dio 插件的
download方法介紹; - 使用
download的回調(diào)方法監(jiān)測下載進(jìn)度; - 使用
CancelToken取消正在下載的任務(wù); - 刪除已下載的文件;
-
path_provider插件管理App文件目錄; - 下載文件調(diào)試過程中發(fā)現(xiàn)的一些問題;
Dio 的下載方法 download
Dio 的下載方法定義如下:
Future<Response> download(
String urlPath,
savePath, {
ProgressCallback? onReceiveProgress,
Map<String, dynamic>? queryParameters,
CancelToken? cancelToken,
bool deleteOnError = true,
String lengthHeader = Headers.contentLengthHeader,
data,
Options? options,
});
-
urlPath:網(wǎng)絡(luò)資源的url; -
savePath:dynamic類型,可以是下載后存儲文件路徑的字符串,也可以是一個返回字符串的回調(diào)方法(Dio 會把headers參數(shù)攜帶過去,方便針對下載返回內(nèi)容構(gòu)建文件路徑); -
onReceiveProgress:文件接收進(jìn)度,是一個void Function(int count, int total)回調(diào)函數(shù),調(diào)用者可以通過該回調(diào)方法監(jiān)測下載進(jìn)度。 -
deleteOnError:發(fā)生錯誤時(shí)候是否刪除已下載的文件,默認(rèn)是 true。 -
lengthHeader:源文件的實(shí)際大?。ㄎ磯嚎s前)。默認(rèn)是header的content-length。如果文件壓縮了,而沒有指定該值的話,那進(jìn)度回調(diào)里的total會是-1;如果使用自定義的header指定了文件的大小,那么total會是自定義的header對應(yīng)的文件大小。 - 其他參數(shù)和普通的請求差不多,這里不再贅述。
為了不暴露下載的具體實(shí)現(xiàn),我們在 http_util.dart 中封裝一個自己的下載方法。
static Future download(
String url,
String savePath, {
Map<String, dynamic> queryParams,
CancelToken cancelToken,
dynamic data,
Options options,
void Function(int, int) onReceiveProgress,
}) async {
try {
return await _dioInstance.download(
url,
savePath,
queryParameters: queryParams,
cancelToken: cancelToken,
onReceiveProgress: onReceiveProgress,
);
} on DioError catch (e) {
if (CancelToken.isCancel(e)) {
EasyLoading.showInfo('下載已取消!');
} else {
if (e.response != null) {
_handleErrorResponse(e.response);
} else {
EasyLoading.showError(e.message);
}
}
} on Exception catch (e) {
EasyLoading.showError(e.toString());
}
}
監(jiān)測下載進(jìn)度
我們新建一個文件下載頁面 file_download.dart完成文件下載的示例。這里定義了幾個屬性來對文件下載過程進(jìn)行反饋:
// 文件下載地址,這里是谷歌瀏覽器的下載地址(Mac 版本)
String _downloadPath =
'https://dl.google.com/chrome/mac/stable/GGRO/googlechrome.dmg';
// 下載進(jìn)度比例,用于檢測下載是否完成
double _downloadRatio = 0.0;
// 下載進(jìn)度百分比
String _downloadIndicator = '0.00%';
// 下載文件的存儲路徑
String _destPath;
// 取消下載的 token
CancelToken _token;
// 指示當(dāng)前是否處于下載中,以便做業(yè)務(wù)判斷
bool _downloading = false;
然后我們定義一個下載方法,在下載過程中如果 total 不為-1就更新下載進(jìn)度,否則提示錯誤(實(shí)際調(diào)試發(fā)現(xiàn),如果涉及到需要驗(yàn)證的,下載后后端實(shí)際會返回網(wǎng)頁,這樣也能下載網(wǎng)頁內(nèi)容下來,但是不是想要的文件)。
void _downloadFile() {
_token = CancelToken();
_downloading = true;
HttpUtil.download(_downloadPath, _destPath, cancelToken: _token,
onReceiveProgress: (int received, int total) {
if (total != -1) {
if (!_token.isCancelled) {
setState(() {
_downloadRatio = (received / total);
if (_downloadRatio == 1) {
_downloading = false;
}
_downloadIndicator =
(_downloadRatio * 100).toStringAsFixed(2) + '%';
});
}
} else {
_downloading = false;
EasyLoading.showError('無法獲取文件大小,下載失敗!');
}
});
}
這里因?yàn)樯婕暗娇赡苋∠?,因此只有在沒有取消的情況下才更新下載狀態(tài),要不可能會出現(xiàn)取消的時(shí)候還處在下載接收字節(jié)的過程中,雖然取消了但是看到下載進(jìn)度還在走的情況。
取消下載
取消下載其實(shí)很簡單,當(dāng)我們點(diǎn)擊取消按鈕的時(shí)候,調(diào)用 CancelToken 的cancel方法即可。這里我們做了一個判斷,下載比例低于1才可以取消,因?yàn)橄螺d完成再取消會拋異常。同時(shí)取消后重置下載比例和顯示的下載百分比。
void _cancelDownload() {
if (_downloadRatio < 1.0) {
_token.cancel();
_downloading = false;
setState(() {
_downloadRatio = 0;
_downloadIndicator = '0.00%';
});
}
}
刪除已經(jīng)下載的文件
對于 App,沒有別的入口管理文件,因此實(shí)際過程中我們需要提供下載入口共用戶清理已下載的文件。實(shí)際已下載的文件,我們需要有下載文件管理功能供用戶管理文件,這個時(shí)候會需要本地存儲支撐,我們在后續(xù)的章節(jié)會介紹本地存儲。
刪除文件前需要判斷文件是否存在,如果文件不存在刪除可能拋出異常。文件的管理使用的是 dart:io 中的方法。
void _deleteFile() {
try {
File downloadedFile = File(_destPath);
if (downloadedFile.existsSync()) {
downloadedFile.delete();
} else {
EasyLoading.showError('文件不存在');
}
} catch (e) {
EasyLoading.showError(e.toString());
}
}
path_provider文件目錄管理
在 App 中沒法直接知道應(yīng)用的文件存儲目錄,因此需要借用 path_provider 插件來獲取 App 的文件存儲目錄,path_provider 提供了如下方法:
-
getTemporaryDirectory:應(yīng)用臨時(shí)目錄(可能被清除) -
getApplicationDocumentsDirectory:應(yīng)用文檔目錄(不會被系統(tǒng)清除,主要用戶數(shù)據(jù)存儲目錄),對于安卓推薦使用外部存儲getExternalStorageDirectory。 -
getApplicationSupportDirectory:應(yīng)用支持目錄,一般放置與用戶無關(guān)的數(shù)據(jù)。 -
getLibraryDirectory:指向應(yīng)用可以持久存儲數(shù)據(jù)的目錄,不支持安卓平臺。 -
getExternalStorageDirectory:獲取外部存儲目錄,不支持 iOS 平臺。 -
getExternalCacheDirectories:獲取外部緩存目錄,,不支持 iOS 平臺。 -
getExternalStorageDirectories:獲取外部可以的目錄列表,不支持 iOS 平臺。 -
getDownloadsDirectory:獲取下載目錄,用于 Web 端,不支持安卓和 iOS平臺。
通過 path_provider拿到Directory對象后,就可以通過 Directory的 path 屬性獲取到完整的目錄路徑。本例我們是在 initialState 里獲取文件存儲路徑的,使用的是臨時(shí)目錄。
void initState() {
getTemporaryDirectory()
.then((tempDir) => {_destPath = tempDir.path + 'googlechrome.dmg'});
super.initState();
}
調(diào)試過程中遇到的一些錯誤
-
OS Error: Read-only file system:安卓系統(tǒng)需要獲取READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE權(quán)限。同時(shí)需要使用path_provider獲取應(yīng)用的文件目錄再往對應(yīng)的目錄讀寫文件和訪問文件目錄。 -
onReceivedProgress中如果total=-1則表示該文件被壓縮或者需要會話信息才可以下載(如后端開啟了驗(yàn)證)。 - 刪除文件的時(shí)候需要檢查文件是否在下載過程中,如果在下載過程中刪除會引起文件讀寫沖突,拋出異常。
-
CancelToken一個實(shí)例只能取消一次請求,因此每次發(fā)起請求的時(shí)候需要重新構(gòu)建CancelToken對象,否則取消一次后無法再次取消。
運(yùn)行結(jié)果及代碼
運(yùn)行結(jié)果如下圖所示:
代碼已提交至 gitee:Dio網(wǎng)絡(luò)請求相關(guān)代碼。
總結(jié)
從示例可以看到,Dio 的下載方法是簡單易用的,而且提供了友好的下載反饋。同時(shí),借助 CancelToken 也能取消或者暫停下載(暫停時(shí)設(shè)置deleteOnError=false,表示不刪除文件,然后恢復(fù)到時(shí)候從已下載的偏移量開始,也可以按這種方式做斷點(diǎn)續(xù)傳,具體方式可以搜索或者自己完成)。Dio 網(wǎng)絡(luò)請求系列的詳細(xì)介紹到這一篇結(jié)束,后續(xù)實(shí)際業(yè)務(wù)用到的時(shí)候再穿插介紹,接下來一篇將對 Dio 系列文章做一個整體總結(jié)。