前言
在做移动端项目时,一些比较大的文档需要我们缓存到本地之后再进行使用,一般都会涉及到文件下载,大文件更是会将文件打成压缩包下载,在应用程序使用时解压缩,之后再使用,我们的社交项目更是如此,我们有大量的特效资源和装扮资源需要下载使用,因此我们在进行项目的正式开发前,先对文件下载链路做了分析,最终确定使用dio(网络下载)+sembast(非关系型数据库)+archive(解压文件)的链路进行,下面通过一个简单的ToDoList看一下效果
效果

步骤拆解
1. 下载资源
1.1. 创建数据列表
php
// 创建初始化的数据列表
List<DownloadItemModel> downloadFileList = [
DownloadItemModel(
id: 1,
name: "图片1",
path: 'https://miaohu-oss-cdn.miaohu123.com/course/1.zip',
isDownload: false,
progress: 0.0,
savePath: '',
zipFiles: []),
DownloadItemModel(
id: 2,
name: "图片2",
path: 'https://miaohu-oss-cdn.miaohu123.com/course/2.zip',
isDownload: false,
progress: 0.0,
savePath: '',
zipFiles: []),
DownloadItemModel(
id: 3,
name: "svga",
path: 'https://miaohu-oss-cdn.miaohu123.com/course/3.zip',
isDownload: false,
progress: 0.0,
savePath: '',
zipFiles: []),
];
1.2. 下载文件
下载文件主要是使用dio将网络文件下载到本地,基本目前的安卓和苹果下载到软件所存在的文件中都不需要申请额外的存储权限了(具体情况需要不同设备测试),因此将文件下载到手机中直接监听下载进度即可
ini
// 下载未下载的文件
Future onclickDownloadFile() async {
// 先检查当前的数据有没有下载
List<DownloadItemModel> filterList =
downloadFileList.where((element) => !element.isDownload).toList();
if (filterList.isNotEmpty) {
// 准备下载
for (var item in filterList) {
downloadFile(item);
}
}
}
// 下载文件
Future downloadFile(DownloadItemModel data) async {
var dir = await localfileDir();
String path = dir.path;
// 获取文件类型,也就是最后的后缀
List<String> urlList = data.path.split('.');
String fileType = urlList[urlList.length - 1];
String filePath = "$path/download/${data.id}.$fileType";
Dio dio = Dio();
try {
dio.download(data.path, filePath, onReceiveProgress: (count, total) {
downloadProgressCallBack(count, total, data.id.toString(), filePath);
});
} on DioError catch (e) {
print(e);
}
}
// 下载进度监听
downloadProgressCallBack(
count,
total,
String id,
String filePath,
) async {
// 修改进度条
downloadFileList
.firstWhere((element) => element.id == int.parse(id))
.progress = count / total;
update(["download"]);
if (count == total) {
downloadFileList
.firstWhere((element) => element.id == int.parse(id))
.isDownload = true;
update(["download"]);
if (filePath.endsWith('zip')) {
unzipFiles(filePath, id);
}
}
}
2. 解压缩
文件下载完成之后解压,这里注意如果是苹果电脑的压缩文件中可能含有MACOSX文件夹,因此需要对此文件夹忽略处理,当然解决方案有很多,这里就不赘述
ini
// 解压文件
Future unzipFiles(url, String id) async {
// 获取到文件路径
var dir = await localfileDir();
String documentPath = dir.path;
// 拿到文件的存储路径
String savePath = "$documentPath/download";
// 从磁盘读取zip文件
List<int> bytes = File(url).readAsBytesSync();
// 解码zip文件
try {
Archive archive = ZipDecoder().decodeBytes(bytes);
String res = '';
List<String> pathFiles = [];
// 将解码的文件解压缩到磁盘
for (ArchiveFile file in archive) {
if (file.isFile) {
List<int> tempData = file.content;
File f = File("$savePath/${file.name}")
..createSync(recursive: true)
..writeAsBytesSync(tempData);
if (!f.path.contains('MACOSX')) {
String clipFile = f.path.substring(dir.path.length);
// myLog(f.path);
pathFiles.add(clipFile);
res = clipFile;
}
} else {
Directory("$savePath/${file.name}").create(recursive: true);
}
}
//整理数据, 通知数据库存储
List<DownloadItemModel> filterList = downloadFileList
.where((element) => element.id.toString() == id)
.toList();
if (filterList.isNotEmpty) {
DownloadItemModel data = filterList.first;
data.savePath = res;
data.zipFiles = pathFiles;
DBService.to.saveFileData(data);
}
} catch (e) {
print(e);
}
}
3. 存储数据库
由于项目比较简单,因此选用了比较轻量解决的非关系型数据库sembast
,官方地址如下:
pub.dev/packages/se...
总的来说sembast
非常简单,简单使用的话只需要创建数据库和分表,然后做相对应的增删改查
3.1. 创建数据库
数据库初始化:初始化数据库
ini
Future initDb() async {
var dir = await localfileDir();
path = dir.path;
// myLog(dir);
var dbPath = '${dir.path}/my_database.db';
// 判断文件是否存在
File dbFile = File(dbPath);
bool exists = await dbFile.exists();
if (!exists) {
// 没有创建过DB,开始创建DB
await dir.create(recursive: true);
}
DatabaseFactory dbFactory = databaseFactoryIo;
// 初始化完成
database = await dbFactory.openDatabase(dbPath);
}
数据库初始化: 创建下载分区
ini
// 下载分区
final downloadStore = intMapStoreFactory.store("download");
3.2. 增加数据
scss
// 存储下载数据
Future saveFileData(DownloadItemModel data) async {
if (database == null) {
await initDb();
}
downloadStore.record(data.id).put(database!, data.toJson());
}
3.3. 查找数据
这里的查找数据是使用的批量查找,单独查找改成record
并且传参换成单独的id即可
csharp
// 查找下载的数据
Future getFileData(List<int> ids) async {
if (database == null) {
await initDb();
}
var map = await downloadStore.records(ids).get(database!);
List<DownloadItemModel> list = [];
if (map.isEmpty) {
return null;
} else {
for (var element in map) {
if (element != null) {
DownloadItemModel info = DownloadItemModel.fromJson(element);
list.add(info);
}
}
return list;
}
}
全部的步骤到这里基本就完成了,剩下的就可以根据自己的业务需求完成扩展,最终代码已上传至下方仓库,有需要欢迎fork和讨论