赞
踩
本示例使用@ohos.request接口创建上传和下载任务,实现上传、下载功能,hfs作为服务器,实现了文件的上传和下载和任务的查询功能。
使用说明
1.本示例功能需要先配置服务器环境后使用,具体配置见[上传下载服务配置]。
2.首页展示上传和下载两个入口组件,点击进入对应的页面,如果要使用后台下载任务,请开启后台任务开关。
3.上传页面(请先在图库中确定已开启图库权限):
点击**+**,从相册选择拉起图库选择照片,图片选择页面支持拍照,选择照片后点击发表进行上传。
在首页中打开后台任务开关后,上传页面开启的是后台上传任务,后台任务在应用退出到后台时可以在通知栏看到任务状态。
4.下载页面:
点击文件列表选择要下载的文件后,点击下载选择指定路径后开始下载。
点击查看下载文件进入下载文件页面,点击文件夹查看文件夹内的文件。
在首页中打开后台任务开关后,下载页面开启的是后台下载任务,后台任务在应用退出到后台时可以在通知栏看到任务状态。
前台下载时只支持单文件下载,后台下载时支持选择多个文件下载。
该示例分为两个模块:
上传模块
源码参考:[RequestUpload.ets]
/* * Copyright (c) 2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { common } from '@kit.AbilityKit'; import { promptAction } from '@kit.ArkUI'; import { request } from '@kit.BasicServicesKit'; import { urlUtils } from '../utils/UrlUtils'; import { logger } from '../utils/Logger'; import { MediaUtils } from '../utils/MediaUtils'; import { BackgroundTaskState, UPLOAD_TOKEN, TOAST_BOTTOM, TASK_MAX } from '../utils/Constants'; const TAG: string = 'RequestUpload'; const HEADER: Record<string, string> = { 'Content-Type': 'multipart/form-data' }; class Upload { private mediaUtils: MediaUtils = new MediaUtils(); private config: request.agent.Config = { action: request.agent.Action.UPLOAD, headers: HEADER, url: '', mode: request.agent.Mode.FOREGROUND, method: 'POST', title: 'upload', network: request.agent.Network.ANY, data: [], token: UPLOAD_TOKEN } private context: common.UIAbilityContext | undefined = undefined; private uploadTask: request.agent.Task | undefined = undefined; private backgroundTask: request.agent.Task | undefined = undefined; private waitList: Array<string> = []; progressCallback: Function | undefined = undefined; completedCallback: Function | undefined = undefined; failedCallback: Function | undefined = undefined; constructor() { setInterval(() => { this.flushBackgroundTask() }, 2000); } async uploadFilesBackground(fileUris: Array<string>): Promise<void> { logger.info(TAG, `uploadFiles begin, ${JSON.stringify(fileUris)}`); this.context = getContext(this) as common.UIAbilityContext; if (fileUris.length === 0) { return; } fileUris.forEach((item: string) => { this.waitList.push(item); }); } async flushBackgroundTask() { let tasks = await request.agent.search({ state: request.agent.State.RUNNING }); let state = AppStorage.get<number>('backTaskState'); if (state === BackgroundTaskState.RUNNING) { if (tasks.length < TASK_MAX && this.waitList.length > 0) { this.createBackgroundTask(this.waitList); this.waitList = []; } else { if (this.backgroundTask === undefined || tasks.indexOf(this.backgroundTask.tid) === -1) { AppStorage.setOrCreate('backTaskState', BackgroundTaskState.NONE); this.backgroundTask = undefined; } } } } async createBackgroundTask(fileUris: Array<string>) { if (this.context === undefined) { return; } this.config.url = await urlUtils.getUrl(this.context); this.config.data = await this.getFilesAndData(this.context.cacheDir, fileUris); this.config.mode = request.agent.Mode.BACKGROUND; try { this.backgroundTask = await request.agent.create(this.context, this.config); await this.backgroundTask.start(); let state = AppStorage.get<number>('backTaskState'); if (state === BackgroundTaskState.PAUSE) { await this.backgroundTask.pause(); } logger.info(TAG, `createBackgroundTask success`); } catch (err) { logger.error(TAG, `task err, err = ${JSON.stringify(err)}`); } } async uploadFiles(fileUris: Array<string>, callback: (progress: number, isSucceed: boolean) => void): Promise<void> { logger.info(TAG, `uploadFiles begin, ${JSON.stringify(fileUris)}`); if (fileUris.length === 0) { return; } // 查询到存在正在执行的上传任务,提示并返回 let tasks = await request.agent.search({ state: request.agent.State.RUNNING, action: request.agent.Action.UPLOAD, mode: request.agent.Mode.FOREGROUND }); if (tasks.length > 0) { promptAction.showToast({ message: $r('app.string.have_upload_task_tips'), bottom: TOAST_BOTTOM }); return; } let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; this.config.data = await this.getFilesAndData(context.cacheDir, fileUris); this.config.url = await urlUtils.getUrl(context); this.config.mode = request.agent.Mode.FOREGROUND; try { this.uploadTask = await request.agent.create(context, this.config); this.uploadTask.on('progress', this.progressCallback = (progress: request.agent.Progress) => { logger.info(TAG, `progress, progress = ${progress.processed} ${progress.state}`); let processed = Number(progress.processed.toString()).valueOf(); let size = progress.sizes[0]; let process: number = Math.floor(processed / size * 100); if (process < 100) { callback(process, false); } }); this.uploadTask.on('completed', this.completedCallback = (progress: request.agent.Progress) => { logger.info(TAG, `complete, progress = ${progress.processed} ${progress.state}`); callback(100, true); this.cancelTask(); }); this.uploadTask.on('failed', this.failedCallback = async (progress: request.agent.Progress) => { if (this.uploadTask) { let taskInfo = await request.agent.touch(this.uploadTask.tid, UPLOAD_TOKEN); logger.info(TAG, `fail, resean = ${taskInfo.reason}, faults = ${JSON.stringify(taskInfo.faults)}`); } callback(100, false); this.cancelTask(); }); await this.uploadTask.start(); } catch (err) { logger.error(TAG, `task err, err = ${JSON.stringify(err)}`); callback(100, false); } } async cancelTask() { if (this.uploadTask === undefined) { return; } try { this.uploadTask.off('progress'); this.progressCallback = undefined; this.uploadTask.off('failed'); this.failedCallback = undefined this.uploadTask.off('completed'); this.completedCallback = undefined await this.uploadTask.stop(); await request.agent.remove(this.uploadTask.tid); } catch (err) { logger.info(TAG, `deleteTask fail,err= ${JSON.stringify(err)}`); } this.uploadTask = undefined; } async pauseOrResume() { let state = AppStorage.get<number>('backTaskState'); if (state === BackgroundTaskState.RUNNING) { await this.pause(); AppStorage.setOrCreate('backTaskState', BackgroundTaskState.PAUSE); } else if (state === BackgroundTaskState.PAUSE) { await this.resume(); AppStorage.setOrCreate('backTaskState', BackgroundTaskState.RUNNING); } else { logger.info(TAG, 'this task state is error'); } } async pause() { logger.info(TAG, 'pause'); if (this.backgroundTask === undefined) { return; } try { await this.backgroundTask.pause(); } catch (err) { logger.info(TAG, `pause fail,err= ${JSON.stringify(err)}`); } } async resume() { logger.info(TAG, 'resume'); if (this.backgroundTask === undefined) { return; } try { await this.backgroundTask.resume(); } catch (err) { logger.info(TAG, `resume fail,err= ${JSON.stringify(err)}`); } } private async getFilesAndData(cacheDir: string, fileUris: Array<string>): Promise<Array<request.agent.FormItem>> { logger.info(TAG, `getFilesAndData begin`); let files: Array<request.agent.FormItem> = []; for (let i = 0; i < fileUris.length; i++) { logger.info(TAG, `getFile fileUri = ${fileUris[i]}`); let imagePath = await this.mediaUtils.copyFileToCache(cacheDir, fileUris[i]); logger.info(TAG, `getFilesAndData ${JSON.stringify(imagePath)}`); let file: request.agent.FormItem = { name: imagePath.split('cache/')[1], value: { path: './' + imagePath.split('cache/')[1] } } files.push(file); } logger.info(TAG, `getFilesAndData ${JSON.stringify(files)}`); return files; } } export const requestUpload = new Upload();
/* * Copyright (c) 2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { picker } from '@kit.CoreFileKit'; import { logger } from '@ohos/uploaddownload'; import { BusinessError } from '@kit.BasicServicesKit'; const TAG: string = 'AddPictures'; @Extend(Image) function imageStyle() { .width('100%') .aspectRatio(1) .objectFit(ImageFit.Fill) .backgroundColor($r('app.color.light_gray')) .borderRadius(12) } const TEXT_WIDTH_FULL: Length = '100%'; @Component export struct AddPictures { @Consume imageList: Array<string>; build() { Column() { Text($r('app.string.tip')) .fontColor($r('app.color.text_normal')) .fontWeight(400) .fontFamily('HarmonyHeiTi') .fontSize(14) .opacity(0.4) .margin({ top: $r('app.float.add_pictures_margin_top'), bottom: $r('app.float.add_pictures_margin_bottom') }) .width(TEXT_WIDTH_FULL) GridRow({ columns: { sm: 3, md: 6, lg: 8 }, gutter: 12 }) { ForEach(this.imageList, (item: string) => { GridCol({ span: 1 }) { Image(item) .imageStyle() } }) GridCol({ span: 1 }) { Row() { Image($r('app.media.ic_public_add')) .size({ width: 24, height: 24 }) .objectFit(ImageFit.Contain) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } .width('100%') .aspectRatio(1) .backgroundColor($r('app.color.white')) .borderRadius(12) .onClick(() => { this.showDialog(); }) } } .width('100%') } addImages = (images: Array<string>) => { images.forEach((item: string) => { if (!this.imageList.includes(item)) { this.imageList.push(item); } }) logger.info(TAG, `addImages imageList=${JSON.stringify(this.imageList)}`); } showDialog() { AlertDialog.show({ message: $r('app.string.pick_album'), alignment: DialogAlignment.Bottom, offset: { dx: 0, dy: -12 }, primaryButton: { value: $r('app.string.cancel'), fontColor: $r('app.color.btn_text_blue'), action: () => { } }, secondaryButton: { value: $r('app.string.ok'), fontColor: $r('app.color.btn_text_blue'), action: () => { try { let photoSelectOptions = new picker.PhotoSelectOptions(); photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE; photoSelectOptions.maxSelectNumber = 5; let photoPicker = new picker.PhotoViewPicker(); photoPicker.select(photoSelectOptions).then((photoSelectResult: picker.PhotoSelectResult) => { this.addImages(photoSelectResult.photoUris); }).catch((err: BusinessError) => { logger.error(TAG, `'PhotoViewPicker.select failed with err: ${JSON.stringify(err)}`); }); } catch (err) { logger.error(TAG, `'PhotoViewPicker failed with err: ${JSON.stringify(err)}`); } } } }) } }
/* * Copyright (c) 2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { common } from '@kit.AbilityKit'; import { promptAction } from '@kit.ArkUI'; import { AddPictures } from '../components/AddPictures'; import { BackgroundTaskState, requestUpload, TOAST_BOTTOM } from '@ohos/uploaddownload'; const TIME_MAX: number = 5; @Entry @Component struct Upload { @StorageLink('isBackground') isBackground: boolean = false; @StorageLink('backTaskState') @Watch('stateChange') backTaskState: BackgroundTaskState = BackgroundTaskState.NONE; @State isBegin: boolean = false; @Provide imageList: Array<string> = []; @State progress: number = 0; @State countdown: number = 0; build() { Navigation() { Scroll() { AddPictures() } .padding({ left: 24, right: 24 }) .width('100%') .layoutWeight(1) .align(Alignment.Top) Column() { Button() { if (this.isBackground && this.backTaskState !== BackgroundTaskState.NONE) { if (this.backTaskState === BackgroundTaskState.RUNNING) { Text($r('app.string.pause')) .fontSize(16) .fontWeight(500) .fontColor($r('app.color.white')) } else { Text($r('app.string.continue')) .fontSize(16) .fontWeight(500) .fontColor($r('app.color.white')) } } else if (this.isBegin && !this.isBackground) { Row() { Progress({ value: this.progress, type: ProgressType.Ring }) .width(20) .height(20) .backgroundColor('#FFFFFF') .color('#558DFF') .style({ strokeWidth: 2, scaleCount: 100, scaleWidth: 2 }) Text(`${this.getResourceString($r('app.string.uploading'))}${this.progress}%`) .fontSize(16) .fontColor('#FFFFFF') .fontWeight(500) .margin({ left: 12 }) }.alignItems(VerticalAlign.Center) } else { if (this.countdown > 0) { Text(`${this.countdown}s`) .fontSize(16) .fontWeight(500) .fontColor($r('app.color.white')) } else { Text($r('app.string.upload')) .fontSize(16) .fontWeight(500) .fontColor($r('app.color.white')) } } } .id('publish') .width('100%') .height(40) .margin({ bottom: this.isBegin ? 16 : 24 }) .enabled(this.countdown > 0 ? false : true) .backgroundColor($r('app.color.button_blue')) .onClick(() => { if (this.isBackground && this.backTaskState !== BackgroundTaskState.NONE) { requestUpload.pauseOrResume(); } else { this.uploadFiles(); } }) if (this.isBegin) { Button() { Text($r('app.string.cancel')) .fontSize(16) .fontWeight(500) .fontColor($r('app.color.btn_text_blue')) } .id('cancel') .width('100%') .height(40) .margin({ bottom: 24 }) .backgroundColor($r('app.color.button_light_gray')) .onClick(() => { // cancel task requestUpload.cancelTask(); this.progress = 0; this.isBegin = false; }) } } .width('100%') .padding({ left: 24, right: 24 }) } .width('100%') .height('100%') .backgroundColor($r('app.color.light_gray')) .title($r('app.string.upload')) .hideBackButton(false) .titleMode(NavigationTitleMode.Mini) .mode(NavigationMode.Stack) } aboutToAppear() { this.isBegin = false; this.backTaskState = BackgroundTaskState.NONE; } stateChange() { if (this.backTaskState === BackgroundTaskState.NONE) { this.imageList = []; } } uploadFiles() { if (this.imageList.length === 0) { return; } if (this.isBackground) { AppStorage.setOrCreate('backTaskState', BackgroundTaskState.RUNNING) requestUpload.uploadFilesBackground(this.imageList); promptAction.showToast({ message: $r('app.string.background_task_start'), bottom: TOAST_BOTTOM }); } else { this.isBegin = true; this.progress = 0; requestUpload.uploadFiles(this.imageList, (progress: number, isSucceed: boolean) => { this.progress = progress; if (this.progress === 100 && isSucceed) { this.isBegin = false; this.imageList = []; promptAction.showToast({ message: $r('app.string.upload_success'), bottom: TOAST_BOTTOM }) } if (this.progress === 100 && isSucceed === false) { this.isBegin = false; this.countdown = TIME_MAX; let interval = setInterval(() => { if (this.countdown > 0) { this.countdown--; } else { clearInterval(interval); } }, 1000); promptAction.showToast({ message: $r('app.string.upload_fail'), bottom: TOAST_BOTTOM }) } }); } } getResourceString(resource: Resource) { let context = getContext(this) as common.UIAbilityContext; return context.resourceManager.getStringSync(resource.id); } }
参考接口:@ohos.request,@ohos.file.picker
下载模块
源码参考:[RequestDownload.ets]
/* * Copyright (c) 2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { common } from '@kit.AbilityKit'; import { promptAction } from '@kit.ArkUI'; import { request } from '@kit.BasicServicesKit'; import { logger } from '../utils/Logger'; import { TOAST_BOTTOM, TASK_MAX, TASK_PAUSE_MSG, TASK_NET_PAUSE_MSG, TASK_RESUME_MSG, TASK_NET_RESUME_MSG } from '../utils/Constants'; const TAG: string = 'RequestDownload'; let isNetPause = false; class RequestDownload { private context: common.UIAbilityContext | undefined = undefined; private waitList: Array<string[]> = []; private downloadTask: request.agent.Task | undefined = undefined; progressCallback: Function | undefined = undefined; completedCallback: Function | undefined = undefined; failedCallback: Function | undefined = undefined; constructor() { setInterval(() => { this.flushBackgroundTask() }, 2000); } async downloadFilesBackground(folder: string, files: Array<string>) { logger.info(TAG, 'downloadFiles'); this.context = getContext(this) as common.UIAbilityContext; files.forEach((item: string) => { this.waitList.push([folder, item]); }); } async flushBackgroundTask() { let tasks = await request.agent.search({ state: request.agent.State.RUNNING }); if (tasks.length < TASK_MAX && this.waitList.length > 0) { let downloadList: Array<string[]> = []; if (this.waitList.length <= TASK_MAX - tasks.length) { downloadList = this.waitList; this.waitList = []; } else { downloadList = this.waitList.slice(0, TASK_MAX - tasks.length); this.waitList = this.waitList.slice(TASK_MAX - tasks.length, this.waitList.length); } logger.info(TAG, `this.waitList = ${JSON.stringify(this.waitList)}`); this.createBackgroundTask(downloadList); } } async createBackgroundTask(downloadList: Array<string[]>) { if (this.context === undefined) { return; } for (let i = 0; i < downloadList.length; i++) { try { let splitUrl = downloadList[i][1].split('//')[1].split('/'); let downloadConfig: request.agent.Config = { action: request.agent.Action.DOWNLOAD, url: downloadList[i][1], method: 'POST', title: 'download', mode: request.agent.Mode.BACKGROUND, network: request.agent.Network.ANY, saveas: `./${downloadList[i][0]}/${splitUrl[splitUrl.length-1]}`, overwrite: true, gauge: true } let downTask = await request.agent.create(this.context, downloadConfig); await downTask.start(); } catch (err) { logger.error(TAG, `task err, err = ${JSON.stringify(err)}`); this.waitList.push(downloadList[i]); } } } async pause() { if (this.downloadTask) { let taskInfo = await request.agent.show(this.downloadTask.tid); logger.info(TAG, `task pause, taskInfo = ${JSON.stringify(taskInfo)}`); await this.downloadTask.pause(); } } async resume() { if (this.downloadTask) { let taskInfo = await request.agent.show(this.downloadTask.tid); logger.info(TAG, `task resume, taskInfo = ${JSON.stringify(taskInfo)}`); await this.downloadTask.resume(); } } async downloadFile(folder: string, url: string, callback: (progress: number, isSuccess: boolean) => void) { logger.info(TAG, 'downloadFile'); // 查询到存在正在执行的下载任务,提示并返回 let tasks = await request.agent.search({ state: request.agent.State.RUNNING, action: request.agent.Action.DOWNLOAD, mode: request.agent.Mode.FOREGROUND }); if (tasks.length > 0) { promptAction.showToast({ message: $r('app.string.have_download_task_tips'), bottom: TOAST_BOTTOM }); return; } let splitUrl = url.split('//')[1].split('/'); let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; let downloadConfig: request.agent.Config = { action: request.agent.Action.DOWNLOAD, url: url, method: 'GET', title: 'download', mode: request.agent.Mode.BACKGROUND, retry: true, network: request.agent.Network.ANY, saveas: `./${folder}/${splitUrl[splitUrl.length-1]}`, overwrite: true } logger.info(TAG, `downloadFile, downloadConfig = ${JSON.stringify(downloadConfig)}`); try { this.downloadTask = await request.agent.create(context, downloadConfig); this.downloadTask.on('progress', this.progressCallback = (progress: request.agent.Progress) => { logger.info(TAG, `progress, progress = ${progress.processed} ${progress.state}`); let processed = Number(progress.processed.toString()).valueOf(); let size = progress.sizes[0]; let process: number = Math.floor(processed / size * 100); if (process < 100) { callback(process, false); } }) this.downloadTask.on('completed', this.completedCallback = (progress: request.agent.Progress) => { logger.info(TAG, `download complete, file= ${url}, progress = ${progress.processed}`); callback(100, true); this.deleteTask(); }) this.downloadTask.on('pause', this.failedCallback = async (progress: request.agent.Progress) => { if (this.downloadTask) { let taskInfo = await request.agent.show(this.downloadTask.tid); logger.info(TAG, `pause, resean = ${taskInfo.reason}, progress = ${progress.processed}, faults = ${JSON.stringify(taskInfo.faults)}`); isNetPause = taskInfo.faults === 0; if (isNetPause) { callback(TASK_NET_PAUSE_MSG, isNetPause); } else { callback(TASK_PAUSE_MSG, isNetPause); } } }) this.downloadTask.on('resume', this.failedCallback = async (progress: request.agent.Progress) => { if (this.downloadTask) { let taskInfo = await request.agent.show(this.downloadTask.tid); logger.info(TAG, `resume, resean = ${taskInfo.reason}, progress = ${progress.processed}, faults = ${JSON.stringify(taskInfo.faults)}`); if (isNetPause) { isNetPause = false; callback(TASK_NET_RESUME_MSG, isNetPause); } else { callback(TASK_RESUME_MSG, isNetPause); } } }) this.downloadTask.on('failed', this.failedCallback = async (progress: request.agent.Progress) => { if (this.downloadTask) { let taskInfo = await request.agent.show(this.downloadTask.tid); logger.info(TAG, `fail, resean = ${taskInfo.reason}, progress = ${progress.processed}, faults = ${JSON.stringify(taskInfo.faults)}`); } callback(100, false); this.deleteTask(); }) await this.downloadTask.start(); } catch (err) { logger.error(TAG, `task err, err = ${JSON.stringify(err)}`); callback(100, false); } } async deleteTask() { if (this.downloadTask) { try { this.downloadTask.off('progress'); this.progressCallback = undefined; this.downloadTask.off('completed'); this.completedCallback = undefined this.downloadTask.off('failed'); this.failedCallback = undefined await request.agent.remove(this.downloadTask.tid); } catch (err) { logger.info(TAG, `deleteTask fail, err= ${JSON.stringify(err)}`); } } this.downloadTask = undefined; } } export const requestDownload = new RequestDownload();
/* * Copyright (c) 2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { promptAction } from '@kit.ArkUI'; import { router } from '@kit.ArkUI'; import { CustomDataSource } from '../components/CustomDataSource'; import { FileModel, FileType, fileUtils, logger, requestFiles, requestDownload, TOAST_BOTTOM, TASK_PAUSE_MSG, TASK_RESUME_MSG, TASK_NET_PAUSE_MSG, TASK_NET_RESUME_MSG } from '@ohos/uploaddownload'; import { SelectFolderDialog } from '../components/SelectFolderDialog'; const TAG: string = 'Download'; const OFFSET_DY: Length = -12; const OFFSET_DX: Length = 0; const LOADING_PROGRESS_WIDTH: Length = 100; const FULL_WIDTH: Length = '100%'; const FULL_HEIGHT: Length = '100%'; const LIST_WIDTH: Length = '100%'; const LIST_HEIGHT: Length = 'auto'; const LIST_BORDER_RADIUS: Length | BorderRadiuses = 24; const PADDING_TOP: Length = 4; const PADDING_BOTTOM: Length = 4; const STROKE_WIDTH: Length = 1; const START_MARGIN: Length = 44; const END_MARGIN: Length = 12; const COLUMN_PADDING_LEFT: Length = 12; const COLUMN_PADDING_RIGHT: Length = 12; const COLUMN_PADDING_BOTTOM: Length = 12; const BUTTON_FONT_SIZE = 16; const MARGIN_TOP: Length = 12; const MARGIN_LEFT: Length = 12; const MARGIN_RIGHT: Length = 12; const MARGIN_BOTTOM: Length = 12; const BUTTON_HEIGHT: Length = 45; @Entry @Component struct Download { private fileData: CustomDataSource = new CustomDataSource([]); @StorageLink('isBackground') isBackground: boolean = false; @Provide downloadFolder: Array<string> = []; @State isGetData: boolean = false; @State checkFile: Array<string> = []; @State checkList: Array<boolean> = []; @State isRunning: boolean = false; @State isPause: boolean = false; @State isNetPause: boolean = false; @State progress: number = 0; private selectFolder = (folder: string) => { logger.info(TAG, `selectFolder = ${folder}`); this.download(folder); } private folderDialogController: CustomDialogController = new CustomDialogController({ builder: SelectFolderDialog({ selectFolder: this.selectFolder }), autoCancel: true, alignment: DialogAlignment.Bottom, offset: { dx: OFFSET_DX, dy: OFFSET_DY } }); build() { Navigation() { Column() { if (this.isGetData) { LoadingProgress() .width(LOADING_PROGRESS_WIDTH) .layoutWeight(1) } else { List({ space: 12 }) { LazyForEach(this.fileData, (item: FileModel, index: number) => { ListItem() { this.FileItem(item, index) } }, (item: FileModel) => JSON.stringify(item)) } .width(LIST_WIDTH) .height(LIST_HEIGHT) .scrollBar(BarState.Off) .backgroundColor(Color.White) .borderRadius(LIST_BORDER_RADIUS) .padding({ top: PADDING_TOP, bottom: PADDING_BOTTOM }) .divider({ strokeWidth: STROKE_WIDTH, startMargin: START_MARGIN, endMargin: END_MARGIN }) } Column().layoutWeight(1) this.BottomView() } .padding({ left: COLUMN_PADDING_LEFT, right: COLUMN_PADDING_RIGHT, bottom: COLUMN_PADDING_BOTTOM }) .height(FULL_HEIGHT) } .width(FULL_WIDTH) .height(FULL_HEIGHT) .hideBackButton(false) .titleMode(NavigationTitleMode.Mini) .mode(NavigationMode.Stack) .backgroundColor($r('app.color.light_gray')) .hideToolBar(false) .title($r('app.string.download')) } @Builder FileItem(file: FileModel, index: number) { Row() { Row() { if (file.fileType === FileType.FOLDER) { Image($r('app.media.ic_files_folder')) .size({ width: 24, height: 24 }) .objectFit(ImageFit.Contain) } else if (file.fileType === FileType.IMAGE) { Image($r('app.media.ic_public_picture')) .size({ width: 24, height: 24 }) .objectFit(ImageFit.Contain) } else if (file.fileType === FileType.MUSIC) { Image($r('app.media.ic_public_music')) .size({ width: 24, height: 24 }) .objectFit(ImageFit.Contain) } else if (file.fileType === FileType.Video) { Image($r('app.media.ic_public_video')) .size({ width: 24, height: 24 }) .objectFit(ImageFit.Contain) } else { Image($r('app.media.ic_public_document')) .size({ width: 24, height: 24 }) .objectFit(ImageFit.Contain) } Text(decodeURIComponent(file.name)) .fontSize(16) .fontWeight(400) .layoutWeight(1) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) .margin({ left: 12 }) } .layoutWeight(1) Checkbox({ name: '', group: 'checkboxGroup' }) .select(this.checkList[index]) .selectedColor($r('app.color.button_blue')) .margin({ left: 12 }) .hitTestBehavior(HitTestMode.None) } .width('100%') .padding({ left: 12, right: 12 }) .height(48) .onClick(() => { this.fileCheck(index); }) } @Builder BottomView() { Column({ space: 12 }) { Button() { Row() { if (!this.isBackground && this.isRunning) { if (this.isPause || this.isNetPause) { Text($r('app.string.continue')) .fontColor(Color.White) .fontSize(BUTTON_FONT_SIZE) } else { Text(`${this.progress}%`) .fontColor(Color.White) .fontSize(BUTTON_FONT_SIZE) Text($r('app.string.downloading')) .fontColor(Color.White) .fontSize(BUTTON_FONT_SIZE) .margin({ left: MARGIN_LEFT }) } } else { Text($r('app.string.download')) .fontColor(Color.White) .fontSize(BUTTON_FONT_SIZE) } } } .id('download_to') .type(ButtonType.Capsule) .height(BUTTON_HEIGHT) .width(FULL_WIDTH) .backgroundColor($r('app.color.button_blue')) .onClick(() => { if (!this.isRunning) { this.folderDialogController.open(); } else { if (!this.isNetPause) { if (this.isPause) { requestDownload.resume(); } else { requestDownload.pause(); } } } }) Button($r('app.string.view_download_files')) .id('view_download_files') .type(ButtonType.Capsule) .backgroundColor($r('sys.color.ohos_id_color_button_normal')) .width('100%') .fontSize(BUTTON_FONT_SIZE) .margin({ bottom: MARGIN_BOTTOM }) .fontColor($r('app.color.btn_text_blue')) .onClick(() => { router.pushUrl({ url: 'pages/DownloadFiles' }); }) } .margin({ top: MARGIN_TOP, left: MARGIN_LEFT, right: MARGIN_RIGHT }) } aboutToAppear() { this.isRunning = false; this.isPause = false; this.isGetData = true; requestFiles.requestFiles().then((data: FileModel[]) => { this.checkList = []; this.isRunning = false; this.fileData.dataArray = data; this.fileData.dataArray.forEach(() => { this.checkList.push(false); }) this.isGetData = false; this.fileData.notifyDataReload(); }) fileUtils.listFolders().then((folders: Array<string>) => { this.downloadFolder = folders; }) } fileCheck(index: number) { if (!this.isBackground) { for (let i = 0; i < this.checkList.length; i++) { if (i !== index) { this.checkList[i] = false; } } } this.checkList[index] = !this.checkList[index]; logger.info(TAG, `this.checkList = ${JSON.stringify(this.checkList)}`); } download(folder: string) { this.checkFile = []; if (this.checkList === undefined) { return; } logger.info(TAG, `this.checkList = ${JSON.stringify(this.checkList)}`); for (let i = 0; i < this.checkList.length; i++) { if (this.checkList[i]) { let fileModel = this.fileData.getData(i); logger.info(TAG, `fileModel = ${JSON.stringify(fileModel)}`); fileModel.files.forEach((url: string) => { let splitUrl = url.split('//')[1].split('/'); if (splitUrl[splitUrl.length-1] !== '') { this.checkFile.push(url); } }); } } logger.info(TAG, `this.checkFile = ${JSON.stringify(this.checkFile)}`); if (this.checkFile.length === 0) { promptAction.showToast({ message: $r('app.string.check_file_tips'), bottom: TOAST_BOTTOM }); return; } this.progress = 0; if (this.isBackground) { this.isRunning = false; requestDownload.downloadFilesBackground(folder, this.checkFile); this.checkFile = []; this.checkList = []; this.fileData.dataArray.forEach(() => { this.checkList.push(false); }) this.fileData.notifyDataReload(); promptAction.showToast({ message: $r('app.string.background_task_start'), bottom: TOAST_BOTTOM }); } else { this.isRunning = true; requestDownload.downloadFile(folder, this.checkFile[0], this.downloadFileCallback); } } downloadFilesCallback = (downloadCount: number, isSuccess: boolean) => { this.progress = downloadCount; if (downloadCount === this.checkFile.length) { this.downloadFinish(isSuccess); } } downloadFileCallback = (progress: number, isSuccess: boolean) => { logger.info(TAG, `downloadFileCallback = ${progress}`); if (progress === TASK_PAUSE_MSG) { this.isPause = true; } else if (progress === TASK_RESUME_MSG) { this.isPause = false; } else if (progress === TASK_NET_PAUSE_MSG) { this.isNetPause = true; let message = $r('app.string.net_pause'); promptAction.showToast({ message: message, bottom: TOAST_BOTTOM }); } else if (progress === TASK_NET_RESUME_MSG) { this.isNetPause = false; let message = $r('app.string.net_resume'); promptAction.showToast({ message: message, bottom: TOAST_BOTTOM }); } else { this.progress = progress; if (this.progress === 100) { this.downloadFinish(isSuccess); } } } downloadFinish(isSuccess: boolean) { this.isRunning = false; this.checkFile = []; this.checkList = []; this.fileData.dataArray.forEach(() => { this.checkList.push(false); }) this.fileData.notifyDataReload(); let message = isSuccess ? $r('app.string.download_finish') : $r('app.string.download_fail'); promptAction.showToast({ message: message, bottom: TOAST_BOTTOM }); } }
/* * Copyright (c) 2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { common } from '@kit.AbilityKit'; import { fileIo } from '@kit.CoreFileKit'; import { logger } from '../utils/Logger'; const TAG: string = 'FileUtil'; const ALBUMS: string[] = ['Pictures', 'Videos', 'Others']; class FileUtil { constructor() { } async initDownloadDir(): Promise<void> { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; logger.info(TAG, `initDownloadDir cacheDir=${context.cacheDir}`); try { fileIo.mkdirSync(`${context.cacheDir}/${ALBUMS[0]}`); fileIo.mkdirSync(`${context.cacheDir}/${ALBUMS[1]}`); fileIo.mkdirSync(`${context.cacheDir}/${ALBUMS[2]}`); } catch (err) { logger.info(TAG, `initDownloadDir err =${JSON.stringify(err)}`); } } async listFolders(): Promise<Array<string>> { await this.initDownloadDir(); return ALBUMS; } async clearFolder(folderName: string): Promise<void> { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; try { let files: string[] = fileIo.listFileSync(`${context.cacheDir}/${folderName}`); logger.info(TAG, `listFiles listFileSync =${JSON.stringify(files)}`); for (let i = 0; i < files.length; i++) { fileIo.unlinkSync(`${context.cacheDir}/${folderName}/${files[i]}`); } } catch (err) { logger.info(TAG, `listFiles err =${JSON.stringify(err)}`); } } async listFiles(folderName: string): Promise<Array<string>> { let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; let files: string[] = []; try { files = fileIo.listFileSync(`${context.cacheDir}/${folderName}`); logger.info(TAG, `listFiles listFileSync =${JSON.stringify(files)}`); } catch (err) { logger.info(TAG, `listFiles err =${JSON.stringify(err)}`); } return files; } } export const fileUtils = new FileUtil();
/* * Copyright (c) 2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { fileUtils } from '../utils/FileUtils'; @Preview @Component export struct FileBrowse { @State folders: Array<string> = ['folder']; @State files: Array<string> = []; @State currentFolder: string = ''; aboutToAppear() { fileUtils.listFolders().then((folders: Array<string>) => { this.folders = folders; }) } build() { Navigation() { List({ space: 12 }) { ForEach(this.folders, (item: string) => { ListItem() { NavRouter() { Row() { Image($r('app.media.ic_files_folder')) .size({ width: 32, height: 26 }) .objectFit(ImageFit.Contain) Text(item) .fontSize(16) .width('100%') .margin({ left: 12 }) } .height(56) .padding({ left: 16 }) .backgroundColor(Color.White) .borderRadius(24) NavDestination() { this.FilesView() } .title(this.CustomTitle(item)) .backgroundColor($r('app.color.light_gray')) } .onStateChange(async (isActivated: boolean) => { if (isActivated) { this.currentFolder = item; this.files = await fileUtils.listFiles(item); } }) } }) } .padding({ left: 12, right: 12 }) } .hideBackButton(false) .titleMode(NavigationTitleMode.Mini) .title($r('app.string.download_files_title')) .mode(NavigationMode.Stack) .backgroundColor($r('app.color.light_gray')) } @Builder CustomTitle(title: string) { Row() { Text(title) .fontSize(20) .fontColor($r('app.color.text_normal')) .fontWeight(700) .margin({ left: 8 }) } .width('100%') } @Builder FilesView() { Column() { List({ space: 12 }) { if (this.files.length === 0) { ListItem() { Text($r('app.string.folder_empty')) .fontSize(16) .width('100%') .margin({ top: 50 }) .textAlign(TextAlign.Center) } } ForEach(this.files, (item: string) => { ListItem() { Text(decodeURIComponent(item)) .fontSize(16) .width('100%') } .padding(12) .height(48) .backgroundColor(Color.White) .borderRadius(24) }) } .padding({ left: 12, right: 12 }) .layoutWeight(1) Column() { Button() { Image($r('app.media.ic_public_delete')) .objectFit(ImageFit.Cover) .size({ width: 24, height: 24 }) } .type(ButtonType.Circle) .width(40) .height(40) .backgroundColor('#FF0000') .margin({ left: 5 }) Text($r('app.string.clear_folder')) .fontSize(14) .fontColor($r('app.color.text_normal')) .opacity(0.6) .margin({ top: 8 }) } .margin({ bottom: 24, top: 6 }) .onClick(() => { fileUtils.clearFolder(this.currentFolder); this.files = []; }) } .height('100%') .backgroundColor($r('app.color.light_gray')) } }
以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下:
内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!
为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。