1# 使用Web组件的下载能力 2 3> **说明:** 4> 5>Web组件的下载功能要求应用通过调用[WebDownloadItem.start](../reference/apis-arkweb/js-apis-webview.md#start11)来指定下载文件的保存路径。值得注意的是,WebDownloadItem.start并非启动下载,下载过程实际上在用户点击页面链接时即已开始。WebDownloadItem.start的作用是将已经下载到临时文件的部分移动到指定目标路径,后续未完成的下载的内容将直接保存到指定目标路径,临时目录位于`/data/storage/el2/base/cache/web/Temp/`。如果决定取消当前下载,应调用[WebDownloadItem.cancel](../reference/apis-arkweb/js-apis-webview.md#cancel11),此时临时文件将被删除。 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 percent complete: " + 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 last error code: " + 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使用[DocumentViewPicker()](../reference/apis-core-file-kit/js-apis-file-picker.md#documentviewpicker)获取当前示例的默认下载目录,将该目录设置为下载目录。 151```ts 152// xxx.ets 153import { webview } from '@kit.ArkWeb'; 154import { BusinessError } from '@kit.BasicServicesKit'; 155import { picker, fileUri } from '@kit.CoreFileKit'; 156@Entry 157@Component 158struct WebComponent { 159 controller: webview.WebviewController = new webview.WebviewController(); 160 delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate(); 161 162 build() { 163 Column() { 164 Button('setDownloadDelegate') 165 .onClick(() => { 166 try { 167 this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => { 168 console.log("will start a download."); 169 // 使用DocumentViewPicker()获取当前示例的默认下载目录,将该目录设置为下载目录 170 getDownloadPathFromPicker().then((downloadPath) => { 171 webDownloadItem.start(downloadPath + '/' + webDownloadItem.getSuggestedFileName()); 172 }); 173 174 }) 175 this.delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => { 176 // 下载任务的唯一标识。 177 console.log("download update guid: " + webDownloadItem.getGuid()); 178 // 下载的进度。 179 console.log("download update percent complete: " + webDownloadItem.getPercentComplete()); 180 // 当前的下载速度。 181 console.log("download update speed: " + webDownloadItem.getCurrentSpeed()) 182 }) 183 this.delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => { 184 console.log("download failed guid: " + webDownloadItem.getGuid()); 185 // 下载任务失败的错误码。 186 console.log("download failed last error code: " + webDownloadItem.getLastErrorCode()); 187 }) 188 this.delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => { 189 console.log("download finish guid: " + webDownloadItem.getGuid()); 190 }) 191 this.controller.setDownloadDelegate(this.delegate); 192 } catch (error) { 193 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 194 } 195 }) 196 Web({ src: $rawfile('index.html'), controller: this.controller }) 197 } 198 } 199 200} 201function getDownloadPathFromPicker(): Promise<string> { 202 return new Promise<string>(resolve => { 203 try { 204 const documentSaveOptions = new picker.DocumentSaveOptions(); 205 documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD 206 const documentPicker = new picker.DocumentViewPicker(); 207 documentPicker.save(documentSaveOptions).then(async (documentSaveResult: Array<string>) => { 208 if (documentSaveResult.length <= 0) { 209 resolve(''); 210 return; 211 } 212 const uriString = documentSaveResult[0]; 213 if (!uriString) { 214 resolve(''); 215 return; 216 } 217 const uri = new fileUri.FileUri(uriString); 218 resolve(uri.path); 219 }).catch((err: BusinessError) => { 220 console.error(`ErrorCode: ${err.code}, Message: ${err.message}`); 221 resolve(''); 222 }); 223 } catch (error) { 224 resolve(''); 225 } 226 }) 227} 228``` 229 230## 使用Web组件恢复进程退出时未下载完成的任务 231在Web组件启动时,可通过[resumeDownload()](../reference/apis-arkweb/js-apis-webview.md#resumedownload11)接口恢复未完成的下载任务。 232 233在以下示例中,通过“record”按钮将当前下载任务保存至持久化文件中,应用重启后,可借助“recovery”按钮恢复持久化的下载任务。示例代码实现了将当前下载任务持久化保存至文件的功能,若需保存多个下载任务,应用可根据需求调整持久化的时机与方式。 234```ts 235// xxx.ets 236import { webview } from '@kit.ArkWeb'; 237import { BusinessError } from '@kit.BasicServicesKit'; 238import { downloadUtil, fileName, filePath } from './downloadUtil'; // downloadUtil.ets 见下文 239 240@Entry 241@Component 242struct WebComponent { 243 controller: webview.WebviewController = new webview.WebviewController(); 244 delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate(); 245 download: webview.WebDownloadItem = new webview.WebDownloadItem(); 246 // 用于记录失败的下载任务。 247 failedData: Uint8Array = new Uint8Array(); 248 249 aboutToAppear(): void { 250 downloadUtil.init(this.getUIContext()); 251 } 252 253 build() { 254 Column() { 255 Button('setDownloadDelegate') 256 .onClick(() => { 257 try { 258 this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => { 259 console.log("will start a download."); 260 // 传入一个下载路径,并开始下载。 261 webDownloadItem.start("/data/storage/el2/base/cache/web/" + webDownloadItem.getSuggestedFileName()); 262 }) 263 this.delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => { 264 console.log("download update percent complete: " + webDownloadItem.getPercentComplete()); 265 this.download = webDownloadItem; 266 }) 267 this.delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => { 268 console.log("download failed guid: " + webDownloadItem.getGuid()); 269 // 序列化失败的下载任务到一个字节数组。 270 this.failedData = webDownloadItem.serialize(); 271 }) 272 this.delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => { 273 console.log("download finish guid: " + webDownloadItem.getGuid()); 274 }) 275 this.controller.setDownloadDelegate(this.delegate); 276 webview.WebDownloadManager.setDownloadDelegate(this.delegate); 277 } catch (error) { 278 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 279 } 280 }) 281 Button('startDownload') 282 .onClick(() => { 283 try { 284 // 这里指定下载地址为 https://www.example.com/,Web组件会发起一个下载任务将该页面下载下来。 285 // 开发者需要替换为自己想要下载的内容的地址。 286 this.controller.startDownload('https://www.example.com/'); 287 } catch (error) { 288 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 289 } 290 }) 291 // 将当前的下载任务信息序列化保存,用于后续恢复下载任务。 292 // 当前用例仅展示下载一个任务的场景,多任务场景请按需扩展。 293 Button('record') 294 .onClick(() => { 295 try { 296 // 保存当前下载数据到持久化文档中。 297 downloadUtil.saveDownloadInfo(downloadUtil.uint8ArrayToStr(this.download.serialize())); 298 } catch (error) { 299 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 300 } 301 }) 302 // 从序列化的下载任务信息中,恢复下载任务。 303 // 按钮触发时必须保证WebDownloadManager.setDownloadDelegate设置完成。 304 Button('recovery') 305 .onClick(() => { 306 try { 307 // 当前默认持久化文件存在,用户根据实际情况增加判断。 308 let webDownloadItem = 309 webview.WebDownloadItem.deserialize(downloadUtil.strToUint8Array(downloadUtil.readFileSync(filePath, fileName))); 310 webview.WebDownloadManager.resumeDownload(webDownloadItem); 311 } catch (error) { 312 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 313 } 314 }) 315 316 Web({ src: 'www.example.com', controller: this.controller }) 317 } 318 } 319} 320``` 321 322下载任务信息持久化工具类文件。 323```ts 324// downloadUtil.ets 325import { util } from '@kit.ArkTS'; 326import fileStream from '@ohos.file.fs'; 327 328const helper = new util.Base64Helper(); 329 330export let filePath : string; 331export const fileName = 'demoFile.txt'; 332export namespace downloadUtil { 333 export function init(context: UIContext): void { 334 filePath = context.getHostContext()!.filesDir; 335 } 336 337 export function uint8ArrayToStr(uint8Array: Uint8Array): string { 338 return helper.encodeToStringSync(uint8Array); 339 } 340 341 export function strToUint8Array(str: string): Uint8Array { 342 return helper.decodeSync(str); 343 } 344 345 export function saveDownloadInfo(downloadInfo: string): void { 346 if (!fileExists(filePath)) { 347 mkDirectorySync(filePath); 348 } 349 350 writeToFileSync(filePath, fileName, downloadInfo); 351 } 352 353 export function fileExists(filePath: string): boolean { 354 try { 355 return fileStream.accessSync(filePath); 356 } catch (error) { 357 return false; 358 } 359 } 360 361 export function mkDirectorySync(directoryPath: string, recursion?: boolean): void { 362 try { 363 fileStream.mkdirSync(directoryPath, recursion ?? false); 364 } catch (error) { 365 console.error(`mk dir error. err message: ${error.message}, err code: ${error.code}`); 366 } 367 } 368 369 export function writeToFileSync(dir: string, fileName: string, msg: string): void { 370 let file = fileStream.openSync(dir + '/' + fileName, fileStream.OpenMode.WRITE_ONLY | fileStream.OpenMode.CREATE); 371 fileStream.writeSync(file.fd, msg); 372 } 373 374 export function readFileSync(dir: string, fileName: string): string { 375 return fileStream.readTextSync(dir + '/' + fileName); 376 } 377 378} 379```