H5+的Android应用如何下载文件

554 阅读3分钟

我用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。

image.png 这时,我通过查看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目录。

于是我使用plusIO功能:

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…

我们能操作的目录大多为应用沙盒目录(应用专属目录)

最终我放弃了这一步添加了下载完成时打开

    plus.runtime.openFile(d.filename);    //选择软件打开文件