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 update 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 failed 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 231## 使用Web组件恢复进程退出时未下载完成的任务 232在Web组件启动时,可通过[resumeDownload()](../reference/apis-arkweb/js-apis-webview.md#resumedownload11)接口恢复未完成的下载任务。 233 234在以下示例中,通过“record”按钮将当前下载任务保存至持久化文件中,应用重启后,可借助“recovery”按钮恢复持久化的下载任务。示例代码实现了将当前下载任务持久化保存至文件的功能,若需保存多个下载任务,应用可根据需求调整持久化的时机与方式。 235```ts 236// xxx.ets 237import { webview } from '@kit.ArkWeb'; 238import { BusinessError } from '@kit.BasicServicesKit'; 239import { downloadUtil, fileName, filePath } from './downloadUtil'; // downloadUtil.ets 见下文 240 241@Entry 242@Component 243struct WebComponent { 244 controller: webview.WebviewController = new webview.WebviewController(); 245 delegate: webview.WebDownloadDelegate = new webview.WebDownloadDelegate(); 246 download: webview.WebDownloadItem = new webview.WebDownloadItem(); 247 // 用于记录失败的下载任务。 248 failedData: Uint8Array = new Uint8Array(); 249 250 build() { 251 Column() { 252 Button('setDownloadDelegate') 253 .onClick(() => { 254 try { 255 this.delegate.onBeforeDownload((webDownloadItem: webview.WebDownloadItem) => { 256 console.log("will start a download."); 257 // 传入一个下载路径,并开始下载。 258 webDownloadItem.start("/data/storage/el2/base/cache/web/" + webDownloadItem.getSuggestedFileName()); 259 }) 260 this.delegate.onDownloadUpdated((webDownloadItem: webview.WebDownloadItem) => { 261 console.log("download update percent complete: " + webDownloadItem.getPercentComplete()); 262 this.download = webDownloadItem; 263 }) 264 this.delegate.onDownloadFailed((webDownloadItem: webview.WebDownloadItem) => { 265 console.log("download failed guid: " + webDownloadItem.getGuid()); 266 // 序列化失败的下载任务到一个字节数组。 267 this.failedData = webDownloadItem.serialize(); 268 }) 269 this.delegate.onDownloadFinish((webDownloadItem: webview.WebDownloadItem) => { 270 console.log("download finish guid: " + webDownloadItem.getGuid()); 271 }) 272 this.controller.setDownloadDelegate(this.delegate); 273 webview.WebDownloadManager.setDownloadDelegate(this.delegate); 274 } catch (error) { 275 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 276 } 277 }) 278 Button('startDownload') 279 .onClick(() => { 280 try { 281 // 这里指定下载地址为 https://www.example.com/,Web组件会发起一个下载任务将该页面下载下来。 282 // 开发者需要替换为自己想要下载的内容的地址。 283 this.controller.startDownload('https://www.example.com/'); 284 } catch (error) { 285 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 286 } 287 }) 288 // 将当前的下载任务信息序列化保存,用于后续恢复下载任务。 289 // 当前用例仅展示下载一个任务的场景,多任务场景请按需扩展。 290 Button('record') 291 .onClick(() => { 292 try { 293 // 保存当前下载数据到持久化文档中。 294 downloadUtil.saveDownloadInfo(downloadUtil.uint8ArrayToStr(this.download.serialize())); 295 } catch (error) { 296 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 297 } 298 }) 299 // 从序列化的下载任务信息中,恢复下载任务。 300 // 按钮触发时必须保证WebDownloadManager.setDownloadDelegate设置完成。 301 Button('recovery') 302 .onClick(() => { 303 try { 304 // 当前默认持久化文件存在,用户根据实际情况增加判断。 305 let webDownloadItem = 306 webview.WebDownloadItem.deserialize(downloadUtil.strToUint8Array(downloadUtil.readFileSync(filePath, fileName))); 307 webview.WebDownloadManager.resumeDownload(webDownloadItem); 308 } catch (error) { 309 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 310 } 311 }) 312 313 Web({ src: 'www.example.com', controller: this.controller }) 314 } 315 } 316} 317``` 318 319下载任务信息持久化工具类文件。 320```ts 321// downloadUtil.ets 322import { util } from '@kit.ArkTS'; 323import fileStream from '@ohos.file.fs'; 324 325const helper = new util.Base64Helper(); 326 327export const filePath = getContext().filesDir; 328export const fileName = 'demoFile.txt'; 329export namespace downloadUtil { 330 331 export function uint8ArrayToStr(uint8Array: Uint8Array): string { 332 return helper.encodeToStringSync(uint8Array); 333 } 334 335 export function strToUint8Array(str: string): Uint8Array { 336 return helper.decodeSync(str); 337 } 338 339 export function saveDownloadInfo(downloadInfo: string): void { 340 if (!fileExists(filePath)) { 341 mkDirectorySync(filePath); 342 } 343 344 writeToFileSync(filePath, fileName, downloadInfo); 345 } 346 347 export function fileExists(filePath: string): boolean { 348 try { 349 return fileStream.accessSync(filePath); 350 } catch (error) { 351 return false; 352 } 353 } 354 355 export function mkDirectorySync(directoryPath: string, recursion?: boolean): void { 356 try { 357 fileStream.mkdirSync(directoryPath, recursion ?? false); 358 } catch (error) { 359 console.error(`mk dir error. err message: ${error.message}, err code: ${error.code}`); 360 } 361 } 362 363 export function writeToFileSync(dir: string, fileName: string, msg: string): void { 364 let file = fileStream.openSync(dir + '/' + fileName, fileStream.OpenMode.WRITE_ONLY | fileStream.OpenMode.CREATE); 365 fileStream.writeSync(file.fd, msg); 366 } 367 368 export function readFileSync(dir: string, fileName: string): string { 369 return fileStream.readTextSync(dir + '/' + fileName); 370 } 371 372} 373```