08 ArkTS 类与静态方法设计:单例、DAO 与设计令牌

08 ArkTS 类与静态方法设计:单例、DAO 与设计令牌

前言

图:08 ArkTS 类与静态方法设计:单例、DAO 与设计令牌 运行效果截图(HarmonyOS NEXT)

ArkTS 完整支持 TypeScript 的面向对象特性------类(class)、继承(extends)、接口(implements)、泛型(generics)。但在鸿蒙应用开发中,类的使用场景有其特有的模式:单例模式管理有状态的服务 (如数据库连接)、纯静态类承载设计令牌 (如颜色、字体常量)、全静态方法类封装数据操作(如 DAO 类)。

本文以"鹿鹿"项目中的三类典型类设计------DatabaseService(单例)、AppColors(静态常量)、UserDao(全静态方法)------为例,深入讲解 ArkTS 类的设计模式与最佳实践。

ArkTS 类定义文档:developer.huawei.com

项目源码:harmony-app GitHub

图:三种类设计模式------单例类(DatabaseService)、静态常量类(AppColors)、全静态方法类(DAO)


一、ArkTS 类的基础特性

1.1 类语法概览

typescript 复制代码
class Animal {
  // 成员变量(属性)
  name: string;
  private age: number;
  protected species: string = 'Unknown';
  readonly birthYear: number;

  // 构造函数
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    this.birthYear = new Date().getFullYear() - age;
  }

  // 实例方法
  speak(): string {
    return `${this.name} says hello`;
  }

  // 静态方法
  static create(name: string): Animal {
    return new Animal(name, 0);
  }

  // Getter
  get info(): string {
    return `${this.name}, age ${this.age}`;
  }
}

1.2 访问修饰符

修饰符 可见范围 用途
public(默认) 任何地方 对外暴露的接口
private 类内部 内部实现细节(如 _instance
protected 类及子类 子类可以访问的属性
readonly 任何地方(只读) 初始化后不可修改的常量
static 通过类名访问 类级别(无需实例)的属性/方法

二、单例模式:DatabaseService

2.1 为什么需要单例

DatabaseService 管理着 RDB 数据库连接,整个应用只需要一个数据库连接实例。使用单例模式确保:

  • 连接复用:避免重复创建数据库文件和连接
  • 全局访问 :任何 DAO 类都可以通过 DatabaseService.getInstance() 获取同一个连接
  • 初始化控制:确保数据库只初始化一次,防止并发初始化导致的数据库文件损坏

#mermaid-svg-Xg6qvMConXvhJone{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Xg6qvMConXvhJone .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Xg6qvMConXvhJone .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Xg6qvMConXvhJone .error-icon{fill:#552222;}#mermaid-svg-Xg6qvMConXvhJone .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Xg6qvMConXvhJone .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Xg6qvMConXvhJone .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Xg6qvMConXvhJone .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Xg6qvMConXvhJone .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Xg6qvMConXvhJone .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Xg6qvMConXvhJone .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Xg6qvMConXvhJone .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Xg6qvMConXvhJone .marker.cross{stroke:#333333;}#mermaid-svg-Xg6qvMConXvhJone svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Xg6qvMConXvhJone p{margin:0;}#mermaid-svg-Xg6qvMConXvhJone .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Xg6qvMConXvhJone .cluster-label text{fill:#333;}#mermaid-svg-Xg6qvMConXvhJone .cluster-label span{color:#333;}#mermaid-svg-Xg6qvMConXvhJone .cluster-label span p{background-color:transparent;}#mermaid-svg-Xg6qvMConXvhJone .label text,#mermaid-svg-Xg6qvMConXvhJone span{fill:#333;color:#333;}#mermaid-svg-Xg6qvMConXvhJone .node rect,#mermaid-svg-Xg6qvMConXvhJone .node circle,#mermaid-svg-Xg6qvMConXvhJone .node ellipse,#mermaid-svg-Xg6qvMConXvhJone .node polygon,#mermaid-svg-Xg6qvMConXvhJone .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Xg6qvMConXvhJone .rough-node .label text,#mermaid-svg-Xg6qvMConXvhJone .node .label text,#mermaid-svg-Xg6qvMConXvhJone .image-shape .label,#mermaid-svg-Xg6qvMConXvhJone .icon-shape .label{text-anchor:middle;}#mermaid-svg-Xg6qvMConXvhJone .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Xg6qvMConXvhJone .rough-node .label,#mermaid-svg-Xg6qvMConXvhJone .node .label,#mermaid-svg-Xg6qvMConXvhJone .image-shape .label,#mermaid-svg-Xg6qvMConXvhJone .icon-shape .label{text-align:center;}#mermaid-svg-Xg6qvMConXvhJone .node.clickable{cursor:pointer;}#mermaid-svg-Xg6qvMConXvhJone .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Xg6qvMConXvhJone .arrowheadPath{fill:#333333;}#mermaid-svg-Xg6qvMConXvhJone .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Xg6qvMConXvhJone .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Xg6qvMConXvhJone .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Xg6qvMConXvhJone .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Xg6qvMConXvhJone .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Xg6qvMConXvhJone .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Xg6qvMConXvhJone .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Xg6qvMConXvhJone .cluster text{fill:#333;}#mermaid-svg-Xg6qvMConXvhJone .cluster span{color:#333;}#mermaid-svg-Xg6qvMConXvhJone div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Xg6qvMConXvhJone .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Xg6qvMConXvhJone rect.text{fill:none;stroke-width:0;}#mermaid-svg-Xg6qvMConXvhJone .icon-shape,#mermaid-svg-Xg6qvMConXvhJone .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Xg6qvMConXvhJone .icon-shape p,#mermaid-svg-Xg6qvMConXvhJone .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Xg6qvMConXvhJone .icon-shape .label rect,#mermaid-svg-Xg6qvMConXvhJone .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Xg6qvMConXvhJone .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Xg6qvMConXvhJone .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Xg6qvMConXvhJone :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} EntryAbility
DatabaseService.getInstance
UserDao
ArchiveDao
HandwritingDao
ReportDao
lulu_handwriting.db

2.2 完整单例实现

typescript 复制代码
// DatabaseService.ets
import relationalStore from '@ohos.data.relationalStore';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common } from '@kit.AbilityKit';

const TAG = 'DatabaseService';
const DB_NAME = 'lulu_handwriting.db';
const DB_VERSION = 2;

export class DatabaseService {
  // ① 私有静态实例(核心:单例容器)
  private static _instance: DatabaseService | null = null;

  // ② 私有实例属性
  private store: relationalStore.RdbStore | null = null;
  private initPromise: Promise<void> | null = null;  // 防重入锁

  // ③ 公开静态获取方法(懒汉式)
  static getInstance(): DatabaseService {
    if (!DatabaseService._instance) {
      DatabaseService._instance = new DatabaseService();
    }
    return DatabaseService._instance;
  }

  // ④ 异步初始化方法
  async init(context: common.UIAbilityContext): Promise<void> {
    // 幂等性:已初始化则直接返回
    if (this.store) {
      hilog.info(0x0000, TAG, 'DB already initialized');
      return;
    }

    // 防重入:正在初始化时返回同一个 Promise
    if (this.initPromise) {
      return this.initPromise;
    }

    // 开始初始化
    this.initPromise = this.doInit(context);
    return this.initPromise;
  }

  private async doInit(context: common.UIAbilityContext): Promise<void> {
    try {
      const config: relationalStore.StoreConfig = {
        name: DB_NAME,
        securityLevel: relationalStore.SecurityLevel.S2
      };

      this.store = await relationalStore.getRdbStore(context, config);
      hilog.info(0x0000, TAG, 'RDB store created, version=%{public}d', DB_VERSION);

      // 执行 DDL(创建表 / 版本升级)
      await this.createTables();
    } catch (e) {
      hilog.error(0x0000, TAG, 'DB init failed: %{public}s', JSON.stringify(e));
      this.initPromise = null;  // 失败后清空,允许重试
      throw e;
    }
  }

  // ⑤ 安全获取 store
  getStore(): relationalStore.RdbStore {
    if (!this.store) {
      throw new Error('DatabaseService 未初始化,请先调用 init()');
    }
    return this.store;
  }

  // ⑥ 状态查询
  isReady(): boolean {
    return this.store !== null;
  }

  // ⑦ 创建表(DDL)
  private async createTables(): Promise<void> {
    const ddl = [
      `CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        open_id TEXT, name TEXT NOT NULL,
        avatar TEXT, created_at INTEGER NOT NULL
      )`,
      `CREATE TABLE IF NOT EXISTS archives (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER NOT NULL, name TEXT NOT NULL,
        emoji TEXT, relation TEXT, color TEXT,
        last_record_at INTEGER, record_count INTEGER DEFAULT 0,
        created_at INTEGER NOT NULL
      )`,
      // ... 其他表
    ];

    for (const sql of ddl) {
      await this.store!.executeSql(sql);
    }
    hilog.info(0x0000, TAG, 'All tables created/verified');
  }
}

2.3 防重入初始化的设计

initPromise 字段是防止并发初始化的关键:

typescript 复制代码
// 场景:多个 DAO 同时在启动阶段调用(竞态)
// 时刻 0: EntryAbility 调用 init()
// 时刻 1: UserDao.ensureLocalUser() 也调用了 init()(因为 DatabaseService 未就绪)

// 没有 initPromise 时:
// → 两次 getRdbStore() 并发执行 → 可能创建两个数据库文件 → 数据混乱

// 有 initPromise 时:
// 时刻 0: initPromise = doInit()   ← 开始初始化
// 时刻 1: return this.initPromise  ← 返回同一个 Promise,等待完成
// 时刻 2: 两个调用者都等到同一个初始化完成

三、静态常量类:AppColors

3.1 纯静态常量类设计

AppColors 是一个没有实例状态的静态工具类 ,所有成员都是 static readonly

typescript 复制代码
// AppColors.ets --- 完整颜色令牌定义
export class AppColors {
  // 背景色系
  static readonly BG: string = '#F1ECE2';          // 页面背景(米色)
  static readonly CARD: string = '#F9F5EF';         // 卡片背景(浅米)
  static readonly PAPER: string = '#F5EFE4';        // 纸张感(标签背景)
  static readonly BG_DEEP: string = '#E8E0D5';      // 稍深背景

  // 品牌色系(暖棕)
  static readonly PRIMARY: string = '#A8907A';       // 主色(中棕)
  static readonly PRIMARY_DEEP: string = '#8B7264';  // 深主色(深棕)
  static readonly PRIMARY_LIGHT: string = '#C4AA8E'; // 浅主色(浅棕)
  static readonly WARM: string = '#D9A689';          // 暖橙色

  // 情绪色系(用于情绪可视化)
  static readonly QUIET: string = '#7B96C2';         // 沉静蓝
  static readonly ROSE: string = '#D4849A';          // 玫瑰粉
  static readonly DEEP: string = '#6B5D52';          // 深棕

  // 文字色系(三级层次)
  static readonly TEXT: string = '#3D352E';          // 主文字(最深)
  static readonly TEXT_2: string = '#8B7E72';        // 次要文字
  static readonly TEXT_3: string = '#B5A99A';        // 第三级文字(最浅)

  // 线条/边框
  static readonly LINE: string = '#E8E0D5';          // 分割线、边框
}

3.2 使用方式

typescript 复制代码
// 直接通过类名访问(无需实例化)
Text('标题').fontColor(AppColors.TEXT)
Row().backgroundColor(AppColors.CARD)
Circle().fill(AppColors.PRIMARY)
Column().borderColor(AppColors.LINE)

// 对比:不使用设计令牌的魔数写法(禁止)
// Text('标题').fontColor('#3D352E')  // ❌ 禁止硬编码颜色

3.3 abstract class vs 普通 class

ArkTS 中可以用 abstract class 来禁止实例化:

typescript 复制代码
// 使用 abstract class 明确禁止实例化
export abstract class AppAnimations {
  static readonly FADE_IN_DURATION: number = 500;
  static readonly RADAR_DRAW_DURATION: number = 800;
  static readonly CURVE_SMOOTH: Curve = Curve.FastOutSlowIn;
}

// 使用普通 class(也可以,但理论上可以 new)
export class AppColors {
  static readonly BG: string = '#F1ECE2';
  // 如果不想被实例化,可以加私有构造函数:
  // private constructor() {}
}

四、全静态方法类:UserDao

4.1 UserDao 设计

UserDao 使用全静态方法设计,无实例状态:

typescript 复制代码
// UserDao.ets
import relationalStore from '@ohos.data.relationalStore';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { DatabaseService } from './DatabaseService';
import { UserEntity } from './Models';

const TAG = 'UserDao';
const TABLE = 'users';

export class UserDao {
  // 私有 getStore:强制通过 DatabaseService 获取连接
  private static getStore(): relationalStore.RdbStore {
    return DatabaseService.getInstance().getStore();
  }

  // 1. 确保本地用户存在(首次启动时调用)
  static async ensureLocalUser(defaultName: string): Promise<UserEntity> {
    try {
      const store = UserDao.getStore();
      const predicates = new relationalStore.RdbPredicates(TABLE);
      predicates.equalTo('open_id', 'local');

      const rs = await store.query(predicates, ['id', 'open_id', 'name', 'created_at']);
      if (rs.rowCount > 0) {
        rs.goToFirstRow();
        const user = UserDao.parseRow(rs);
        rs.close();
        return user;
      }
      rs.close();

      // 不存在则创建
      return await UserDao.createLocalUser(defaultName);
    } catch (e) {
      hilog.error(0x0000, TAG, 'ensureLocalUser failed: %{public}s', JSON.stringify(e));
      throw e;
    }
  }

  // 2. 创建本地用户
  static async createLocalUser(name: string): Promise<UserEntity> {
    const store = UserDao.getStore();
    const entity: UserEntity = {
      open_id: 'local',
      name,
      created_at: Date.now()
    };
    const values: relationalStore.ValuesBucket = {
      open_id: 'local',
      name,
      created_at: entity.created_at
    };
    const id = await store.insert(TABLE, values);
    return { ...entity, id };
  }

  // 3. 按华为账号 Open ID 查找
  static async findByOpenId(openId: string): Promise<UserEntity | null> {
    try {
      const store = UserDao.getStore();
      const predicates = new relationalStore.RdbPredicates(TABLE);
      predicates.equalTo('open_id', openId);
      const rs = await store.query(predicates);
      if (rs.rowCount === 0) {
        rs.close();
        return null;
      }
      rs.goToFirstRow();
      const user = UserDao.parseRow(rs);
      rs.close();
      return user;
    } catch (e) {
      hilog.error(0x0000, TAG, 'findByOpenId failed: %{public}s', JSON.stringify(e));
      return null;
    }
  }

  // 4. 更新用户名
  static async updateName(id: number, name: string): Promise<void> {
    const store = UserDao.getStore();
    const values: relationalStore.ValuesBucket = { name };
    const predicates = new relationalStore.RdbPredicates(TABLE);
    predicates.equalTo('id', id);
    await store.update(values, predicates);
  }

  // 私有解析方法
  private static parseRow(rs: relationalStore.ResultSet): UserEntity {
    return {
      id: rs.getLong(rs.getColumnIndex('id')),
      open_id: rs.getString(rs.getColumnIndex('open_id')),
      name: rs.getString(rs.getColumnIndex('name')),
      avatar: rs.isColumnNull(rs.getColumnIndex('avatar')) ? undefined
        : rs.getString(rs.getColumnIndex('avatar')),
      created_at: rs.getLong(rs.getColumnIndex('created_at'))
    };
  }
}

五、三种类设计模式对比

5.1 选型决策矩阵

场景 模式 示例 关键特征
管理有状态的系统资源 单例 DatabaseService private static _instance
承载只读配置常量 纯静态常量类 AppColorsAppFontsAppAnimations 所有成员 static readonly
封装无状态的数据操作 全静态方法类 UserDaoArchiveDao 所有方法 static,无实例
管理 UI 组件状态 @Component struct HandwritingRadarHmTitleBar ArkUI 组件,有 build()

5.2 单例 vs 全静态类

typescript 复制代码
// 单例模式:需要持有状态(store 是实例状态)
class DatabaseService {
  private store: RdbStore | null = null;    // 实例状态
  static getInstance(): DatabaseService { ... }
  async init(): Promise<void> { ... }
  getStore(): RdbStore { ... }
}

// 全静态类:无需持有状态(通过单例获取 store)
class UserDao {
  // 没有任何实例变量
  static async create(item: UserEntity): Promise<number> {
    const store = DatabaseService.getInstance().getStore();  // 每次方法调用时获取
    return store.insert('users', toValuesBucket(item));
  }
}

六、继承与多态

6.1 抽象类与继承

typescript 复制代码
// 抽象基类(不可直接实例化)
abstract class BaseAiService {
  protected isInitialized: boolean = false;

  // 抽象方法(子类必须实现)
  abstract init(): Promise<boolean>;

  // 模板方法
  async ensureInit(): Promise<void> {
    if (!this.isInitialized) {
      this.isInitialized = await this.init();
    }
  }
}

// 具体实现类
class OcrService extends BaseAiService {
  async init(): Promise<boolean> {
    // OCR 模型加载逻辑
    return true;
  }

  async recognize(imagePath: string): Promise<string> {
    await this.ensureInit();
    // OCR 识别逻辑
    return '';
  }
}

6.2 接口实现(implements)

typescript 复制代码
interface Closeable {
  close(): void;
}

interface Queryable<T> {
  findById(id: number): Promise<T | null>;
  listAll(): Promise<T[]>;
}

// 实现多个接口
class ResourceManager implements Closeable, Queryable<string> {
  close(): void { /* 关闭资源 */ }
  findById(id: number): Promise<string | null> { return Promise.resolve(null); }
  listAll(): Promise<string[]> { return Promise.resolve([]); }
}

七、注意事项与常见问题

7.1 开发注意事项

在实际开发过程中,需特别注意以下几点:

  • API 兼容性:部分接口仅在特定 HarmonyOS NEXT 版本中可用,需做版本条件判断
  • 权限模型:采用静态声明(module.json5)+ 动态申请(requestPermissionsFromUser)的两阶段授权
  • 生命周期 :合理使用 aboutToAppear()aboutToDisappear() 管理资源初始化与释放
  • 状态同步 :跨页面数据通过 AppStorage 共享,组件内状态使用 @State / @Prop / @Link 装饰器

7.2 常见错误与解决方案

常见问题快速排查表:

问题类型 排查方向 参考方法
应用崩溃 查看 hilog 错误日志 hilog.error(TAG, "...", e.message)
状态丢失 检查 AppStorage 键名拼写 统一使用常量管理键名
动画不流畅 避免在 animateTo 回调中执行 I/O 动画与数据操作分离

八、最佳实践与性能优化

8.1 代码组织规范

以下规范可显著提升代码可读性与可维护性:

  • 分层解耦:UI 逻辑(pages/)与数据操作(DAO/Service)严格分离,不在 build() 中调用数据库
  • 组件拆分 :单个 @Component 保持 100~200 行以内,大型组件拆分为 @Builder 子函数
  • 令牌约束 :颜色、字号、间距统一引用 AppColors / AppFonts / AppAnimations,禁止魔数
  • 错误处理 :异步方法统一返回 Promise<T | null>,异常时 hilog.error 记录并返回 null

8.2 性能优化要点

以下是常见性能优化措施的对比分析:

优化维度 优化前 优化后 效果
列表渲染 ForEach 全量渲染 LazyForEach 按需渲染 内存降低 40-60%
图片加载 不指定尺寸 指定 width/height 减少布局重计算
数据库查询 每次重新查询 合理缓存 + 精确 Predicates 查询速度提升 5-10x
动画实现 逐帧手动绘制 animateTo 属性动画 稳定 60fps
状态更新 全局刷新 @State 精细化作用域 减少无效渲染

总结

ArkTS 类的设计在鸿蒙开发中形成了三种典型模式:

  1. 单例模式DatabaseService):私有静态实例 + getInstance() + 异步初始化 + 防重入锁
  2. 静态常量类AppColors):所有成员 static readonly,无实例状态,直接类名访问
  3. 全静态方法类UserDao):所有方法 static,依赖单例获取状态,无需实例化

每种模式对应不同的使用场景,合理选择能让代码更简洁、可维护。

下一篇预告第09篇 ArkTS 异步编程:Promise 与 async/await

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

相关推荐
ZZZMMM.zip1 小时前
基于鸿蒙HarmonyOS NEXT开发AI天气助手应用:智能出行新体验与鸿蒙Flutter框架跨端实践
人工智能·flutter·华为·harmonyos·鸿蒙系统
心中有国也有家2 小时前
AtomGit Flutter 鸿蒙客户端:一键记录情绪的极致体验
学习·flutter·华为·harmonyos
ZZZMMM.zip2 小时前
营养方程式-营养餐单规划的HarmonyOS开发实践
人工智能·华为·harmonyos·鸿蒙系统
不才难以繁此生2 小时前
HarmonyOS RDB 实战|中式美食菜谱表设计:食材、步骤与购物清单怎么落库
flutter·harmonyos·rdb·中式美食·本地持久化
小雨青年2 小时前
【HarmonyOS 7开发者前瞻】06 HarmonyOS 7 AI 能力接入前,先用业务场景判断优先级
人工智能·华为·harmonyos
TrisighT2 小时前
鸿蒙 ArkWeb 混合开发全攻略:JSBridge 双向通信 + 实战登录注入
前端·harmonyos·arkts
●VON3 小时前
HarmonyKit | 鸿蒙新特性:pasteboard 剪贴板 API 集成与 promptAction 迁移指南
华为·harmonyos
木木子224 小时前
# 星座配对深度解析:Select 长列表选择器、矩阵评分算法与数据驱动建议
java·算法·华为·矩阵·harmonyos
liukuang1104 小时前
AI PC五强争霸:联想赌算力,华为改规则,荣耀卷价格
人工智能·华为