Administrator
发布于 2026-09-02 / 1,121 阅读
0
0

鸿蒙 SDK 二次封装:内部 Framework 设计实践

系列文章《鸿蒙开发从入门到专家》第 48 篇 · 高级

做鸿蒙应用开发到一定规模,你会发现一个问题:每个业务模块都在重复封装系统能力。

网络模块在封装 @kit.NetworkKit 的 http,存储模块在封装 @kit.ArkData 的 preferences,日志模块在封装 @kit.PerformanceAnalysisKit 的 hilog。每个模块的封装方式不同,接口风格不统一,升级系统 API 时要改 N 个地方。

这时候该不该抽象一个内部 framework?答案是看规模。如果你的应用只有 3-5 个模块,各模块自己封装没问题。如果超过 10 个模块,或者有多个应用要复用同一套封装,那就该做。

这篇讲怎么设计一个内部 framework,以及设计过程中最容易踩的坑。

一、什么时候该做 Framework

信号

含义

同一系统能力被 3+ 模块重复封装

重复劳动

各模块 API 风格不统一

新人上手成本高

系统 API 升级要改多处

维护负担重

有多个应用共享同一套能力

跨应用复用需求

如果只有 1-2 个信号,可以先在模块内做封装。如果 3 个以上信号同时出现,做 framework。

二、Framework 的分层设计

2.1 三层架构

一个合理的内部 framework 分三层:

┌─────────────────────────────────┐
│     Business Modules            │  ← 业务层:各业务模块
│  (login, order, payment, ...)   │
├─────────────────────────────────┤
│     Internal Framework          │  ← 框架层:统一封装
│  ├── network                    │
│  ├── storage                    │
│  ├── log                        │
│  ├── router                     │
│  └── permission                 │
├─────────────────────────────────┤
│     HarmonyOS Kit APIs          │  ← 系统层:官方 API
│  (@kit.NetworkKit, etc.)        │
└─────────────────────────────────┘

框架层做的是"系统能力的适配和增强",不是"业务逻辑的封装"。框架层不应该知道"登录"或"订单"是什么。

2.2 模块划分原则

模块

封装的系统能力

对外提供

network

http 请求、WebSocket

统一的 Request/Response 接口

storage

preferences、RDB、文件

统一的 KV 和文件接口

log

hilog

统一的 Logger 接口

router

router、Navigation

统一的路由跳转和参数传递

permission

abilityAccessCtrl

统一的权限申请流程

模块划分的依据是"系统能力的边界",不是"业务功能"。不要把"用户管理"和"网络请求"放一个模块——前者是业务,后者是基础设施。

三、Network 模块设计

以网络模块为例,讲完整的封装思路。

3.1 设计目标

  1. 统一请求/响应格式。

  2. 统一错误处理。

  3. 统一拦截器机制(添加 token、日志、重试)。

  4. 超时和重试可配置。

  5. 不绑定具体业务,不包含业务逻辑。

3.2 接口定义

// framework/network/types.ts

export interface RequestConfig {
  url: string;
  method: HttpMethod;
  headers?: Record<string, string>;
  data?: string | Object | ArrayBuffer;
  timeout?: number;
  retry?: number;
}

export interface Response<T = any> {
  code: number;
  data: T;
  headers: Record<string, string>;
}

export enum HttpMethod {
  GET = 'GET',
  POST = 'POST',
  PUT = 'PUT',
  DELETE = 'DELETE'
}

export interface Interceptor {
  onRequest?(config: RequestConfig): Promise<RequestConfig>;
  onResponse?(response: Response): Promise<Response>;
  onError?(error: NetworkError): Promise<NetworkError>;
}

export class NetworkError extends Error {
  constructor(
    public code: number,
    message: string,
    public config?: RequestConfig
  ) {
    super(message);
  }
}

3.3 实现

// framework/network/HttpClient.ts
import { http } from '@kit.NetworkKit';
import type { RequestConfig, Response, Interceptor, HttpMethod } from './types';

export class HttpClient {
  private interceptors: Interceptor[] = [];
  private defaultTimeout: number = 10000;

  addInterceptor(interceptor: Interceptor): void {
    this.interceptors.push(interceptor);
  }

  async request<T = any>(config: RequestConfig): Promise<Response<T>> {
    let finalConfig = { timeout: this.defaultTimeout, ...config };

    // 请求拦截器
    for (const interceptor of this.interceptors) {
      if (interceptor.onRequest) {
        finalConfig = await interceptor.onRequest(finalConfig);
      }
    }

    // 执行请求
    const httpResponse = await this.executeRequest(finalConfig);

    // 构造响应
    let response: Response<T> = {
      code: httpResponse.responseCode,
      data: this.parseData(httpResponse.result),
      headers: httpResponse.header
    };

    // 响应拦截器
    for (const interceptor of this.interceptors) {
      if (interceptor.onResponse) {
        response = await interceptor.onResponse(response);
      }
    }

    return response;
  }

  private async executeRequest(config: RequestConfig): Promise<http.HttpResponse> {
    const httpRequest = http.createHttp();

    try {
      const response = await httpRequest.request(config.url, {
        method: this.mapMethod(config.method),
        header: config.headers,
        extraData: config.data,
        connectTimeout: config.timeout,
        readTimeout: config.timeout,
      });

      return response;
    } finally {
      httpRequest.destroy();
    }
  }

  private mapMethod(method: HttpMethod): http.RequestMethod {
    const map: Record<HttpMethod, http.RequestMethod> = {
      [HttpMethod.GET]: http.RequestMethod.GET,
      [HttpMethod.POST]: http.RequestMethod.POST,
      [HttpMethod.PUT]: http.RequestMethod.PUT,
      [HttpMethod.DELETE]: http.RequestMethod.DELETE,
    };
    return map[method];
  }

  private parseData(raw: string | Object | ArrayBuffer): any {
    if (typeof raw === 'string') {
      try {
        return JSON.parse(raw);
      } catch {
        return raw;
      }
    }
    return raw;
  }
}

3.4 预置拦截器

框架层提供常用的拦截器,业务层按需挂载:

// framework/network/interceptors/TokenInterceptor.ts
export class TokenInterceptor implements Interceptor {
  constructor(private getToken: () => string) {}

  async onRequest(config: RequestConfig): Promise<RequestConfig> {
    const token = this.getToken();
    if (token) {
      config.headers = {
        ...config.headers,
        'Authorization': `Bearer ${token}`
      };
    }
    return config;
  }
}

// framework/network/interceptors/RetryInterceptor.ts
export class RetryInterceptor implements Interceptor {
  constructor(private maxRetries: number = 3) {}

  async onError(error: NetworkError): Promise<NetworkError> {
    const retryCount = (error.config as any)?._retryCount || 0;
    if (retryCount < this.maxRetries && this.isRetryable(error.code)) {
      (error.config as any)._retryCount = retryCount + 1;
      // 返回特殊标记,由 HttpClient 重新执行
      throw new RetrySignal(error.config);
    }
    return error;
  }

  private isRetryable(code: number): boolean {
    return code >= 500 || code === 0;  // 服务端错误或网络超时
  }
}

3.5 业务层使用

// 业务模块的初始化代码
const client = new HttpClient();
client.addInterceptor(new TokenInterceptor(() => AppStorage.get('token')));
client.addInterceptor(new LogInterceptor());

// 业务调用
const response = await client.request({
  url: '/api/users',
  method: HttpMethod.GET
});

业务层只管调 request,不用关心 token 怎么加、错误怎么重试、超时怎么设。这些都在框架层处理好了。

四、Storage 模块设计

存储模块的封装重点是统一接口,屏蔽 preferences 和 RDB 的差异。

// framework/storage/StorageManager.ts
import { preferences } from '@kit.ArkData';

export class StorageManager {
  private store: preferences.Preferences;

  static async create(context: Context, name: string): Promise<StorageManager> {
    const prefs = await preferences.getPreferences(context, name);
    return new StorageManager(prefs);
  }

  private constructor(store: preferences.Preferences) {
    this.store = store;
  }

  async get<T>(key: string, defaultValue: T): Promise<T> {
    const value = await this.store.get(key, JSON.stringify(defaultValue));
    try {
      return JSON.parse(value as string) as T;
    } catch {
      return defaultValue;
    }
  }

  async put<T>(key: string, value: T): Promise<void> {
    await this.store.put(key, JSON.stringify(value));
    await this.store.flush();
  }

  async remove(key: string): Promise<void> {
    await this.store.delete(key);
    await this.store.flush();
  }
}

业务层用的时候不关心底层是 preferences 还是别的存储引擎:

const storage = await StorageManager.create(context, 'user_settings');
await storage.put('theme', { mode: 'dark' });
const theme = await storage.get('theme', { mode: 'light' });

五、设计原则与常见错误

5.1 框架层的边界

框架层封装系统能力,不包含业务逻辑。判断标准:

  • 框架层不应该出现 “user”、“order”、“product” 等业务名词。

  • 框架层不应该知道 API 的具体路径和参数含义。

  • 框架层提供通用能力(缓存、重试、日志),业务层组合使用。

如果框架层里出现了业务逻辑,说明抽象出了问题,需要重新划线。

5.2 避免过度设计

不是所有东西都需要抽象。过度设计的表现:

  • 为只有 2 个调用点的函数封装了一层接口。

  • 为未来"可能"的需求预留了 10 个配置参数,实际只用了 2 个。

  • 抽象层太多,调用栈深到看不清逻辑。

原则:先有三处重复,再抽封装。 两处重复可以先复制粘贴,三处再考虑抽象。过早抽象和错误抽象的成本比重复代码更高。

5.3 版本管理

内部 framework 应该独立为一个 HAR(Harmony Archive)包,有自己的版本号和 changelog。业务模块通过 oh-package.json5 依赖:

{
  "dependencies": {
    "@internal/framework": "file:../framework"
  }
}

升级 framework 时走版本管理流程,业务模块按需升级。不要让 framework 和业务代码混在一个仓库里随意改动。

5.4 文档

framework 的每个模块应该有:

  • 接口定义(.d.ts 文件自带)。

  • 使用示例。

  • 升级时的 breaking changes 列表。

没有文档的 framework 没人敢用,最后会变成只有作者自己维护的"私有库"。

小结

要点

结论

何时做

3+ 模块重复封装、API 风格不统一、跨应用复用

分层

系统层 → 框架层 → 业务层,框架不含业务逻辑

网络模块

统一 Request/Response + 拦截器机制

存储模块

统一 KV 接口,屏蔽底层差异

原则

先有三处重复再抽象,不过度设计

管理

独立 HAR 包 + 版本号 + 文档

下一篇讲 H5 容器化。当你的应用有大量 H5 页面时,需要一个专门的容器框架来管理加载、通信和离线缓存。


评论