鸿蒙专家 发表于 2026-8-5 09:00:00

HarmonyOS 6.1.1 ArkWeb下载溯源:getOrigin

在混合开发与浏览器架构中,下载拦截和管控能力直接体现内核的控制力。用户点击下载链接时,请求往往经历多次301/302重定向,服务端还可能用防盗链机制校验Referrer。过去在HarmonyOS上,开发者通过ArkWeb的WebDownloadItem拦截下载任务时,只能拿到最终地址或数据流,难以做源头审计,也无法在自研下载器中透传原始上下文。

HarmonyOS NEXT 6.1.1(API 24)针对ArkWeb下载控制模块新增了两个关键接口:getOriginalUrl()还原发起下载的初始URL,忽略中间重定向;getReferrerUrl()提取引发下载的引荐页地址,为第三方下载器补充防盗链所需的Referer字段。这两个接口属于@kit.ArkWeb套件中的下载委托层WebDownloadDelegate,仅在Stage模型下可用,无需申请特殊权限。

接口声明如下:

// 获取下载文件的原始URL(重定向前用户点击或系统请求的第一现场地址)
getOriginalUrl(): string;

// 获取下载文件的referrer地址(用户点击下载链接前停留的网页)
getReferrerUrl(): string;


实际开发中,典型的痛点是:很多大厂的下载链接采用统一分发调度,例如用户点击 https://d.example.com/latest,服务端返回302跳转到带签名和CDN节点的真实长链接。如果开发者接管下载后只拿得到真实长链接,无法与业务中台核对发版配置,统计归因也会失真。getOriginalUrl能够直接返回用户最初点击的/latest地址,让业务侧看清下载源头。

另一个高频场景是防盗链。企业私有附件系统通常设置严格的Referrer校验,如果直接把WebDownloadItem中的最终链接交给request模块做断点续传,会因缺少原始上下文而收到403。通过getReferrerUrl()拿到引荐页地址后,在自定义HTTP请求头中填入Referer字段,即可正常通过防盗链校验。

下面用一个完整的Demo说明接入流程。页面包含输入框和按钮,隐藏一个Web实例承载内核。核心逻辑在setupDownloadDelegate中:挂载WebDownloadDelegate,在onBeforeDownload回调里拦截下载任务,调用新增接口打印溯源信息,最后指定沙盒路径调用start方法启动下载。


import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct ArkWebDemo {
controller: webview.WebviewController = new webview.WebviewController();
delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate();
@State logs: string[] = [];
@State inputUrl: string = 'https://www.example.com/download/test.zip';

private appendLog(msg: string) {
    let now = new Date();
    let timeStr = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}.${now.getMilliseconds()}`;
    this.logs.unshift(`[${timeStr}] ${msg}`);
}

aboutToAppear() {
    this.appendLog('ArkWeb 下载溯源控制舱已初始化');
    this.setupDownloadDelegate();
}

setupDownloadDelegate() {
    try {
      this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => {
      // 获取未发生重定向前的起始请求地址
      let originalUrl = webDownloadItem.getOriginalUrl();
      // 获取触发下载事件的引荐页面来源
      let referrerUrl = webDownloadItem.getReferrerUrl();
      let guid = webDownloadItem.getGuid();
      let fileName = webDownloadItem.getSuggestedFileName();

      this.appendLog(`拦截到下载请求 `);
      this.appendLog(`原始触发 URL: ${originalUrl}`);
      this.appendLog(`Referrer 溯源: ${referrerUrl}`);
      this.appendLog(`建议文件名: ${fileName}`);

      // 拼接沙盒下载路径,写入应用el2 cache目录
      let downloadPath = "/data/storage/el2/base/cache/web/" + fileName;
      this.appendLog(`开始写入沙盒路径: ${downloadPath}`);

      // 允许内核接管写入流程,必须显式调用
      webDownloadItem.start(downloadPath);
      });

      this.delegate.onDownloadUpdated((item) => this.appendLog(`进度: ${item.getPercentComplete()}%`));
      this.delegate.onDownloadFailed((item) => this.appendLog(`下载失败 `));
      this.delegate.onDownloadFinish((item) => this.appendLog(`下载完成 `));

      this.controller.setDownloadDelegate(this.delegate);
      this.appendLog('下载委托监听已挂载');
    } catch (error) {
      let err = error as BusinessError;
      this.appendLog(`委托挂载失败 Msg: ${err.message}`);
    }
}

triggerDownload() {
    try {
      this.appendLog(`尝试触发下载: ${this.inputUrl}`);
      this.controller.startDownload(this.inputUrl);
    } catch (error) {
      let err = error as BusinessError;
      this.appendLog(`触发下载异常 Msg: ${err.message}`);
    }
}

build() {
    Column() {
      Text('ArkWeb 下载溯源防线').fontSize(22).fontWeight(FontWeight.Bold).margin({ top: 40, bottom: 20 })
      TextInput({ text: this.inputUrl }).onChange((val) => this.inputUrl = val).width('90%').margin({ bottom: 10 })
      Button('触发下载并溯源').onClick(() => this.triggerDownload()).backgroundColor('#007DFF').width('90%').margin({ bottom: 20 })
      List({ space: 8 }) {
      ForEach(this.logs, (log: string) => {
          ListItem() { Text(log).fontSize(12).fontFamily('monospace').fontColor('#333333') }
      }, (log: string) => log)
      }.width('90%').height('40%').backgroundColor('#EFEFEF').padding(10).borderRadius(8)
      Web({ src: 'www.example.com', controller: this.controller }).width(1).height(1).visibility(Visibility.Hidden)
    }.width('100%').height('100%')
}
}


运行Demo后,点击“触发下载并溯源”,日志会依次展示:委托挂载成功、触发下载的URL、onBeforeDownload回调中提取出的原始URL与Referrer、建议文件名,以及沙盒写入路径,最终显示下载完成。在直连无重定向的测试环境下,原始URL和Referrer可能一致或为空;在真实网页嵌套和重定向场景中,这两个值能精确刻画用户的点击跳转图谱。

需要特别注意的是生命周期闭环。onBeforeDownload回调拦截WebDownloadItem后,相当于完全劫持了下载流程,必须显式调用start(path)传入合法写入路径;如果决定不下载,也要做相应销毁处理。拦截后既不调用start也不做任何清理,内核引擎会陷入等待流状态,可能诱发底层资源泄漏甚至崩溃。

HarmonyOS 6.1.1通过这两个接口补齐了ArkWeb下载链路的溯源闭环。应用侧既能摆脱无法感知前置重定向的被动局面,又能在接管下载时复刻HTTP上下文,安全控制颗粒度明显提升。对于需要自研下载器、统计归因或对接防盗链系统的团队,这两个API值得尽快适配验证。

热心网友3 发表于 2026-8-5 09:05:00

Re: HarmonyOS 6.1.1 ArkWeb下载溯源:getOrigin

这个接口加得确实实用,之前处理重定向下载只能拿最终地址,查问题经常要自己拼日志还原现场。有了getOriginalUrl,至少能定位用户最初点了什么,跟业务侧核对配置也方便。 防盗链那个场景太真实了,自研下载器经常因为Referrer被拦,原来还得自己从Web里取上下文,现在委托层直接给了,省了不少事。楼主那个Demo流程也很清晰,onBeforeDownload里先溯源再start,逻辑挺完整的。等有空在API 24上试试,看看重定向多级之后返回的originalUrl是不是真的第一跳。

热心网友3 发表于 2026-8-5 09:05:00

Re: HarmonyOS 6.1.1 ArkWeb下载溯源:getOrigin

感谢分享!这个接口太实用了,尤其是getOriginalUrl,之前处理那些重定向下载链接时确实只能拿到最终地址,业务侧做归因统计特别麻烦。现在直接在拦截回调里就能拿到第一现场,省了很多手工拼接和回溯的工作。 另外防盗链场景也很真实,我们之前就遇到过类似情况,拿最终链接去请求结果被403,还得额外从请求头里扒Referer。这两个接口配合`onBeforeDownload`,能少写不少糙代码。 想问几个细节:`start(downloadPath)`里的路径如果目录不存在,ArkWeb会不会自动创建?还是需要开发者自己先mkdir?还有`getReferrerUrl()`是不是永远返回用户停留页,如果页面是通过JS的`location.href`触发的下载,还能拿到吗?期待楼主后续能再讲讲这些边界场景。

热心网友3 发表于 2026-8-5 09:05:00

Re: HarmonyOS 6.1.1 ArkWeb下载溯源:getOrigin

这个接口补得挺及时,之前处理重定向下载确实只能拿到最终地址,业务侧想做归因还得自己维护映射表,麻烦得很。现在getOriginalUrl直接给到初始URL,省了不少事。 另外getReferrerUrl对防盗链场景太实用了,以前自己拼Referer还得从Web页面里手动注入,现在委托层直接透传,第三方下载器接起来干净多了。Stage模型限定倒是没问题,反正新应用都推荐用Stage。 有个小疑问:`onDownloadUpdated`这个回调是不是下载过程中会频繁触发?如果文件大,日志刷屏会不会影响性能?还是说本身有节流机制?
页: [1]
查看完整版本: HarmonyOS 6.1.1 ArkWeb下载溯源:getOrigin