9afce6f6创建于 2025年5月7日历史提交
/*
 * Copyright (c) 2024 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 { FileListDataSource } from '../model/FileListDataSource';
import { FileItem } from '../model/FileItemModel';
import worker, { MessageEvents } from '@ohos.worker'; // 引入worker模块
import { BusinessError } from '@ohos.base';
import fs from '@ohos.file.fs'; // 引入文件管理模块
import { logger } from '../utils/Logger';
import { promptAction } from '@kit.ArkUI';

const TAG = 'DecompressFileMainPage';
const LAYOUT_WEIGHT = 1;
const CACHED_COUNT = 5; // LazyForEach预加载的Item数量
const LINEAR_GRADIENT_START = 0; // 颜色渐变起始位置
const LINEAR_GRADIENT_END = 1; // 颜色渐变结束位置

/**
 * 功能描述: 本示例介绍在Worker 子线程使用@ohos.zlib 提供的zlib.decompressfile接口对沙箱目录中的压缩文件进行解压操作,解压成功后将解压路径返回主线程,获取解压文件列表。
 *
 * 推荐场景: 解压文件
 *
 * 核心组件:
 * 1. decompressFileByWorker()
 *
 * 实现步骤:
 * 1. 将项目目录rawfile下的压缩文件写入到应用的沙箱目录,用于后续介绍解压操作。
 * 2. 解压按钮回调中创建Worker线程,主线程使用postMessage()向Worker线程发送应用沙箱路径和压缩文件名称,使用Worker子线程解压文件。
 * 3. 主线程接收Worker子线程发送过来的解压文件所在沙箱目录。
 * 4. 根据解压后的文件所属沙箱目录,获取解压后的文件列表。
 */
@Component
export struct DecompressFileViewComponent {
  @State pathDir: string = ''; // 应用沙箱目录
  @State outFileDir: string = ''; // 解压后的文件所处的应用沙箱目录
  private rawfilePath: string = ''; // rawfile压缩文件的应用沙箱路径
  private rawfileZipName: string = 'decompress_file_test.zip'; // 待解压的文件名
  private context: Context = getContext(this);
  private fileListData: FileListDataSource = new FileListDataSource(); // 解压后的文件列表

  aboutToAppear() {
    this.initZip(this.rawfileZipName)
  }

  // 性能:使用Worker子线程解压文件,https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/performance/multi_thread_capability.md/
  decompressFileByWorker(rawfileZipName: string): void {
    /**
     * TODO:知识点:主线程中使用new worker.ThreadWorker创建Worker对象。
     * TODO:知识点:@标识路径加载形式:所有种类的模块加载本地HAR中的Worker线程文件,加载路径规则:@{moduleName}/ets/{relativePath}。
     */
    let workerInstance: worker.ThreadWorker = new worker.ThreadWorker('../workers/Worker.ets');
    // TODO:知识点:主线程在onmessage中接收来自worker线程的消息。
    workerInstance.onmessage = (e: MessageEvents): void => {
      if (e.data) {
        promptAction.showToast({
          message: $r('app.string.decompress_file_toast')
        });
        this.outFileDir = e.data;
        logger.info(TAG, `Decompressed Files outFileDir: ${this.outFileDir}`);
        // 根据解压后的文件所属沙箱目录,获取解压后的文件列表
        this.getFileListData(this.outFileDir);
      } else {
        logger.error(TAG, 'Decompress Files failed!');
      }
      // TODO:知识点:主线程使用terminate()销毁Worker线程。
      workerInstance.terminate();
    }
    /**
     * TODO:知识点:主线程使用postMessage()向worker线程发送消息。
     * 主线程使用postMessage()向worker线程发送应用沙箱路径和压缩文件名称。
     */
    workerInstance.postMessage({ pathDir: this.pathDir, rawfileZipName: rawfileZipName });
  }

  // 将项目目录rawfile下的压缩文件写入到应用的沙箱目录,用于后续介绍解压操作。
  initZip(rawfileZipName: string) {
    // 使用getRowFileContent接口以字节数组的形式获取到rawfile中的文件内容。
    this.context.resourceManager.getRawFileContent(rawfileZipName, (error: BusinessError, value: Uint8Array) => {
      if (error) {
        logger.error(TAG, `getRawFileContent failed, error message: ${error.message}, error code: ${error.code}`);
      } else {
        const rawFile: Uint8Array = value;
        this.pathDir = this.context.filesDir; // 获取应用沙箱目录
        logger.info(TAG, `Application Sandbox Directory: ${this.pathDir}`);
        this.rawfilePath = `${this.pathDir}/${rawfileZipName}`; // 设置rawfile压缩文件的应用沙箱路径
        const file = fs.openSync(this.rawfilePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); // 在指定路径以同步方法打开或创建文件
        // 使用fs.write接口将字节数组形式的rawfile的文件内容写入到指定沙箱路径filePath中
        fs.write(file.fd, rawFile.buffer).then((writeLen: number) => {
          logger.info(TAG, `write data to file succeed and size is: ${writeLen}`);
        }).catch((err: BusinessError) => {
          logger.error(TAG, `write data to file failed with error message: ${err.message}, error code: ${err.code}`);
        }).finally(() => {
          logger.info(TAG, 'write finished');
          fs.closeSync(file); // 以同步方法关闭文件。
        });
      }
    });
  }

  // 获取解压后的文件列表
  getFileListData(outFileDir: string) {
    // TODO:知识点:使用fs.listFile接口获取解压得到的沙箱目录下的所有文件名。
    fs.listFile(outFileDir).then((fileNames: Array<string>) => {
      this.fileListData.clearData(); // 清空上一次获取的列表
      for (let i = 0; i < fileNames.length; i++) {
        this.fileListData.pushData(new FileItem(fileNames[i], `${outFileDir}/${fileNames[i]}`));
      }
    }).catch((err: BusinessError) => {
      logger.error('list file failed with error message: ${err.message}, error code: ${err.code}');
    });
  }

  build() {
    Column() {
      Row() {
        Column() {
          Text($r("app.string.decompress_file"))
            .fontColor($r('app.color.ohos_id_color_background'))
            .fontWeight(FontWeight.Bolder)
            .fontSize($r("app.integer.decompress_file_title_fontsize"))
          Text($r("app.string.decompress_file_sub_title"))
            .fontColor($r('app.color.ohos_id_color_background'))
        }
        .width($r("app.integer.decompress_file_title_width"))
        .alignItems(HorizontalAlign.Start)

        Image($r("app.media.decompress_file_ic_files_compress"))
          .width($r("app.integer.decompress_file_top_bar_icon_width"))
          .height($r("app.integer.decompress_file_top_bar_icon_width"))
      }
      .width('100%')
      .height($r("app.integer.decompress_file_top_bar_height"))
      .padding($r("app.integer.decompress_file_top_bar_padding"))
      .justifyContent(FlexAlign.SpaceBetween)
      .borderRadius($r('app.string.ohos_id_corner_radius_default_m'))
      .linearGradient({
        direction: GradientDirection.Top, // 渐变方向
        repeating: true, // 渐变颜色是否重复
        colors: [[$r("app.color.decompress_file_linear_gradient_start"), LINEAR_GRADIENT_START], [$r('app.color.decompress_file_linear_gradient_end'), LINEAR_GRADIENT_END]] // 数组末尾元素占比小于1时满足重复着色效果
      })

      // 压缩文件组件
      Row() {
        Image($r("app.media.decompress_file_ic_files_compress"))
          .width($r("app.integer.decompress_file_icon_width"))
          .height($r("app.integer.decompress_file_icon_width"))
          .margin({ right: $r('app.string.ohos_id_card_margin_start') })
        Text(this.rawfileZipName)
          .fontSize($r('app.string.ohos_id_text_size_body1'))
          .layoutWeight(LAYOUT_WEIGHT)
        Button($r("app.string.decompress_file_decompress"))
          .fontWeight(FontWeight.Bold)
          .type(ButtonType.Normal)
          .width($r("app.integer.decompress_file_button_width"))
          .height($r("app.integer.decompress_file_button_height"))
          .borderRadius($r('app.string.ohos_id_corner_radius_default_m'))
          .onClick(() => {
            this.decompressFileByWorker(this.rawfileZipName);
          })
      }
      .padding($r('app.string.ohos_id_card_padding_start'))
      .backgroundColor($r('app.color.ohos_id_color_background'))

      Divider()

      Text($r("app.string.decompress_file_decompressed_files"))
        .textAlign(TextAlign.Start)
        .fontSize($r('app.string.ohos_id_text_size_headline'))
        .fontWeight(FontWeight.Bold)
        .width('100%')
        .padding($r('app.string.ohos_id_card_padding_start'))
        .backgroundColor($r('app.color.decompress_file_result_background'))

      Divider()

      // 解压后的文件列表组件
      List() {
        // 性能:使用LazyForEach加载文件列表,https://gitee.com/harmonyos-cases/cases/blob/master/docs/performance/lazyforeach_optimization.md
        LazyForEach(this.fileListData, (item: FileItem) => {
          ListItem() {
            FileListItem({ item: item })
          }
        }, (item: FileItem) => JSON.stringify(item))
      }
      .cachedCount(CACHED_COUNT)
      .width('100%')
      .backgroundColor($r('app.color.ohos_id_color_background'))
      .height($r("app.integer.decompress_file_list_height"))
      .layoutWeight(LAYOUT_WEIGHT)
      .divider({
        strokeWidth: $r("app.integer.decompress_file_stroke_width"),
        color: $r('app.color.ohos_id_color_sub_background'),
        startMargin: $r('app.string.ohos_id_card_margin_start'),
        endMargin: $r('app.string.ohos_id_card_margin_start')
      }) // 每行之间的分界线
      .borderRadius({
        bottomLeft: $r('app.string.ohos_id_corner_radius_default_m'),
        bottomRight: $r('app.string.ohos_id_corner_radius_default_m')
      })

    }
    .height('100%')
    .padding($r('app.string.ohos_id_card_padding_start'))
    .backgroundColor($r('app.color.ohos_id_color_sub_background'))
  }
}

@Reusable
@Component
struct FileListItem {
  @State item: FileItem = new FileItem('', '');

  aboutToReuse(params: Record<string, FileItem>): void {
    this.item = params.item;
  }

  build() {
    Row() {
      Image($r("app.media.decompress_file_ic_files_documents"))
        .width($r("app.integer.decompress_file_icon_width"))
        .height($r("app.integer.decompress_file_icon_width"))
        .margin({ right: $r('app.string.ohos_id_card_margin_start') })
      Text(this.item.fileName)
        .fontSize($r('app.string.ohos_id_text_size_body1'))
    }
    .padding($r('app.string.ohos_id_card_padding_start'))
  }
}