HarmonyOS 鸿蒙Next中webview打开h5链接下载问题

HarmonyOS 鸿蒙Next中webview打开h5链接下载问题 我现在的问题是用鸿蒙自带的webview打开一个h5页面,这个h5页面有个下载动作,是js自己的下载动作,无鸿蒙提供方法支持,这里想实现的就是纯h5下载, 目前现状是点击了没反应,是在鸿蒙的webview里必须下载鸿蒙的包吗 不能下载其他的包吗?还是怎么,请各位大佬有知道的麻烦说下,谢谢了

8 回复

Web 组件里 H5 触发下载后,应用侧需要接管下载任务。官方 ArkWeb 文档说明,应通过 WebDownloadDelegate.onBeforeDownload 接收下载开始前的回调,并在回调中调用 WebDownloadItem.start(“保存路径”)。如果不调用 start 并提供下载路径,下载任务会保持 PENDING。

实现方式是:创建 WebDownloadDelegate,在 onBeforeDownload 里取 item.getSuggestedFileName() 拼接应用可写路径,例如应用沙箱 cache 目录,然后调用 item.start(path),最后通过 controller.setDownloadDelegate(delegate) 设置下载代理。这个机制和下载的是否是鸿蒙安装包无关,普通 H5 文件下载也需要这样处理。

依据:

WebDownloadDelegate.onBeforeDownload

WebDownloadItem

更多关于HarmonyOS 鸿蒙Next中webview打开h5链接下载问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这个不是“只能下载鸿蒙包”的问题,本质上是 ArkWeb/WebView 对 H5 下载行为默认不会自动接管。

很多 H5 的下载:

window.location.href = xxx

或者:

a.download
blob
application/octet-stream

在普通浏览器里会触发下载。

但是 HarmonyOS WebView 默认:

  • 不会自动帮你下载文件
  • 需要宿主侧接管下载事件
  • 否则就是“点击没反应”

所以:

不是不能下载 apk/zip/pdf/exe 之类文件。

而是:

WebView 没有实现下载监听。

———————— 解决方案:

需要监听:

onDownloadStart

然后自己处理下载逻辑。

例如:

Web({
  src: this.url,
  controller: this.controller
})
.onDownloadStart((event) => {
  console.info('下载链接:' + event.url)

  // 可以调用系统浏览器
  // 或自己下载
})

event 里面一般会有:

event.url
event.userAgent
event.contentDisposition
event.mimetype
event.contentLength

———————— 常见处理方式:

方案1(最简单):

直接拉起系统浏览器下载。

import common from '@ohos.app.ability.common';
import wantConstant from '@ohos.app.ability.wantConstant';

let want = {
  action: 'ohos.want.action.viewData',
  uri: event.url
}

context.startAbility(want)

这个最稳。

很多商业 App 都这么干。

———————— 方案2:

自己实现下载器。

使用:

@ohos.request

或者:

http.createHttp()

下载文件。

适合:

  • 显示下载进度
  • 断点续传
  • 自定义保存目录

———————— 另外有几个坑。

1、blob 下载

很多 Vue/React H5:

URL.createObjectURL(blob)

ArkWeb 某些版本不支持 blob download。

表现就是:

点击无反应。

这种一般要:

前端改成真实下载链接。

例如:

不要:

blob:

改成:

https://xxx/test.apk

———————— 2、a.download 属性

ArkWeb 对:

<a download>

兼容性一般。

尤其:

  • canvas导出
  • excel导出
  • blob导出

经常失效。

建议前端:

改后端真实文件流。

———————— 3、跨域问题

有些下载链接:

  • cookie鉴权
  • referer校验
  • cors限制

WebView 请求不到。

这时候:

系统浏览器能下载

WebView 不能下载

非常常见。

———————— 总结一句:

鸿蒙 WebView 不是不能下载别的包。

而是:

H5 下载行为默认不会自动处理。

正确做法是:

.onDownloadStart(...)

宿主接管下载。

用ArkWeb的webView的话,要下载网页里的下载,需要通过WebDownloadDelegate来操作。
这个用arkWeb打开网页,下载保存到手机存储,支持用户选择手机存储路径。
参考《这个》文章的示例。

不是啊,并非只能下载鸿蒙相关的包。WebView需要应用侧主动设置下载委托(WebDownloadDelegate)来拦截并处理下载请求,才能实现任意文件(如APK、文档、图片等)的下载。

你先为WebView设置WebDownloadDelegate,并在其回调中启动下载、指定保存路径。

先确保权限已经加了

"permissions": [
  "ohos.permission.INTERNET",
  "ohos.permission.READ_USER_STORAGE",
  "ohos.permission.WRITE_USER_STORAGE"
]

在给 WebView 绑定 DownloadDelegate

import web_webview from '@ohos.web.webview';
import business_error, { BusinessError } from '@ohos.base';
import { picker } from '@kit.CoreFileKit';
import fs from '@ohos.file.fs';
import { fileUri } from '@kit.CoreFileKit';

@Entry
@Component
struct WebDownloadFilePage {
  controller: web_webview.WebviewController = new web_webview.WebviewController();
  delegate: web_webview.WebDownloadDelegate = new web_webview.WebDownloadDelegate();

  build() {
    Column() {
      Button('setDownloadDelegate')
        .onClick(() => {
          try {
            this.delegate.onBeforeDownload((webDownloadItem: web_webview.WebDownloadItem) => {
              console.log("======will start a download." + webDownloadItem.getSuggestedFileName());
              webDownloadItem.start("/data/storage/el2/base/cache/web/" + webDownloadItem.getSuggestedFileName());
            })
            this.delegate.onDownloadUpdated((webDownloadItem: web_webview.WebDownloadItem) => {
              console.log("======download update guid: " + webDownloadItem.getGuid());
            })
            this.delegate.onDownloadFailed((webDownloadItem: web_webview.WebDownloadItem) => {
              console.log("download failed guid: " + webDownloadItem.getGuid());
            })
            this.delegate.onDownloadFinish((webDownloadItem: web_webview.WebDownloadItem) => {
              console.log("======download finish guid: " + webDownloadItem.getGuid());
              console.log("======downloadFullPath:" + webDownloadItem.getFullPath())
              let documentSaveOptions = new picker.DocumentSaveOptions();
              documentSaveOptions.newFileNames = [webDownloadItem.getSuggestedFileName()];
              let documentPicker = new picker.DocumentViewPicker();
              documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD;
              documentPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
                let uri = documentSaveResult$1;
                let path: string = new fileUri.FileUri(uri).path
                let sanPath = "/data/storage/el2/base/cache/web/" + webDownloadItem.getSuggestedFileName()
                let sanFile = fs.openSync(sanPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
                let pubFile = fs.openSync(path + '/***.pdf', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
                fs.copyFileSync(sanFile.fd, pubFile.fd)
                console.info('DocumentViewPicker.save successfully, DocumentSaveResult uri: ' + JSON.stringify(documentSaveResult));
              }).catch((err: BusinessError) => {
                console.error('DocumentViewPicker.save failed with err: ' + JSON.stringify(err));
              });
            })
            this.controller.setDownloadDelegate(this.delegate);
          } catch (error) {
            let e: business_error.BusinessError = error as business_error.BusinessError;
            console.error(`ErrorCode: ${e.code}, Message: ${e.message}`);
          }
        })
      Button('startDownload')
        .onClick(() => {
          try {
            this.controller.startDownload('https://***.pdf');
          } catch (error) {
            let e: business_error.BusinessError = error as business_error.BusinessError;
            console.error(`ErrorCode: ${e.code}, Message: ${e.message}`);
          }
        })
      Web({ src: 'www.huawei.com', controller: this.controller })
        .fileAccess(true)
        .javaScriptAccess(true)
        .domStorageAccess(true)
    }.margin({ top: 200 })
  }
}

你好,监听下载后使用DocumentViewPicker拉起保存,参考使用ArkWeb下载文件提示完成但文件为何为空

import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
import { fileUri, picker } from '@kit.CoreFileKit';

@Entry
@Component
struct WebComponent {
  controller: webview.WebviewController = new webview.WebviewController();
  delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate();

  build() {
    Column() {
      Button('setDownloadDelegate')
        .onClick(() => {
          // 下载开始前通知给用户,用户需要在此接口中调用WebDownloadItem.start("xxx")并提供下载路径,否则下载会一直处于PENDING状态。
          this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => {
            console.info('EntryAbility: will start a download.');
            const documentSaveOptions = new picker.DocumentSaveOptions();
            // 修改pickerMode为DOWNLOAD,删除newFileNames和fileSuffixChoices,设置为DOWNLOAD时,配置的参数newFileNames和fileSuffixChoices将不会生效
            documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD;
            let uris: Array<string> = [];
            let documentViewPicker = new picker.DocumentViewPicker();
            documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
              // 固定返回当前HAP包名的文件夹
              uris = documentSaveResult;
              if (0 == uris.length) {
                return;
              }
              console.info(`EntryAbility: documentViewPicker.save to file succeed and uris are:${uris}`);

              const uriString = documentSaveResult[0];
              if (!uriString) {
                return;
              }
              // 添加文件路径转换
              const uri = new fileUri.FileUri(uriString);
              webDownloadItem.start(uri.path + '/fileName_' + (new Date()).getTime() + '.txt');
              console.info(`EntryAbility: download to ${uri.path}`);
            }).catch((err: BusinessError) => {
              console.error(`EntryAbility: Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
            });
          });

          this.controller.setDownloadDelegate(this.delegate);
        });
      Button('startDownload')
        .onClick(() => {
          try {
            // 运行时需替换为实际的链接
            this.controller.startDownload('XXX');
          } catch (error) {
            console.error(`ErrorCode: ${(error as BusinessError).code},  Message: ${(error as BusinessError).message}`);
          }
        });
      // 运行时需替换为实际的链接
      Web({ src: 'XXX', controller: this.controller }).fileAccess(false).geolocationAccess(false);
    };
  }
}

Web 组件不会像系统浏览器一样自动把所有 H5 下载动作接管并落盘,所以点击 H5 里的下载按钮没反应,通常不是“只能下载鸿蒙包”或文件类型限制,而是宿主应用没有处理下载链路。

普通 http/https 文件链接可以先在 Web 上监听 onDownloadStart,回调里能拿到 url、userAgent、contentDisposition、mimetype、contentLength 等信息;如果还需要进度/开始前控制,可以再用 WebDownloadDelegate 这类下载委托能力接管。拿到下载地址后,由 ArkTS 侧用 request.downloadFile 或网络请求下载到应用沙箱,再通过 Picker/SaveButton 等方式让用户保存到外部位置。

如果 H5 是 blob:、base64、a[download] 或 JS 动态生成文件,ArkTS 侧可能拿不到一个真实的可下载 URL,这种情况建议让 H5 通过 JSBridge(registerJavaScriptProxy / javaScriptProxy)把后端下载 URL 或文件内容传给应用侧,再由应用侧保存。还要确认 module.json5 里有 INTERNET 权限,保存到用户可见目录时走系统授权/安全控件。

在HarmonyOS Next中,WebView打开H5链接下载通过WebviewControlleronDownloadStart回调处理,需实现DownloadListener接口,在其中调用downloadManager请求下载。需配置ohos.permission.INTERNET及文件读写权限。下载文件默认保存在应用沙箱,可通过fileAccess扩展控制。

在 HarmonyOS Next 的 WebView 中,出于安全与生态管控要求,默认禁止 H5 直接触发非应用商店来源的“自由下载”。所以纯 JS 下载动作(如通过 a 标签 download 属性或 blob 生成链接点击)可能无响应,并不是必须下载鸿蒙包,而是需要原生端配合拦截并接管下载流程。

解决办法: 在原生侧通过 onDownloadStartonLoadIntercept 事件监听 WebView 的下载意图,再使用 @ohos.requestdownload 接口将文件写入沙箱或公共目录,完成后可通知用户。例如简单示例:

webview.on('downloadStart', (event) => {
  // 通过 event.url 获取下载地址
  request.downloadFile(context, {
    url: event.url,
    filePath: '/path/to/save' // 沙箱路径
  }).then(() => {
    // 下载完成
  });
  // 阻止 WebView 默认行为
  event.preventDefault();
});

这样就能实现类似纯 H5 下载的效果,但实际依赖原生能力。

回到顶部