撸了一个flutter断点续传下载。
基于dio,自配。
以下是源码,自取。
import 'dart:async';
import 'dart:io';
import 'package:dio/dio.dart';
/*
* 文件下载
* 懒加载单例
*/
class DownLoadManage {
//用于记录正在下载的url,避免重复下载
// var downloadingUrls = new List();
var downloadingUrls = new Map<String, CancelToken>();
// 单例公开访问点
factory DownLoadManage() => _getInstance();
// 静态私有成员,没有初始化
static DownLoadManage _instance;
// 私有构造函数
DownLoadManage._() {
// 具体初始化代码
}
// 静态、同步、私有访问点
static DownLoadManage _getInstance() {
if (_instance == null) {
_instance = DownLoadManage._();
}
return _instance;
}
/*
*下载
*/
Future download(url, savePath,
{ProgressCallback onReceiveProgress,
Function done,
Function failed}) async {
int downloadStart = 0;
bool fileExists = false;
File f = File(savePath);
if (await f.exists()) {
downloadStart = f.lengthSync();
fileExists = true;
}
print("开始:" + downloadStart.toString());
if (fileExists && downloadingUrls.containsKey(url)) {
//正在下载
return;
}
var dio = Dio();
int contentLength = await _getContentLength(dio, url);
if (downloadStart == contentLength) {
//存在本地文件,命中缓存
done();
return;
}
CancelToken cancelToken = new CancelToken();
downloadingUrls[url] = cancelToken;
Future downloadByDio(String url, int start) async {
try {
Response response = await dio.get(
url,
options: Options(
responseType: ResponseType.stream,
followRedirects: false,
headers: {"range": "bytes=$start-"},
),
);
print(response.headers);
File file = new File(savePath.toString());
var raf = file.openSync(mode: FileMode.append);
Completer completer = new Completer<Response>();
Future future = completer.future;
int received = start;
int total = response.headers.contentLength;
Stream<List<int>> stream = response.data.stream;
StreamSubscription subscription;
Future asyncWrite;
subscription = stream.listen(
(data) {
subscription.pause();
// Write file asynchronously
asyncWrite = raf.writeFrom(data).then((_raf) {
// Notify progress
received += data.length;
if (onReceiveProgress != null) {
onReceiveProgress(received, total);
}
raf = _raf;
if (cancelToken == null || !cancelToken.isCancelled) {
subscription.resume();
}
});
},
onDone: () async {
try {
await asyncWrite;
await raf.close();
completer.complete(response);
downloadingUrls.remove(url);
if (done != null) {
done();
}
} catch (e) {
downloadingUrls.remove(url);
completer.completeError(_assureDioError(e));
if (failed != null) {
failed(e);
}
}
},
onError: (e) async {
try {
await asyncWrite;
await raf.close();
downloadingUrls.remove(url);
if (failed != null) {
failed(e);
}
} finally {
completer.completeError(_assureDioError(e));
}
},
cancelOnError: true,
);
// ignore: unawaited_futures
cancelToken?.whenCancel?.then((_) async {
await subscription.cancel();
await asyncWrite;
await raf.close();
});
return await _listenCancelForAsyncTask(cancelToken, future);
} catch (e) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx and is also not 304.
if (e.response != null) {
print(e.response.data);
print(e.response.headers);
print(e.response.request);
} else {
// Something happened in setting up or sending the request that triggered an Error
print(e.request);
print(e.message);
}
if (CancelToken.isCancel(e)) {
print("下载取消");
}else{
if (failed != null) {
failed(e);
}
}
downloadingUrls.remove(url);
}
}
await downloadByDio(url, downloadStart);
}
/*
* 获取下载的文件大小
*/
Future _getContentLength(Dio dio, url) async {
try {
Response response = await dio.head(url);
return response.headers.contentLength;
} catch (e) {
print("_getContentLength Failed:" + e.toString());
return 0;
}
}
void stop(String url) {
if (downloadingUrls.containsKey(url)) {
downloadingUrls[url].cancel();
}
}
Future<T> _listenCancelForAsyncTask<T>(
CancelToken cancelToken, Future<T> future) {
Completer completer = new Completer();
if (cancelToken != null && cancelToken.cancelError == null) {
cancelToken.addCompleter(completer);
return Future.any([completer.future, future]).then<T>((result) {
cancelToken.removeCompleter(completer);
return result;
}).catchError((e) {
cancelToken.removeCompleter(completer);
throw e;
});
} else {
return future;
}
}
DioError _assureDioError(err) {
if (err is DioError) {
return err;
} else {
var _err = DioError(message: err.toString(), error: err);
if (err is Error) {
_err.stackTrace = err.stackTrace;
}
return _err;
}
}
}
使用:
start() async {
var url ="https://XXX";
var savePath = sDCardDir + "/XXX/XXX.XXX";
File f = File(sDCardDir + "/XXX");
if (!await f.exists()) {
new Directory(sDCardDir + "/XXX").createSync();
}
await DownLoadManage().download(url, savePath,
onReceiveProgress: (received, total) {
if (total != -1) {
print("下载1已接收:" +
received.toString() +
"总共:" +
total.toString() +
"进度:+${(received / total * 100).floor()}%");
}
}, done: () {
print("下载1完成");
}, failed: (e) {
print("下载1失败:" + e.toString());
});
}