我用React开发一个前端应用,客户加需求需要安卓的App。为了前端代码能够复用,我就用了HBuilderX
将React build后的包打成Apk。
普通文件下载
在开发过程中需要下载文件,我都是通过创建a
标签来实现的,如下:
export const downloadFile = (url, fileName?) => {
const dwldLink = document.createElement('a');
dwldLink.setAttribute('href', url);
dwldLink.style.visibility = 'hidden';
if(fileName) dwldLink.download = fileName;
document.body.appendChild(dwldLink);
dwldLink.click();
// remove it after click
document.body.removeChild(dwldLink);
}
这可以满足很多文件下载的需要,而且不需要代码里关系,都被浏览器和webview管理了,很方便。
带请求头下载
后面遇到一个需要鉴权的下载,需要携带请求头,这也难不倒我,我很快就写好了代码:
const downloadTask = (url, fileName, token) => {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "blob"; // 声明返回类型为二进制数据
xhr.setRequestHeader("Authorization", token); // 设置自定义请求头
xhr.addEventListener("progress", function(event) {
if (event.lengthComputable) {
const p = (event.loaded / event.total) * 100;
setDownloadPercent(p.toFixed()); //下载进度进度条
}
});
xhr.onerror = onError;
xhr.onload = function() {
if (xhr.status === 200) {
setDownloadPercent(100)
const blob = new Blob([xhr.response], { type: "application/octet-stream" });
const downloadUrl = URL.createObjectURL(blob);
// 创建下载链接并点击触发下载
const link = document.createElement("a");
link.href = downloadUrl;
link.download = fileName;
link.click();
// 清理对象 URL
URL.revokeObjectURL(downloadUrl);
setTimeout(close, 500); // 关闭/回调
}
};
xhr.send();
}
虽然js下载会用到浏览器缓存,但是文件不会很大,也很轻松搞定了。 这是我再打包apk,出现问题了,下载文件名变成了一串字符串,并不是我提供的fileName, 而且下载也失败了
这又是什么问题呢?
我通过Android studio的日志看到Apk只支持http/https
的下载链接,不支持blob。
这时,我通过查看HbuilderX的文档发现了
Native.js
插件,经过一番查看API, 我又实现了:
const downloadTaskPlus = (url, fileName, token) => {
const dtask = plus.downloader.createDownload(url,{
filename:"_downloads/"+ fileName, //路径-沙盒路径 查不到
timeout: 1,
},function(d, status){
//d为下载的文件对象
if(status == 200){
setTimeout(close, 500);
message.success('保存成功');
}else{
//下载失败
onError({message: '下载失败, 请稍后再试'})
plus.downloader.clear(); //清除下载任务
}
})
dtask.addEventListener('statechanged', (task) => {
if (!dtask) {
return;
}
// no default
switch (task.state) {
case 3: //下载进度进度条
const p = (task.downloadedSize / task.totalSize) * 100;
setDownloadPercent(p.toFixed())
break;
}
});
dtask.setRequestHeader('Authorization', token);
dtask.start();
}
这还有个问题,虽然下载成功了,但是文档在文件管理器里搜索不到,只能在成功的一刻选择打开,然后保存,就在我怀疑并没有真实下载的时候。我用华为文件管理器的深度搜索搜索到了文件。在经过我仔细研读API后,我发现:
_downloads
是对应Android系统应用外部存储目录(通常为/sdcard/Android/data/%PACKAGENAME%/
,其中%PACKAGENAME%
是程序的包名)下的downloads
目录。
于是我使用plus
的IO
功能:
plus.io.resolveLocalFileSystemURL( d.filename , function(entry){
plus.io.resolveLocalFileSystemURL('file:///storage/emulated/0/Download', function(dstEntry){
entry.moveTo( dstEntry, fileName, function(res){
message.success('保存成功, 文件保存路径为:' + '/Download/' + fileName);
}, function(e){
console.error( JSON.stringify(e) );
});
}, function(e){
console.error( JSON.stringify(e) );
});
}, function(e){
console.error( JSON.stringify(e) );
})
但是并没有成功,查看了报错提示:
targetSdkVersion设置>=29后在Android10+系统设备不支持当前路径。请更改为应用运行路径! 具体请看:ask.dcloud.net.cn/article/361…
我们能操作的目录大多为应用沙盒目录(应用专属目录)
- 应用沙盒目录只能自己直接访问
- App卸载,数据会清除。
- plus.io中的DirectoryEntry对象的removeRecursively、getDirectory、getFile、remove、copyTo、moveTo仅支持应用沙盒目录
最终我放弃了这一步添加了下载完成时打开
plus.runtime.openFile(d.filename); //选择软件打开文件