《文件查询》五、小说文件查询案例指南

HarmonyOS 小说文件查询案例:TaskPool + State Management V2 实战

效果

前言

本文通过一个完整的小说文件查询应用案例,演示如何在 HarmonyOS 中结合 TaskPool 多线程State Management V2 进行应用开发。案例覆盖了从项目搭建、数据建模、并发服务到 UI 渲染的完整开发流程。

通过本案例,你将掌握:

  • 使用 @Concurrent + fileIo 在子线程中执行文件查询
  • 使用 @ComponentV2 + @ObservedV2 构建响应式 UI
  • 使用 @Local + @Trace 实现细粒度状态管理
  • 数据在 TaskPool 子线程与主线程之间的传递模式

一、项目概述

1.1 功能描述

本应用实现了一个小说文件查询工具,核心功能包括:

  • 初始化加载:应用启动时,根据嵌入的元数据在沙箱目录中创建小说文件(避免子线程访问 rawfile 路径限制)
  • 关键词搜索:输入关键词后,在子线程中按文件名、作者、内容进行搜索
  • 分类过滤:支持按「科幻、悬疑、文学、历史、现代」分类筛选
  • 多维排序:支持按匹配度、文件大小、名称排序
  • 实时反馈:搜索带 300ms 防抖,查询过程显示加载动画

1.2 技术栈

技术说明
语言ArkTS
SDKHarmonyOS 6.1 (API 23)
状态管理State Management V2
多线程@ohos.taskpool + @Concurrent
文件操作@kit.CoreFileKit (fileIo)
组件模型@ComponentV2

1.3 UI 效果

┌──────────────────────────────────┐
│  小说查询                TaskPool │
├──────────────────────────────────┤
│  🔍 搜索小说名称、作者或内容...   │
├──────────────────────────────────┤
│  全部  科幻  悬疑  文学  历史 现代│
├──────────────────────────────────┤
│  共 100 个结果   匹配度|大小|名称 │
├──────────────────────────────────┤
│ ┌──────────────────────────────┐ │
│ │ 科幻              匹配度 100%│ │
│ │ 星际迷航纪                   │ │
│ │ 作者:陈星河                 │ │
│ │ 公元2387年,人类文明已经...  │ │
│ │ 📄 1.2KB  📅 2024-06-20     │ │
│ └──────────────────────────────┘ │
│ ┌──────────────────────────────┐ │
│ │ 悬疑              匹配度 80% │ │
│ │ 古城密码                     │ │
│ │ ...                          │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────┘

二、项目结构设计

2.1 目录结构

entry/src/main/
├── ets/
│   ├── common/
│   │   └── Constants.ets              # 常量定义 + 小说种子数据
│   ├── model/
│   │   └── NovelFileInfo.ets          # @ObservedV2 数据模型
│   ├── service/
│   │   └── NovelQueryService.ets      # @Concurrent 查询服务
│   ├── viewmodel/
│   │   └── NovelListViewModel.ets     # @ObservedV2 视图模型
│   └── pages/
│       └── Index.ets                  # 主页面 (@ComponentV2)
├── resources/
│   └── rawfile/
│       └── novels/                    # 原始小说文件(参考素材,非运行时依赖)
│           ├── 星际迷航纪.txt
│           ├── 古城密码.txt
│           └── ... (100个原创小说文件)

设计说明:小说元数据(标题、作者、分类、简介)以 NOVEL_SEEDS 常量数组的形式嵌入代码中。子线程在沙箱目录中根据种子数据创建文件,不依赖 rawfile 路径访问。这是因为 TaskPool 子线程无法通过 fileIo 直接访问 rawfile 目录。

2.2 架构分层

┌────────────────────────────────────────────────┐
│  UI 层 (@ComponentV2)                          │
│  Index.ets → SearchBar + FilterBar + CardList  │
└──────────────┬─────────────────────────────────┘
               │ @Local viewModel
┌──────────────▼─────────────────────────────────┐
│  ViewModel 层 (@ObservedV2)                    │
│  NovelListViewModel → 过滤/排序/状态管理        │
└──────────────┬─────────────────────────────────┘
               │ 调用
┌──────────────▼─────────────────────────────────┐
│  Service 层 (主线程封装)                        │
│  NovelQueryHelper → Task 创建 + 数据转换        │
└──────────────┬─────────────────────────────────┘
               │ taskpool.execute
┌──────────────▼─────────────────────────────────┐
│  TaskPool 层 (子线程)                           │
│  @Concurrent initNovelFiles / queryNovelFiles   │
│  fileIo 文件读写操作                            │
└────────────────────────────────────────────────┘

2.3 数据流向

  1. 初始化aboutToAppearNovelQueryHelper.initFiles(NOVEL_SEEDS) → TaskPool 子线程将种子数据写入沙箱文件 → 返回 NovelFileRawData[] → 转换为 NovelFileInfo[] → 赋值给 ViewModel
  2. 搜索:用户输入 → 防抖 300ms → NovelQueryHelper.search() → TaskPool 子线程匹配 → 结果返回 UI
  3. 过滤/排序:分类切换/排序按钮 → ViewModel 前端过滤 → @Trace 自动刷新 UI

三、数据模型层

3.1 NovelFileRawData — 跨线程传输载体

由于 TaskPool 子线程返回的数据必须是可序列化类型,而 @ObservedV2 类实例不可序列化,因此定义了纯 interface 作为传输载体:

export interface NovelFileRawData {
  fileName: string      // 文件名
  filePath: string      // 完整路径
  fileSize: number      // 文件大小(字节)
  lastModified: number  // 最后修改时间戳
  preview: string       // 内容预览(前120字符)
  matchScore: number    // 搜索匹配度(0-100)
  category: string      // 分类(科幻/悬疑/文学等)
  author: string        // 作者
  title: string         // 标题
}

设计要点:只包含基本类型(string、number),确保可以被序列化后跨线程传递。

3.2 NovelFileInfo — @ObservedV2 响应式模型

主线程接收到 NovelFileRawData 后,转换为 @ObservedV2 类实例,实现 UI 的细粒度响应式更新:

@ObservedV2
export class NovelFileInfo {
  @Trace fileName: string = ''
  @Trace title: string = ''
  @Trace author: string = ''
  @Trace category: string = ''
  @Trace fileSize: number = 0
  @Trace lastModified: number = 0
  @Trace filePath: string = ''
  @Trace preview: string = ''
  @Trace matchScore: number = 0
  @Trace isSelected: boolean = false

  constructor(raw?: NovelFileRawData) {
    if (raw) {
      this.fileName = raw.fileName
      this.title = raw.title
      this.author = raw.author
      this.category = raw.category
      this.fileSize = raw.fileSize
      this.lastModified = raw.lastModified
      this.filePath = raw.filePath
      this.preview = raw.preview
      this.matchScore = raw.matchScore
    }
  }
}

为什么要 @Trace? 每个属性都标记 @Trace,当某个小说的 matchScore 变化时,只有引用该属性的 UI 组件会重新渲染,而不是整个列表刷新。

3.3 NovelListViewModel — 页面状态管理

@ObservedV2
export class NovelListViewModel {
  @Trace novelList: Array<NovelFileInfo> = []      // 完整列表
  @Trace filteredList: Array<NovelFileInfo> = []   // 过滤后列表
  @Trace searchKeyword: string = ''                 // 搜索关键词
  @Trace currentCategory: string = '全部'           // 当前分类
  @Trace isLoading: boolean = false                 // 加载状态
  @Trace totalCount: number = 0                     // 结果总数
  @Trace currentSort: SortType = 'score'            // 排序方式
}

关键方法 — 过滤逻辑:

updateFilteredList(): void {
  let result: Array<NovelFileInfo> = [];

  for (let i = 0; i < this.novelList.length; i++) {
    let item: NovelFileInfo = this.novelList[i];

    // 分类过滤
    if (this.currentCategory !== '全部' && item.category !== this.currentCategory) {
      continue;
    }

    // 关键词过滤
    if (this.searchKeyword !== '') {
      let keyword: string = this.searchKeyword.toLowerCase();
      let match: boolean = item.title.toLowerCase().indexOf(keyword) >= 0
        || item.author.toLowerCase().indexOf(keyword) >= 0
        || item.preview.toLowerCase().indexOf(keyword) >= 0;
      if (!match) continue;
    }

    result.push(item);
  }

  this.filteredList = result;
  this.totalCount = result.length;
}

注意:使用 for 循环而非 Array.filter() 是因为 ArkTS 编译器存在泛型推断限制(arkts-no-inferred-generic-params),高阶函数链可能导致编译错误。


四、并发服务层

4.1 种子数据定义

由于 TaskPool 子线程无法通过 fileIo 访问 rawfile 目录(rawfile 需要通过 resourceManager API 在主线程访问),我们将小说元数据以常量数组的形式嵌入代码中:

// Constants.ets
export interface NovelSeedData {
  fileName: string
  title: string
  author: string
  category: string
  preview: string
}

export const NOVEL_SEEDS: NovelSeedData[] = [
  { fileName: '星际迷航纪.txt', title: '星际迷航纪', author: '陈星河', category: '科幻',
    preview: '公元2387年,人类文明已经跨越太阳系的边界...' },
  { fileName: '古城密码.txt', title: '古城密码', author: '苏墨白', category: '悬疑',
    preview: '考古学家方若琳在西北荒漠的一次抢救性发掘中...' },
  // ... 共 100 本小说
]

踩坑经验:最初尝试在 @Concurrent 函数中通过 resourceDir + '/rawfile/novels/' 访问 rawfile 文件,运行时抛出 No such file or directory 错误。这是因为 rawfile 资源在沙箱中并非以普通文件系统路径暴露,需要通过 resourceManager.getRawFileContent() 等 API 访问,而这些 API 在子线程中不可用。因此改为将元数据嵌入代码,在子线程中写入沙箱目录。

4.2 @Concurrent 初始化函数

应用启动时,根据种子数据在沙箱目录中创建小说文件。这个操作在 TaskPool 子线程中执行:

@Concurrent
function initNovelFiles(filesDir: string, seeds: Array<SeedItem>): Array<NovelFileRawData> {
  let novelsDir: string = filesDir + '/' + NOVEL_DIR_NAME;
  let results: Array<NovelFileRawData> = [];

  // 确保沙箱目录存在
  if (!fileIo.accessSync(novelsDir)) {
    fileIo.mkdirSync(novelsDir, true);
  }

  for (let i = 0; i < seeds.length; i++) {
    let seed: SeedItem = seeds[i];
    let filePath: string = novelsDir + '/' + seed.fileName;

    // 构建文件内容
    let content: string = '标题:' + seed.title + '\n' +
      '作者:' + seed.author + '\n' +
      '分类:' + seed.category + '\n\n' +
      '【内容简介】\n\n' + seed.preview;

    // 幂等写入:已存在则跳过,不存在则创建
    if (!fileIo.accessSync(filePath)) {
      let fd: fileIo.File = fileIo.openSync(filePath,
        fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
      let encoder: util.TextEncoder = util.TextEncoder.create('utf-8');
      let encoded: Uint8Array = encoder.encodeInto(content);
      fileIo.writeSync(fd.fd, encoded.buffer as ArrayBuffer);
      fileIo.closeSync(fd.fd);
    }

    // 获取文件统计信息
    let fileStat: fileIo.Stat = fileIo.statSync(filePath);

    results.push({
      fileName: seed.fileName,
      filePath: filePath,
      fileSize: fileStat.size,
      lastModified: fileStat.mtime,
      preview: seed.preview,
      matchScore: 100,
      category: seed.category,
      author: seed.author,
      title: seed.title
    });
  }
  return results;
}

关键设计:

  • 种子数据 seeds 作为参数从主线程传入(普通 interface 数组可序列化)
  • 使用 accessSync 检查文件是否已存在,保证多次启动的幂等性
  • 使用 TextEncoder 将字符串编码为 Uint8Array 后写入文件
  • 同步 API(mkdirSyncaccessSyncstatSync)在子线程中使用不会阻塞主线程

4.3 @Concurrent 查询函数

搜索功能的并发实现,支持文件名匹配和内容匹配:

@Concurrent
function queryNovelFiles(dirPath: string, keyword: string): Array<NovelFileRawData> {
  let results: Array<NovelFileRawData> = [];
  let lowerKeyword: string = keyword.toLowerCase();

  let files: Array<string> = fileIo.listFileSync(dirPath);
  for (let i = 0; i < files.length; i++) {
    let fileName: string = files[i];
    if (!fileName.endsWith('.txt')) continue;

    let content: string = readTextFromFile(dirPath + '/' + fileName);
    let metadata: MetaData = parseMetadata(content, fileName);

    // 匹配度评分
    let score: number = 100;
    if (lowerKeyword !== '') {
      let title: string = metadata.title.toLowerCase();
      if (title === lowerKeyword) {
        score = 100;          // 完全匹配
      } else if (title.indexOf(lowerKeyword) >= 0) {
        score = 80;           // 标题包含
      } else if (content.toLowerCase().indexOf(lowerKeyword) >= 0) {
        score = 50;           // 内容包含
      } else {
        score = 0;            // 不匹配
      }
    }

    if (score > 0) {
      results.push(/* 构建结果对象 */);
    }
  }

  // 按匹配度降序排列
  for (let i = 0; i < results.length - 1; i++) {
    for (let j = i + 1; j < results.length; j++) {
      if (results[j].matchScore > results[i].matchScore) {
        let temp = results[i];
        results[i] = results[j];
        results[j] = temp;
      }
    }
  }
  return results;
}

匹配度评分机制:

匹配情况评分说明
标题完全等于关键词100精确命中
标题或作者包含关键词80模糊命中
仅内容包含关键词50全文命中
都不匹配0过滤掉

4.4 NovelQueryHelper — 服务封装

NovelQueryHelper 运行在主线程,封装 TaskPool 任务的创建和执行:

export class NovelQueryHelper {
  private filesDir: string = '';

  constructor(filesDir: string) {
    this.filesDir = filesDir;
  }

  async initFiles(seeds: Array<SeedItem>): Promise<Array<NovelFileInfo>> {
    let task: taskpool.Task = new taskpool.Task(initNovelFiles, this.filesDir, seeds);
    let result: Object = await taskpool.execute(task, taskpool.Priority.MEDIUM);
    let rawDataList: Array<NovelFileRawData> = result as Array<NovelFileRawData>;
    return this.convertToNovelList(rawDataList);
  }

  async search(keyword: string): Promise<Array<NovelFileInfo>> {
    let novelsDir: string = this.filesDir + '/' + NOVEL_DIR_NAME;
    let task: taskpool.Task = new taskpool.Task(queryNovelFiles, novelsDir, keyword);
    let result: Object = await taskpool.execute(task, taskpool.Priority.HIGH);
    let rawDataList: Array<NovelFileRawData> = result as Array<NovelFileRawData>;
    return this.convertToNovelList(rawDataList);
  }

  private convertToNovelList(rawDataList: Array<NovelFileRawData>): Array<NovelFileInfo> {
    let result: Array<NovelFileInfo> = [];
    for (let i = 0; i < rawDataList.length; i++) {
      result.push(new NovelFileInfo(rawDataList[i]));
    }
    return result;
  }
}

优先级选择策略:

  • 初始化:Priority.MEDIUM(默认优先级,不抢占资源)
  • 搜索:Priority.HIGH(用户交互敏感,尽快返回结果)

五、UI 层实现

5.1 主页面声明

使用 @ComponentV2 + @Local 声明页面级状态:

@Entry
@ComponentV2
struct Index {
  @Local viewModel: NovelListViewModel = new NovelListViewModel()
  @Local searchInput: string = ''
  @Local queryHelper: NovelQueryHelper | null = null
  private searchTimer: number = -1

  aboutToAppear(): void {
    this.initData()
  }
}

V2 vs V1 对照:

V1 写法V2 写法
@Component struct Index@ComponentV2 struct Index
@State message: string@Local message: string
@State list: Array<Item>@Local vm: ViewModel(配合 @ObservedV2)

5.2 初始化流程

async initData(): Promise<void> {
  this.viewModel.isLoading = true;
  try {
    let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
    this.queryHelper = new NovelQueryHelper(context.filesDir);
    let list: Array<NovelFileInfo> = await this.queryHelper.initFiles(NOVEL_SEEDS);
    this.viewModel.setNovelList(list);
  } catch (e) {
    this.viewModel.errorMessage = `初始化失败: ${JSON.stringify(e)}`;
  }
  this.viewModel.isLoading = false;
}

流程说明:

  1. 获取 UIAbilityContext 取得 filesDir(不再需要 resourceDir
  2. 创建 NovelQueryHelper 实例,传入 filesDir
  3. 调用 initFiles(NOVEL_SEEDS),将种子数据传入 TaskPool 子线程写入沙箱文件
  4. 返回的 NovelFileInfo[] 赋值给 ViewModel
  5. isLoading 变为 false@Trace 触发 UI 刷新,列表显示

5.3 搜索防抖实现

doSearch(keyword: string): void {
  if (this.searchTimer !== -1) {
    clearTimeout(this.searchTimer);
  }
  this.searchTimer = setTimeout(async () => {
    this.viewModel.searchKeyword = keyword;
    if (keyword.trim() === '') {
      this.viewModel.clearSearch();
      return;
    }
    this.viewModel.isLoading = true;
    try {
      if (this.queryHelper) {
        let results = await this.queryHelper.search(keyword);
        this.viewModel.setServerResult(results);
      }
    } catch (e) {
      this.viewModel.errorMessage = `查询失败: ${JSON.stringify(e)}`;
    }
    this.viewModel.isLoading = false;
  }, 300);  // 300ms 防抖
}

为什么需要防抖? 用户每输入一个字符都会触发搜索,如果不加防抖,连续输入 “星际” 会触发两次 TaskPool 查询。300ms 的延迟可以合并连续输入,只在用户停顿后执行一次查询。

5.4 搜索栏组件

Row() {
  Image($r('sys.media.ohos_ic_public_search_filled'))
    .width(20).height(20).fillColor('#999999')

  TextInput({ placeholder: '搜索小说名称、作者或内容...', text: this.searchInput })
    .layoutWeight(1)
    .backgroundColor(Color.Transparent)
    .onChange((value: string) => {
      this.searchInput = value;
      this.doSearch(value);
    })

  if (this.searchInput.length > 0) {
    Image($r('sys.media.ohos_ic_public_cancel_filled'))
      .width(18).height(18).fillColor('#cccccc')
      .onClick(() => {
        this.searchInput = '';
        this.viewModel.clearSearch();
      })
  }
}

5.5 分类过滤条

使用横向 Scroll + ForEach 渲染分类标签:

Scroll() {
  Row({ space: 10 }) {
    ForEach(CATEGORIES, (category: string) => {
      Text(category)
        .fontColor(this.viewModel.currentCategory === category ? '#1890FF' : '#666666')
        .backgroundColor(this.viewModel.currentCategory === category ? '#E6F7FF' : '#F5F5F5')
        .borderRadius(16)
        .padding({ left: 14, right: 14, top: 6, bottom: 6 })
        .onClick(() => {
          this.viewModel.setCategory(category);
        })
    })
  }
}
.scrollable(ScrollDirection.Horizontal)

V2 状态驱动:当 viewModel.currentCategory 变化时,@Trace 自动触发选中状态的样式更新。

5.6 小说卡片

@Builder
NovelCard(novel: NovelFileInfo) {
  Column() {
    // 分类标签 + 匹配度
    Row() {
      Text(novel.category)
        .fontColor('#1890FF')
        .backgroundColor('#E6F7FF')
        .borderRadius(4)
      Blank()
      Text(`${novel.matchScore}%`)
        .fontColor(novel.matchScore >= 80 ? '#52C41A' : '#FAAD14')
    }

    // 标题 + 作者
    Text(novel.title).fontSize(18).fontWeight(FontWeight.Bold)
    Text(`作者:${novel.author}`).fontSize(13)

    // 内容预览
    Text(novel.preview).maxLines(3).textOverflow({ overflow: TextOverflow.Ellipsis })

    // 文件信息
    Row() {
      Text(novel.getFormattedSize())
      Text(novel.getFormattedDate())
    }
  }
  .padding(16)
  .backgroundColor('#FFFFFF')
  .borderRadius(12)
  .shadow({ radius: 4, color: 'rgba(0,0,0,0.06)', offsetX: 0, offsetY: 2 })
}

六、关键技术要点

6.1 State Management V1 → V2 对照

V1 装饰器V2 装饰器用途
@State@Local页面内状态
@Prop@Param父传子(值传递)
@Link@Param + @Event双向绑定改为单向+回调
@ObjectLink@ObservedV2 + @Trace对象级响应式
@Provide/@Consume@Provider/@Consumer跨层级状态共享
@Watch@Monitor属性变化监听
@Computed计算属性缓存

6.2 跨线程数据传输模式

主线程                                子线程 (@Concurrent)
┌──────────────────┐     ┌──────────────────┐
│ NOVEL_SEEDS      │ ──→ │  序列化传输       │
│ (interface[])    │     │  (深拷贝)         │
│ 元数据嵌入代码    │     │                   │
└──────────────────┘     └────────┬─────────┘
                                  │ 反序列化 + 写入文件
                                  ▼
                         ┌──────────────────┐
                         │ 创建沙箱文件      │
                         │ fileIo.openSync  │
                         │ fileIo.writeSync │
                         │ 返回 RawData[]    │
                         └────────┬─────────┘
                                  │ 序列化返回
                                  ▼
                         ┌──────────────────┐
                         │ new NovelFileInfo │
                         │ (@ObservedV2)     │
                         │ @Trace 属性       │
                         └──────────────────┘

核心原则

  • 种子数据作为参数传入子线程(普通 interface 数组可序列化)
  • 子线程创建文件后返回纯数据(interface),主线程将其转换为响应式对象(@ObservedV2 类)

6.3 ArkTS 泛型限制

ArkTS 编译器对泛型方法有严格限制,以下写法可能导致编译错误:

// ❌ 可能触发 arkts-no-inferred-generic-params
let names = this.novelList.map(item => item.title);

// ✅ 使用 for 循环替代
let names: Array<string> = [];
for (let i = 0; i < this.novelList.length; i++) {
  names.push(this.novelList[i].title);
}

七、性能优化要点

优化项做法效果
文件操作不阻塞 UIfileIo 操作放在 @Concurrent 子线程主线程保持 60fps
搜索防抖setTimeout 300ms减少不必要的 TaskPool 调用
精确 UI 刷新@Trace 标记每个属性只有变化的属性触发对应组件重渲染
排序优先级选择搜索用 HIGH,初始化用 MEDIUM用户交互优先响应
文件幂等创建accessSync 检查已存在则跳过避免重复 IO
预览截断只读取前 120 字符减少内存占用

八、完整源码文件清单

文件路径职责
Constants.etsets/common/常量定义 + 小说种子数据(分类、评分、配置)
NovelFileInfo.etsets/model/数据模型 + 传输接口
NovelQueryService.etsets/service/@Concurrent 并发函数 + Helper 封装
NovelListViewModel.etsets/viewmodel/视图模型(过滤/排序/状态管理)
Index.etsets/pages/主页面 UI

九、踩坑与解决方案

9.1 子线程无法访问 rawfile 目录

问题现象:在 @Concurrent 函数中通过 resourceDir + '/rawfile/novels/' 访问 rawfile 文件时,运行时抛出:

Error: No such file or directory
    at initNovelFiles (NovelQueryService.ets:22:42)
taskpool::PerformTask occur exception

根因分析

  • rawfile 资源在沙箱中并非以普通文件系统路径暴露
  • 访问 rawfile 需要使用 resourceManager.getRawFileContent() 等 API
  • resourceManager 依赖于 UIAbilityContext,在 TaskPool 子线程中不可用

解决方案

  • 将小说元数据以 NovelSeedData[] 常量数组的形式嵌入代码
  • 种子数据是纯 interface 数组,可以序列化后传入子线程
  • 子线程在 filesDir 沙箱目录中根据种子数据创建文件
  • 后续查询操作直接读取沙箱目录,无 rawfile 依赖

9.2 ArkTS 泛型推断限制

问题现象:使用 Array.filter()Array.map() 等高阶函数时编译报错。

解决方案:统一使用 for 循环替代高阶函数链,避免触发 arkts-no-inferred-generic-params

9.3 @Concurrent 函数中不可访问 UI 上下文

问题现象:在 @Concurrent 函数中调用 getContext() 报错。

解决方案:所有需要的路径信息(如 filesDir)在主线程中获取后,作为参数传入 @Concurrent 函数。


十、总结与扩展

10.1 核心收获

本案例展示了 HarmonyOS 应用开发的三个核心能力:

  1. TaskPool 多线程:将文件 IO 等耗时操作放在子线程执行,保证 UI 流畅
  2. State Management V2:通过 @ObservedV2 + @Trace 实现细粒度响应式更新
  3. 跨线程数据传递:使用纯 interface 作为序列化载体,主线程转换为响应式对象

10.2 可扩展方向

  • 全文搜索:在子线程中实现倒排索引,支持更精确的全文检索
  • 分布式文件查询:结合分布式数据管理,搜索多设备上的小说文件
  • 文件管理:添加删除、移动、重命名等文件管理功能
  • 阅读功能:添加小说全文阅读页面,支持翻页、字体设置(已实现,详见《Navigation 页面导航实现指南》)

系列文档:

  • 📘 @ohos.taskpool 使用指南 — TaskPool API 详解与使用规范
  • 📗 本文 — 基础查询功能的完整实现过程
  • 📙 Navigation 页面导航实现指南 — 详情页跳转功能的添加步骤
  • 📕 小说查询案例总体介绍指南 — 案例总体效果与架构介绍
  • 📓 小说初始化实现指南 — 种子数据设计与批量文件初始化流程

参考文档:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值