网络-上传和下载(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 = 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();
- 源码参考:[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 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
-
下载模块
- 使用@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 = 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();
- 源码参考:[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'))
}
@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 });
}
}
- 源码参考:[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'))
}
@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'))
}
}
- 参考接口:@ohos.request,@ohos.file.fs
以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下:
内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!
鸿蒙【北向应用开发+南向系统层开发】文档
鸿蒙【基础+实战项目】视频
鸿蒙面经
为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!