1# 使用Web组件的下载能力 2 3> **说明:** 4> 5>Web组件的下载功能要求应用通过调用WebDownloadItem.start来指定下载文件的保存路径。值得注意的是,WebDownloadItem.start并非启动下载,下载过程实际上在用户点击页面链接时即已开始。WebDownloadItem.start的作用是将已经下载到临时文件的部分移动到指定目标路径,后续未完成的下载的内容将直接保存到指定目标路径,临时目录位于`/data/storage/el2/base/cache/web/Temp/`。如果决定取消当前下载,应调用WebDownloadItem.cancel,此时临时文件将被删除。 6> 7>如果不希望在WebDownloadItem.start之前将文件下载到临时目录,可以通过WebDownloadItem.cancel中断下载,后续可通过[WebDownloadManager.resumeDownload](../reference/apis-arkweb/js-apis-webview.md#resumedownload11)恢复中断的下载。 8 9## 监听页面触发的下载 10 11通过[setDownloadDelegate()](../reference/apis-arkweb/js-apis-webview.md#setdownloaddelegate11)向Web组件注册一个DownloadDelegate来监听页面触发的下载任务。资源由Web组件来下载,Web组件会通过DownloadDelegate将下载的进度通知给应用。 12 13下面的示例中,在应用的rawfile中创建index.html以及download.html。应用启动后会创建一个Web组件并加载index.html,点击setDownloadDelegate按钮向Web组件注册一个DownloadDelegate,点击页面里的下载按钮的时候会触发一个下载任务,在DownloadDelegate中可以监听到下载的进度。 14 15```ts 16// xxx.ets 17import { webview } from '@kit.ArkWeb'; 18import { BusinessError } from '@kit.BasicServicesKit'; 19 20@Entry 21@Component 22struct WebComponent { 23 controller: webview.WebviewController = new webview.WebviewController(); 24 delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate(); 25 26 build() { 27 Column() { 28 Button('setDownloadDelegate') 29 .onClick(() => { 30 try { 31 this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => { 32 console.log("will start a download."); 33 // 传入一个下载路径,并开始下载。 34 // 如果传入一个不存在的路径,则会下载到默认/data/storage/el2/base/cache/web/目录。 35 webDownloadItem.start("/data/storage/el2/base/cache/web/" + webDownloadItem.getSuggestedFileName()); 36 }) 37 this.delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => { 38 // 下载任务的唯一标识。 39 console.log("download update guid: " + webDownloadItem.getGuid()); 40 // 下载的进度。 41 console.log("download update guid: " + webDownloadItem.getPercentComplete()); 42 // 当前的下载速度。 43 console.log("download update speed: " + webDownloadItem.getCurrentSpeed()) 44 }) 45 this.delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => { 46 console.log("download failed guid: " + webDownloadItem.getGuid()); 47 // 下载任务失败的错误码。 48 console.log("download failed guid: " + webDownloadItem.getLastErrorCode()); 49 }) 50 this.delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => { 51 console.log("download finish guid: " + webDownloadItem.getGuid()); 52 }) 53 this.controller.setDownloadDelegate(this.delegate); 54 } catch (error) { 55 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 56 } 57 }) 58 Web({ src: $rawfile('index.html'), controller: this.controller }) 59 } 60 } 61} 62``` 63 64加载的html文件。 65```html 66<!-- index.html --> 67<!DOCTYPE html> 68<html> 69<body> 70// 点击视频右下方菜单的下载按钮会触发下载任务。 71<video controls="controls" width="800px" height="580px" 72 src="http://vjs.zencdn.net/v/oceans.mp4" 73 type="video/mp4"> 74</video> 75<a href='data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E' download='download.html'>下载download.html</a> 76</body> 77</html> 78``` 79 80待下载的html文件。 81```html 82<!-- download.html --> 83<!DOCTYPE html> 84<html> 85<body> 86<h1>download test</h1> 87</body> 88</html> 89``` 90 91## 使用Web组件发起一个下载任务 92 93使用[startDownload()](../reference/apis-arkweb/js-apis-webview.md#startdownload11)接口发起一个下载。 94Web组件发起的下载会根据当前显示的url以及Web组件默认的Referrer Policy来计算referrer。 95 96 在下面的示例中,先点击setDownloadDelegate按钮向Web注册一个监听类,然后点击startDownload主动发起了一个下载, 97 该下载任务也会通过设置的DownloadDelegate来通知app下载的进度。 98 99```ts 100// xxx.ets 101import { webview } from '@kit.ArkWeb'; 102import { BusinessError } from '@kit.BasicServicesKit'; 103 104@Entry 105@Component 106struct WebComponent { 107 controller: webview.WebviewController = new webview.WebviewController(); 108 delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate(); 109 110 build() { 111 Column() { 112 Button('setDownloadDelegate') 113 .onClick(() => { 114 try { 115 this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => { 116 console.log("will start a download."); 117 // 传入一个下载路径,并开始下载。 118 webDownloadItem.start("/data/storage/el2/base/cache/web/" + webDownloadItem.getSuggestedFileName()); 119 }) 120 this.delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => { 121 console.log("download update guid: " + webDownloadItem.getGuid()); 122 }) 123 this.delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => { 124 console.log("download failed guid: " + webDownloadItem.getGuid()); 125 }) 126 this.delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => { 127 console.log("download finish guid: " + webDownloadItem.getGuid()); 128 }) 129 this.controller.setDownloadDelegate(this.delegate); 130 } catch (error) { 131 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 132 } 133 }) 134 Button('startDownload') 135 .onClick(() => { 136 try { 137 // 这里指定下载地址为 https://www.example.com/,Web组件会发起一个下载任务将该页面下载下来。 138 // 开发者需要替换为自己想要下载的内容的地址。 139 this.controller.startDownload('https://www.example.com/'); 140 } catch (error) { 141 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 142 } 143 }) 144 Web({ src: 'www.example.com', controller: this.controller }) 145 } 146 } 147} 148``` 149 150 151## 使用Web组件恢复进程退出时未下载完成的任务 152在Web组件启动时,可通过[resumeDownload()](../reference/apis-arkweb/js-apis-webview.md#resumedownload11)接口恢复未完成的下载任务。 153 154在以下示例中,通过“record”按钮将当前下载任务保存至持久化文件中,应用重启后,可借助“recovery”按钮恢复持久化的下载任务。示例代码实现了将当前下载任务持久化保存至文件的功能,若需保存多个下载任务,应用可根据需求调整持久化的时机与方式。 155```ts 156// xxx.ets 157import { webview } from '@kit.ArkWeb'; 158import { BusinessError } from '@kit.BasicServicesKit'; 159import { downloadUtil, fileName, filePath } from './downloadUtil'; // downloadUtil.ets 见下文 160 161@Entry 162@Component 163struct WebComponent { 164 controller: webview.WebviewController = new webview.WebviewController(); 165 delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate(); 166 download: webview.WebDownloadItem = new webview.WebDownloadItem(); 167 // 用于记录失败的下载任务。 168 failedData: Uint8Array = new Uint8Array(); 169 170 build() { 171 Column() { 172 Button('setDownloadDelegate') 173 .onClick(() => { 174 try { 175 this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => { 176 console.log("will start a download."); 177 // 传入一个下载路径,并开始下载。 178 webDownloadItem.start("/data/storage/el2/base/cache/web/" + webDownloadItem.getSuggestedFileName()); 179 }) 180 this.delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => { 181 console.log("download update percent complete: " + webDownloadItem.getPercentComplete()); 182 this.download = webDownloadItem; 183 }) 184 this.delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => { 185 console.log("download failed guid: " + webDownloadItem.getGuid()); 186 // 序列化失败的下载任务到一个字节数组。 187 this.failedData = webDownloadItem.serialize(); 188 }) 189 this.delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => { 190 console.log("download finish guid: " + webDownloadItem.getGuid()); 191 }) 192 this.controller.setDownloadDelegate(this.delegate); 193 webview.WebDownloadManager.setDownloadDelegate(this.delegate); 194 } catch (error) { 195 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 196 } 197 }) 198 Button('startDownload') 199 .onClick(() => { 200 try { 201 // 这里指定下载地址为 https://www.example.com/,Web组件会发起一个下载任务将该页面下载下来。 202 // 开发者需要替换为自己想要下载的内容的地址。 203 this.controller.startDownload('https://www.example.com/'); 204 } catch (error) { 205 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 206 } 207 }) 208 // 将当前的下载任务信息序列化保存,用于后续恢复下载任务。 209 // 当前用例仅展示下载一个任务的场景,多任务场景请按需扩展。 210 Button('record') 211 .onClick(() => { 212 try { 213 // 保存当前下载数据到持久化文档中。 214 downloadUtil.saveDownloadInfo(downloadUtil.uint8ArrayToStr(this.download.serialize())); 215 } catch (error) { 216 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 217 } 218 }) 219 // 从序列化的下载任务信息中,恢复下载任务。 220 // 按钮触发时必须保证WebDownloadManager.setDownloadDelegate设置完成。 221 Button('recovery') 222 .onClick(() => { 223 try { 224 // 当前默认持久化文件存在,用户根据实际情况增加判断。 225 let webDownloadItem = 226 webview.WebDownloadItem.deserialize(downloadUtil.strToUint8Array(downloadUtil.readFileSync(filePath, fileName))); 227 webview.WebDownloadManager.resumeDownload(webDownloadItem); 228 } catch (error) { 229 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 230 } 231 }) 232 233 Web({ src: 'www.example.com', controller: this.controller }) 234 } 235 } 236} 237``` 238 239下载任务信息持久化工具类文件 240```ts 241// downloadUtil.ets 242import { util } from '@kit.ArkTS'; 243import fileStream from '@ohos.file.fs'; 244 245const helper = new util.Base64Helper(); 246 247export const filePath = getContext().filesDir; 248export const fileName = 'demoFile.txt'; 249export namespace downloadUtil { 250 251 export function uint8ArrayToStr(uint8Array: Uint8Array): string { 252 return helper.encodeToStringSync(uint8Array); 253 } 254 255 export function strToUint8Array(str: string): Uint8Array { 256 return helper.decodeSync(str); 257 } 258 259 export function saveDownloadInfo(downloadInfo: string): void { 260 if (!fileExists(filePath)) { 261 mkDirectorySync(filePath); 262 } 263 264 writeToFileSync(filePath, fileName, downloadInfo); 265 } 266 267 export function fileExists(filePath: string): boolean { 268 try { 269 return fileStream.accessSync(filePath); 270 } catch (error) { 271 return false; 272 } 273 } 274 275 export function mkDirectorySync(directoryPath: string, recursion?: boolean): void { 276 try { 277 fileStream.mkdirSync(directoryPath, recursion ?? false); 278 } catch (error) { 279 console.error(`mk dir error. err message: ${error.message}, err code: ${error.code}`); 280 } 281 } 282 283 export function writeToFileSync(dir: string, fileName: string, msg: string): void { 284 let file = fileStream.openSync(dir + '/' + fileName, fileStream.OpenMode.WRITE_ONLY | fileStream.OpenMode.CREATE); 285 fileStream.writeSync(file.fd, msg); 286 } 287 288 export function readFileSync(dir: string, fileName: string): string { 289 return fileStream.readTextSync(dir + '/' + fileName); 290 } 291 292} 293```