该项目已发布到uniapp官方插件商店供大家免费使用借鉴:iso 安卓 文件下载模板 - DCloud 插件市场  

在uniapp中提供了相应的文件下载api,但是无法对已下载的文件进行统一的管理。也不能对重复下载进行过滤。所以我封装了一个专用于处理app下载任务的组件。思路是通过本地缓存来保存我们所有下载文件的url,和名称等字段,将来在下载前进行筛选去重。下载成功更新这个缓存。放弃使用uniapp的api转用plus,这样可以更灵活的实现文件管理(调用原生api的io)。从而对下载文件进行增删改查。

相关代码已上传gitee,正在开源审核中。先上关键代码。

获取下载文件的地址,可以写成异步请求
			getInfo() {

				this.downLoadInfo.object.down_url = 'https://lmg.jj20.com/up/allimg/tp02/1Z9191923035R0-0-lp.jpg'
				// console.log(this.downLoadInfo)
				this.localStorageCheck()
			},
			//PLUS-APP封装下载
			plusDownload() {
				//拦截多次下载请求
				if (!this.downLoadFlag || this.downloadStatus == 3) {
					return
				}
				uni.showLoading({
					title: '下载中...'
				});
				this.downLoadFlag = false
				//设置不同平台的文件保存路径
				if (plus.os.name == 'Android') {
					//公共文件
					// this.downloaderOptions = {
					// 	filename: 'file://storage/emulated/0/xiaobaidownload/'+this.fileInfo.title
					// }
					//沙盒
					this.downloaderOptions = {}
				} else if (plus.os.name == 'iOS') {
					// this.savePath ='/Library/Pandora/downloads'
					//ios只能在沙盒内打开
					this.downloaderOptions = {}
				}
				const url = this.downLoadInfo.object.down_url
				// console.log(this.downLoadInfo)
				this.dtask = plus.downloader.createDownload(url, {
					...this.downloaderOptions //利用保存路径,实现下载文件的重命名
				}, (d, status) => {
					// 下载完成

					if (status == 200) {
						this.filePath = d.filename
						// console.log(this.filePath)
						this.canOpen = true //可以打开文件
						this.downLoadFlag = true
						//下载成功后操作缓存记录下载信息
						try {
							const item = {
								name: this.downLoadInfo.title, //文件下载名
								path: this.downLoadInfo.object.down_url, //可作为未下载文件的唯一标识
								fullPath: d.filename // _downloads/****.png 可作为本地下载文件的唯一标识
							}
							this.downLoadInfoValue.data.push(item)
							const value = JSON.stringify(this.downLoadInfoValue)
							// console.log(value)
							plus.storage.setItem("downLoadList_", value);
						} catch (e) {
							// error
							console.log(e)
						}
						uni.showToast({
							title: '下载成功'
						})
						uni.hideLoading()
					} else {
						console.log("Download failed: " + status);
						this.dtask.clear() //清除下载器
						uni.showToast({
							title: '下载失败',
							icon: 'error'
						})
					} 
				});
				this.dtask.addEventListener('statechanged', (dtask) => {
					if (!dtask) {
						return;
					}
					// no default

					switch (dtask.state) {

						case 0:
							// console.log('开始下载');
							this.downloadStatus = 0
							break;
						case 2:
							// console.log('链接到服务器...');
							this.downloadStatus = 2
							break;

						case 3:
							this.program = parseInt(dtask.downloadedSize / dtask.totalSize * 100)
							this.downloadStatus = 3
							break;

						case 4:
							// console.log('监听下载完成');
							this.downloadStatus = 4
							break;
						case 5:

							// console.log('下载任务已暂停');
							this.downloadStatus = 5
							break;
					}
				});
				this.dtask.start();
				// console.log(this.dtask)
			},
			//利用本地缓存进行下载管理
			localStorageCheck() {
				plus.storage.getItemAsync('downLoadList_', success => {

					this.downLoadInfoValue = JSON.parse(success.data)

					if (this.downLoadInfoValue.data.length != 0) {

						this.beDownLoadedFlag = this.downLoadInfoValue.data.some(el => {
							//对比请求来的接口数据和本地存在的value
							this.filePath = el.fullPath
							return el.path === this.downLoadInfo.object.down_url
						})
						if (this.beDownLoadedFlag) {
							uni.showToast({
								title: '本课件已下载,可直接打开',
								icon: 'none'
							})
						}
					} else if (this.downLoadInfoValue.data.length == 0) {
						console.log('下载记录为空')
						
					}
				}, error => {
					// console.log(error)
					if (error.code == -3) {
						// plus.storage.setItem('downLoadList', []);
						uni.setStorage({ //这里的逻辑可以移植到登录页,登录成功后生成key
							key: 'downLoadList_',
							data: [],
							success: () => {
								this.downLoadInfoValue = JSON.parse(plus.storage.getItem(
									"downLoadList_"))
								console.log();
							}
						});
					}
				});
				// console.log(plus.storage.getItem('downLoadList'))
			},
			pause(flag) {
				if (flag === true) {
					this.dtask.pause()
					this.resumeOrpause = !this.resumeOrpause
				} else if (flag === false) {
					this.dtask.resume()
					this.resumeOrpause = !this.resumeOrpause
				}
			},
			openFile() {
				plus.runtime.openFile(this.filePath);
				// plus.runtime.openFile('/storage/emulated/0/Android/data/io.dcloud.HBuilder/downloads/厚涂入门技法讲解.pdf');
			},
			toFilesManagement() { //此方法不是该页面功能,下载页面不会再向下跳转。
				uni.navigateTo({
					url: '/pages/filelist/filelist'
				})
			},

			openThis() {
				//从缓存里取相应fullPath
				plus.runtime.openFile(this.fullPath);
			},
			cancle() {
				// 取消下载
				console.log('取消下载')
				plus.downloader.clear();
				//删除未完成文件
				this.$store.commit('REMOVE_UNFINISHED_FILE', this.dtask.filename)
			},
		},

  这里会出现个小问题就是如果是在文件没有下载完成的情况下,用户退出该页面。原理上讲,因为下载任务是调用了手机端的原生api和页面生命周期不构成关系。但是为了合乎使用逻辑,我在组件销毁后清除下载任务并删除为完成的文件。当然如果需要下载任务在后台运行的这里可以忽略。

mutations: {
		REMOVE_UNFINISHED_FILE(state, value) {
			plus.io.requestFileSystem(plus.io.PUBLIC_DOWNLOADS, entry => {
				//创建目录对象的读取对象
				var directoryReader = entry.root.createReader();
				directoryReader.readEntries(fs => {
					//使用some方法提高性能
					fs.some((el,index,arr) => {
						// console.log(index+'-'+arr.length)
							if (el.toURL() === value) {
								el.remove(success => {
									console.log('删除未完成的:'+value)
								}, e => {})
								return true
							}
						})
				})
			}, e => {
				console.log('error')
			})
		}

	},

此处利用的是vuex的muations,因为在组件销毁前处理复杂逻辑业务需要时间,绝大多数是无法在组件销毁前完成的,所以转移到store里是比较不错的思路。

然后就是对下载文件的管理:这里用到了plus的文件管理系统api。默认路径为app的沙盒plus.io.PUBLIC_DOWNLOADS,相关的知识可以自行阅读plus文档。

plus.io.requestFileSystem(plus.io.PUBLIC_DOWNLOADS, entry => {
				//创建目录对象的读取对象
				this.directoryReader = entry.root.createReader();
				this.list()
			}, e => {
				console.log('error')
			})
			//获取缓存文件列表			
			plus.storage.getItemAsync('downLoadList_', success => {

				this.downLoadInfoValue = JSON.parse(success.data)
				// console.log(this.downLoadInfoValue)
			}, e => {
				console.log('获取下载文件缓存失败')
			})

上述代码写在onLoad函数,完成移动端对文件读写操作对象的实例化。

  在plus和uniapp的api使用对比过程中,会发现其实uniapp只是对plus的大多数api进行了二次封装,比如打开文件这个功能,如果用plus会顺畅许多也不会有失效问题。而uniapp时常因为未知原因导致api失效,而plus不会。比如   plus.runtime.openFile(this.fullPath);,打开本地文件这个功能。
  

剩下的就是将缓存,本地文件还有下载任务的统一协调。总体思路并不复杂,关键还是不能局限于uniapp的有限api,毕竟uniapp的底层代码也都是调用的plus。希望大家看完鄙人的浅见能有所收获和启发。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐