网络-上传和下载(ArkTS)
介绍
本示例使用@ohos.request接口创建上传和下载任务,实现上传、下载功能,hfs作为服务器,实现了文件的上传和下载和任务的查询功能。
效果预览
使用说明
1.本示例功能需要先配置服务器环境后使用,具体配置见[上传下载服务配置]。
2.首页展示上传和下载两个入口组件,点击进入对应的页面,如果要使用后台下载任务,请开启后台任务开关。
3.上传页面(请先在图库中确定已开启图库权限):
点击**+**,从相册选择拉起图库选择照片,图片选择页面支持拍照,选择照片后点击发表进行上传。
在首页中打开后台任务开关后,上传页面开启的是后台上传任务,后台任务在应用退出到后台时可以在通知栏看到任务状态。
4.下载页面:
点击文件列表选择要下载的文件后,点击下载选择指定路径后开始下载。
点击查看下载文件进入下载文件页面,点击文件夹查看文件夹内的文件。
在首页中打开后台任务开关后,下载页面开启的是后台下载任务,后台任务在应用退出到后台时可以在通知栏看到任务状态。
前台下载时只支持单文件下载,后台下载时支持选择多个文件下载。
具体实现
-
该示例分为两个模块:
-
上传模块
- 使用@ohos.request中接口agent.create创建上传任务,调用@ohos.request中的Task相关接口实现上传任务的创建、取消、进度加载,失败的任务会调用查询接口获取失败原因并打印在日志中,支持多个文件上传。
-
-
源码参考:[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 = undefinedthis.uploadTask.off('completed');this.completedCallback = undefinedawait 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();
- 源码参考:[AddPictures.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 { 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)}`);}}}})}
}
- 源码参考:[Upload.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 { 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 taskrequestUpload.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
-
下载模块
- 使用@ohos.request中接口agent.create创建上传任务,调用@ohos.request中的Task相关接口实现上传任务的创建、取消、暂停、继续、进度加载,失败的任务会调用查询接口获取失败原因并打印在日志中,前台下载任务只支持单个文件下载,后台下载任务支持多文件下载。使用@ohos.file.fs完成指定路径的创建和查询已下载的文件。
-
-
源码参考:[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 = undefinedthis.downloadTask.off('failed');this.failedCallback = undefinedawait 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();
- 源码参考:[Download.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 { 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'))}@BuilderFileItem(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);})}@BuilderBottomView() {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 });}
}
- 源码参考:[FileUtils.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 { 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();
- 源码参考:[FileBrowse.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 { 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'))}@BuilderCustomTitle(title: string) {Row() {Text(title).fontSize(20).fontColor($r('app.color.text_normal')).fontWeight(700).margin({ left: 8 })}.width('100%')}@BuilderFilesView() {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'))}
}
- 参考接口:@ohos.request,@ohos.file.fs
以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下:
内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!
鸿蒙【北向应用开发+南向系统层开发】文档
鸿蒙【基础+实战项目】视频
鸿蒙面经
为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!