2026-03-11 08:10:11 +00:00
2026-03-11 08:09:42 +00:00
2026-03-11 08:10:11 +00:00

OpenClaw 鸿蒙客户端项目设计规格及实施要求说明书

版本: 2.0
目标平台: HarmonyOS 6.0.2 (API 22)
开发语言: ArkTS严格模式
最后更新: 2026-03-11


1. 项目概述

1.1 项目目标

开发一款运行于鸿蒙手机的原生应用通过手机热点与树莓派5上部署的 OpenClaw AI 服务 通信,提供类微信的 AI 对话界面,并完整支持 Markdown 格式的内容渲染(包括流式输出效果)。应用需具备自动发现局域网内 OpenClaw 服务器的能力,并能在无法自动发现时通过手动配置连接。

1.2 核心特性

  • 原生 ArkUI 实现:基于 ArkTS + Stage 模型,完全适配 API 22。
  • 高性能 Markdown 渲染:采用支付宝开源的 FluidMarkdown 鸿蒙版,直接调用 ArkUI 渲染引擎,支持流式增量输出,告别 WebView 性能瓶颈。
  • 自动服务发现:优先使用 mDNS/DNS-SD 协议自动发现局域网内的 OpenClaw 网关,兼容手动配置。
  • 简洁对话界面:消息气泡区分用户与 AI支持本地历史记录存储。

2. 技术栈与依赖

类别 名称/库 版本/要求 说明
操作系统 HarmonyOS 6.0.2 (API 22) 编译 SDK 版本必须为 22兼容运行版本 ≥12
开发语言 ArkTS 严格模式 使用 @ohos.* 系统模块,无额外 JS 依赖
UI 框架 ArkUI API 22 内置 声明式 UI 开发
Markdown 渲染 @antgroup/fluidmarkdown ≥1.0.0 (2025.12 开源) GitHub 仓库
网络通信 @ohos.net.http API 22 内置 HTTP 客户端
服务发现 @ohos.net.mdns (待确认) API 22 可能支持 需查阅 API 22 官方文档确认 mDNS 模块是否存在
状态管理 AppStorage / LocalStorage API 22 内置 应用级状态持久化
资源回收 AutoFinalizer (Util) API 22 新特性 用于 WebView 等资源的自动回收(本项目已无 WebView但保留说明

3. 系统架构

3.1 部署架构

┌─────────────────┐      WiFi Hotspot      ┌─────────────────┐
│   华为手机       │  ═══════════════════►  │   树莓派5        │
│  (HarmonyOS)    │    (192.168.43.x)     │  (OpenClaw)      │
│                 │                        │  Port: 18789     │
│  ┌───────────┐  │                        │  mDNS Broadcast  │
│  │ OpenClaw  │  │◄───────────────────────│  _openclaw._tcp  │
│  │   Client  │  │      HTTP API          │  local.          │
│  └───────────┘  │                        └──────────────────┘
└─────────────────┘

3.2 应用架构(文件结构)

OpenClaw_Client/
├── AppScope/                                 # (无需修改)
├── entry/
│   ├── src/main/
│   │   ├── ets/
│   │   │   ├── entryability/
│   │   │   │   └── EntryAbility.ets          # 应用入口
│   │   │   ├── pages/
│   │   │   │   ├── Index.ets                 # 主聊天界面
│   │   │   │   └── Settings.ets              # 服务器配置界面
│   │   │   ├── components/
│   │   │   │   ├── ChatBubble.ets            # 消息气泡组件
│   │   │   │   └── ServerDiscovery.ets       # (可选)服务发现列表组件
│   │   │   ├── model/
│   │   │   │   └── ChatMessage.ets           # 消息数据模型
│   │   │   ├── service/
│   │   │   │   ├── OpenClawApi.ets           # OpenClaw HTTP API 封装
│   │   │   │   └── DiscoveryService.ets      # 可选mDNS 服务发现封装
│   │   │   └── utils/
│   │   │       └── StorageUtil.ets           # (可选)本地存储工具
│   │   └── resources/
│   │       └── base/
│   │           └── element/
│   │               └── string.json           # 国际化字符串
│   └── module.json5                           # 模块配置
├── build-profile.json5                         # 项目级构建配置
└── oh-package.json5                            # 项目级依赖配置

4. 功能需求

功能模块 需求描述 优先级
服务器连接 支持手动输入服务器地址IP:Port和令牌支持自动发现局域网内 OpenClaw 服务并列出,点击即连接。 P0
对话界面 类微信聊天界面底部输入框消息列表滚动。用户消息右对齐纯文本AI 消息左对齐Markdown 渲染)。 P0
Markdown 渲染 AI 返回内容必须完整支持 Markdown 语法标题、列表、代码块、表格、LaTeX 公式等)。采用 FluidMarkdown 原生渲染,支持流式追加输出(模拟逐字生成)。 P0
消息历史 自动保存最近 50 条对话到本地存储,重启应用后恢复。 P1
连接状态指示 主界面顶部显示当前连接状态(绿点/红点),点击红点可快速跳转到设置页。 P1
错误处理 网络请求失败、服务端返回错误时,在对话中以气泡形式给出明确提示。 P1
自动重连 当网络切换或服务重启时,尝试自动重新连接(可选)。 P2

5. 技术规范与约束

5.1 API 22 合规要求

  • 编译 SDK 版本必须为 22 (compileSdkVersion = 22),兼容运行版本 ≥12 (compatibleSdkVersion = 22 表示仅支持 API 22 及以上设备)。
  • 必须使用 ArkTS 严格模式,所有变量需显式类型声明。
  • 网络请求必须使用 @ohos.net.http,并正确调用 destroy() 释放资源。
  • 禁止直接操作 DOM 或使用 WebView 进行核心渲染(已替换为原生组件)。

5.2 FluidMarkdown 集成规范

  • 通过 ohpm 安装依赖:ohpm install @antgroup/fluidmarkdown
  • 在代码中导入:import { Markdown, EMarkdownMode, MarkdownController } from '@antgroup/fluidmarkdown';
  • 流式输出:若需模拟逐字输出,可将内容按字符拆分后逐步设置 content 属性FluidMarkdown 内部会增量更新。
  • 交互处理:通过 onMarkdownNodeClick 回调处理链接/图片点击事件,可使用 router.pushUrl@ohos.web.webview 打开链接。

5.3 自动服务发现mDNS实现说明

  • 首选方案:使用系统 mDNS 模块 @ohos.net.mdnsAPI 22 中需确认是否存在)。若存在,通过 addLocalServiceDiscovery 监听 _openclaw._tcp 服务。
  • 备选方案:若系统 mDNS 不可用可尝试局域网广播扫描UDP 广播 + 特定端口探测),但可靠性较低。
  • 实现位置:建议在 Settings.ets 页面中集成发现列表,并在 Index.etsaboutToAppear 中尝试自动连接上次成功的主机。
  • 权限:若使用 mDNS需在 module.json5 中添加 ohos.permission.DISTRIBUTED_DEVICE_DISCOVERY(需实际验证)。

6. 文件替换与修改指南

6.1 项目初始化

使用 DevEco Studio 创建一个新的 Empty Ability 工程,选择 Stage 模型,语言选择 ArkTS,目标 SDK 版本选择 6.0.2(22)。生成的标准工程目录结构如上所示。

6.2 文件替换与新增清单

文件路径 操作 说明
build-profile.json5 (项目根目录) 替换 设置 compileSdkVersioncompatibleSdkVersion 为 22
oh-package.json5 (项目根目录) 修改 添加 @antgroup/fluidmarkdown 依赖
entry/oh-package.json5 新增 可选,若模块级依赖独立,可在此添加依赖
entry/module.json5 替换 配置权限INTERNET、GET_NETWORK_INFO和 Ability 信息
entry/src/main/ets/entryability/EntryAbility.ets 替换 初始化全局状态 serverBaseUrl, accessToken, isServerConnected
entry/src/main/ets/model/ChatMessage.ets 新增 定义消息接口
entry/src/main/ets/service/OpenClawApi.ets 新增 封装 HTTP 请求方法 chathealthCheck
entry/src/main/ets/components/ChatBubble.ets 新增 实现消息气泡AI 消息集成 FluidMarkdown 组件
entry/src/main/ets/pages/Index.ets 替换 主聊天界面,包含消息列表、输入框、发送逻辑
entry/src/main/ets/pages/Settings.ets 新增 服务器配置界面(手动输入 + 自动发现列表占位)
entry/src/main/resources/base/element/string.json 修改 添加必要的字符串资源(可选)

重要:原 WebView 相关文件(MarkdownView.ets, markdown-it.min.js不再使用,请删除以避免编译冲突。

6.3 配置文件详细内容

6.3.1 build-profile.json5(项目根目录)

{
  "app": {
    "signingConfigs": [],
    "compileSdkVersion": 22,
    "compatibleSdkVersion": 22,
    "products": [
      {
        "name": "default",
        "signingConfig": "default",
        "compileSdkVersion": 22,
        "compatibleSdkVersion": 22,
        "runtimeOS": "HarmonyOS"
      }
    ]
  },
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [
        {
          "name": "default",
          "applyToProducts": ["default"]
        }
      ]
    }
  ]
}

6.3.2 oh-package.json5(项目根目录)

{
  "name": "openclaw-client",
  "version": "1.0.0",
  "description": "OpenClaw HarmonyOS Client",
  "dependencies": {
    "@antgroup/fluidmarkdown": "^1.0.0"   // 请以仓库最新版本号为准
  }
}

执行命令:在项目根目录执行 ohpm install 下载依赖。

6.3.3 entry/module.json5

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": ["phone"],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:layered_image",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["action.system.home"]
          }
        ]
      }
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.GET_NETWORK_INFO"
      }
      // 若使用 mDNS取消下一行注释
      // {
      //   "name": "ohos.permission.DISTRIBUTED_DEVICE_DISCOVERY"
      // }
    ]
  }
}

6.4 源代码文件详细内容

6.4.1 entry/src/main/ets/entryability/EntryAbility.ets

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    AppStorage.setOrCreate('serverBaseUrl', '');
    AppStorage.setOrCreate('accessToken', 'mobile-access-token-2026');
    AppStorage.setOrCreate('isServerConnected', false);
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        console.error(`Failed to load main page: ${JSON.stringify(err)}`);
      }
    });
  }
}

6.4.2 entry/src/main/ets/model/ChatMessage.ets

export interface ChatMessage {
  id: string;
  role: 'user' | 'assistant' | 'system';
  content: string;
  timestamp: Date;
}

6.4.3 entry/src/main/ets/service/OpenClawApi.ets

import { http } from '@ohos.net.http';

export class OpenClawApi {
  async chat(baseUrl: string, token: string, agentId: string = 'main', message: string): Promise<string> {
    const httpRequest = http.createHttp();
    const url = `${baseUrl}/v1/responses`;
    const payload = {
      model: `openclaw:${agentId}`,
      messages: [{ role: 'user', content: message }]
    };

    try {
      const response = await httpRequest.request(url, {
        method: http.RequestMethod.POST,
        header: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${token}`
        },
        extraData: JSON.stringify(payload),
        expectDataType: http.HttpDataType.STRING,
        connectTimeout: 10000,
        readTimeout: 30000
      });

      if (response.responseCode === 200) {
        const data = JSON.parse(response.result as string);
        return data.choices?.[0]?.message?.content || '[无响应内容]';
      } else {
        throw new Error(`HTTP Error ${response.responseCode}: ${response.result}`);
      }
    } catch (error) {
      console.error(`OpenClawApi.chat failed: ${error.message}`);
      throw new Error(`请求失败: ${error.message}`);
    } finally {
      httpRequest.destroy();
    }
  }

  async healthCheck(baseUrl: string, token: string): Promise<boolean> {
    const httpRequest = http.createHttp();
    try {
      const response = await httpRequest.request(`${baseUrl}/health`, {
        method: http.RequestMethod.GET,
        header: { 'Authorization': `Bearer ${token}` },
        connectTimeout: 5000
      });
      return response.responseCode === 200;
    } catch {
      return false;
    } finally {
      httpRequest.destroy();
    }
  }
}

6.4.4 entry/src/main/ets/components/ChatBubble.ets

import { ChatMessage } from '../model/ChatMessage';
import { Markdown, EMarkdownMode, MarkdownController } from '@antgroup/fluidmarkdown';

@Component
export struct ChatBubble {
  @Prop message: ChatMessage;
  private markdownController: MarkdownController = new MarkdownController();

  build() {
    Row() {
      if (this.message.role === 'user') {
        Blank()
        Column() {
          Text(this.message.content)
            .fontSize(16)
            .fontColor(Color.White)
            .padding(12)
            .backgroundColor('#007AFF')
            .borderRadius(18)
            .maxLines(100)
            .wordBreak(WordBreak.BREAK_ALL)
        }
        .padding({ right: 8 })
        .alignItems(HorizontalAlign.End)
      } else {
        Column() {
          Markdown({
            content: this.message.content,
            controller: this.markdownController,
            mode: EMarkdownMode.Normal,
            onMarkdownNodeClick: (data) => {
              if (data.type === 'link' && data.href) {
                console.info('Link clicked: ' + data.href);
                // 可调用系统浏览器或 WebView 打开链接
              }
            }
          })
          .backgroundColor('#E9E9EB')
          .borderRadius(18)
          .padding(8)
        }
        .padding({ left: 8 })
        .alignItems(HorizontalAlign.Start)
        Blank()
      }
    }
    .width('100%')
    .padding({ top: 4, bottom: 4 })
  }
}

6.4.5 entry/src/main/ets/pages/Index.ets

import { OpenClawApi } from '../service/OpenClawApi';
import { ChatMessage } from '../model/ChatMessage';
import { ChatBubble } from '../components/ChatBubble';
import { router } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  @State messageList: ChatMessage[] = [];
  @State inputText: string = '';
  @State isLoading: boolean = false;
  @StorageLink('serverBaseUrl') serverBaseUrl: string = '';
  @StorageLink('accessToken') accessToken: string = '';
  @StorageLink('isServerConnected') isServerConnected: boolean = false;

  private listScroller: ListScroller = new ListScroller();
  private api: OpenClawApi = new OpenClawApi();

  aboutToAppear() {
    this.loadHistory();
    if (this.serverBaseUrl) {
      this.checkServerConnection();
    }
  }

  loadHistory() {
    const history = AppStorage.get<ChatMessage[]>('chat_history');
    if (history) {
      this.messageList = history;
    }
  }

  saveHistory() {
    const historyToSave = this.messageList.slice(-50);
    AppStorage.setOrCreate('chat_history', historyToSave);
  }

  async checkServerConnection() {
    if (!this.serverBaseUrl) return;
    const connected = await this.api.healthCheck(this.serverBaseUrl, this.accessToken);
    this.isServerConnected = connected;
  }

  async sendMessage() {
    if (!this.inputText.trim() || !this.serverBaseUrl) {
      // 提示用户配置服务器
      return;
    }

    const userMsg: ChatMessage = {
      id: Date.now().toString(),
      role: 'user',
      content: this.inputText,
      timestamp: new Date()
    };
    this.messageList.push(userMsg);
    this.inputText = '';
    this.scrollToBottom();

    this.isLoading = true;
    try {
      const aiResponse = await this.api.chat(this.serverBaseUrl, this.accessToken, 'main', userMsg.content);
      const aiMsg: ChatMessage = {
        id: (Date.now() + 1).toString(),
        role: 'assistant',
        content: aiResponse,
        timestamp: new Date()
      };
      this.messageList.push(aiMsg);
      this.saveHistory();
    } catch (error) {
      const errorMsg: ChatMessage = {
        id: (Date.now() + 1).toString(),
        role: 'assistant',
        content: `**连接错误**\n\`\`\`\n${error.message}\n\`\`\``,
        timestamp: new Date()
      };
      this.messageList.push(errorMsg);
    } finally {
      this.isLoading = false;
      this.scrollToBottom();
    }
  }

  scrollToBottom() {
    setTimeout(() => {
      this.listScroller.scrollEdge(Edge.Bottom);
    }, 150);
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('OpenClaw')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        Blank()
        Circle()
          .width(12)
          .height(12)
          .fill(this.isServerConnected ? Color.Green : Color.Red)
          .margin({ right: 8 })
        Button('设置')
          .fontSize(14)
          .onClick(() => {
            router.pushUrl({ url: 'pages/Settings' });
          })
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#F5F5F5')

      // 消息列表
      List({ scroller: this.listScroller }) {
        ForEach(this.messageList, (msg: ChatMessage) => {
          ListItem() {
            ChatBubble({ message: msg })
          }
        }, (msg: ChatMessage) => msg.id)
      }
      .width('100%')
      .layoutWeight(1)
      .padding(8)

      // 输入区域
      Row() {
        TextInput({ placeholder: '输入消息...', text: $$this.inputText })
          .width('80%')
          .height(48)
          .fontSize(16)
          .enabled(this.isServerConnected)
        Button(this.isLoading ? '发送中' : '发送')
          .width('18%')
          .height(48)
          .enabled(!this.isLoading && this.inputText.length > 0 && this.isServerConnected)
          .onClick(() => this.sendMessage())
      }
      .width('100%')
      .padding(16)
      .backgroundColor(Color.White)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F0F0F0')
  }
}

6.4.6 entry/src/main/ets/pages/Settings.ets

import { router } from '@kit.ArkUI';
import { OpenClawApi } from '../service/OpenClawApi';

@Entry
@Component
struct Settings {
  @StorageLink('serverBaseUrl') serverBaseUrl: string = '';
  @StorageLink('accessToken') accessToken: string = '';
  @State inputUrl: string = '';
  @State inputToken: string = '';
  @State discoveredServers: Array<{ name: string, url: string }> = [];
  @State isScanning: boolean = false;
  @State statusMessage: string = '';

  private api: OpenClawApi = new OpenClawApi();

  aboutToAppear() {
    this.inputUrl = this.serverBaseUrl;
    this.inputToken = this.accessToken;
    this.startDiscovery();
  }

  startDiscovery() {
    // TODO: 使用 mDNS 实现自动发现
    // 此处为占位示例,模拟发现过程
    this.isScanning = true;
    setTimeout(() => {
      this.discoveredServers = [
        { name: '树莓派5 (192.168.43.101)', url: 'http://192.168.43.101:18789' }
      ];
      this.isScanning = false;
    }, 2000);
  }

  selectServer(url: string) {
    this.inputUrl = url;
  }

  async testAndSave() {
    if (!this.inputUrl) {
      this.statusMessage = '请输入服务器地址';
      return;
    }
    this.isScanning = true; // 复用为测试中状态
    this.statusMessage = '正在测试连接...';
    const isValid = await this.api.healthCheck(this.inputUrl, this.inputToken);
    if (isValid) {
      this.serverBaseUrl = this.inputUrl;
      this.accessToken = this.inputToken;
      this.statusMessage = '连接成功!';
      setTimeout(() => router.back(), 1000);
    } else {
      this.statusMessage = '连接失败,请检查地址和令牌';
    }
    this.isScanning = false;
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Button('返回')
          .onClick(() => router.back())
        Blank()
        Text('服务器设置')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        Blank()
      }
      .width('100%')
      .padding(16)

      Column() {
        // 手动输入区域
        TextInput({ placeholder: '服务器地址', text: $$this.inputUrl })
          .height(48)
          .margin({ top: 20 })
          .placeholderColor('#999999')
        Text('示例: http://192.168.43.101:18789')
          .fontSize(12)
          .fontColor('#999999')
          .width('100%')
          .textAlign(TextAlign.Start)

        TextInput({ placeholder: '访问令牌', text: $$this.inputToken })
          .height(48)
          .margin({ top: 10 })

        Button(this.isScanning ? '测试中...' : '测试并保存')
          .width('100%')
          .height(48)
          .margin({ top: 20 })
          .enabled(!this.isScanning)
          .onClick(() => this.testAndSave())

        if (this.statusMessage) {
          Text(this.statusMessage)
            .fontColor(this.statusMessage.includes('成功') ? Color.Green : Color.Red)
            .margin({ top: 10 })
        }

        // 自动发现列表
        Text('自动发现设备')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .margin({ top: 30 })
        if (this.isScanning) {
          Text('正在扫描...')
        } else {
          List() {
            ForEach(this.discoveredServers, (item) => {
              ListItem() {
                Row() {
                  Text(item.name)
                  Blank()
                  Button('连接')
                    .onClick(() => this.selectServer(item.url))
                }
                .width('100%')
                .padding(10)
              }
            }, item => item.url)
          }
          .height(200)
        }
      }
      .width('90%')
      .padding(16)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F0F0F0')
  }
}

6.5 资源文件修改

entry/src/main/resources/base/element/string.json

{
  "string": [
    {
      "name": "module_desc",
      "value": "OpenClaw 鸿蒙客户端"
    },
    {
      "name": "EntryAbility_desc",
      "value": "AI 对话界面"
    },
    {
      "name": "EntryAbility_label",
      "value": "OpenClaw"
    }
  ]
}

7. 自动服务发现详细设计可选P2

7.1 方案选择

  • mDNS推荐:使用 @ohos.net.mdns 模块。需在 module.json5 中添加权限,并查询 API 22 文档确认接口。
  • UDP 广播扫描:若不支持 mDNS可向局域网广播地址发送探测包监听特定端口响应。但可靠性较低且需处理多线程。

7.2 mDNS 接口设计示例

// DiscoveryService.ets
import { mdns } from '@ohos.net.mdns';

export class DiscoveryService {
  private discovery: mdns.DiscoveryService | null = null;

  startDiscovery(callback: (services: Array<{ name: string, host: string, port: number }>) => void) {
    mdns.createDiscoveryService('_openclaw._tcp', (err, discovery) => {
      if (err) return;
      this.discovery = discovery;
      discovery.on('discoveryResult', (result) => {
        callback([{ name: result.serviceName, host: result.host, port: result.port }]);
      });
      discovery.startDiscovering();
    });
  }

  stopDiscovery() {
    this.discovery?.stopDiscovering();
  }
}

注意:以上代码仅为示例,实际需根据 API 22 的 @ohos.net.mdns 模块文档调整。


8. 构建与验证清单

8.1 构建步骤

  1. 在 DevEco Studio 中创建工程,选择 SDK 6.0.2(22)。
  2. 按照 6.2 节清单替换/新增所有文件。
  3. 在项目根目录执行 ohpm install 下载 FluidMarkdown 依赖。
  4. 连接真机API 22 设备)或模拟器,点击运行。

8.2 功能验证

  • 编译通过:无报错,成功生成 HAP 并安装。
  • 手动配置连接:在设置页输入正确的树莓派 IP:Port 和令牌,点击测试并保存,返回主界面后连接状态指示变绿。
  • 消息发送与渲染发送消息后AI 响应内容以 Markdown 格式正确显示(如 # 标题- 列表、`代码` 等)。
  • 流式输出测试:可通过模拟分片追加内容验证 FluidMarkdown 的增量渲染(例如使用定时器逐字增加消息内容)。
  • 历史记录:重启应用后,之前对话应保留。
  • 自动发现(若实现):在树莓派启动 OpenClaw 并广播服务后,设置页应显示发现的设备,点击可自动填充地址。

9. 注意事项

  • FluidMarkdown 版本:请始终参考 GitHub 仓库 的最新文档API 可能迭代。
  • mDNS 可用性:在编写自动发现代码前,务必在 API 22 真机上测试 @ohos.net.mdns 模块是否存在及可用。若不可用,及时回退至手动配置。
  • 资源回收:虽然本项目无 WebView但若使用定时器或网络请求需确保在组件销毁时取消避免内存泄漏。
  • 错误处理:网络请求务必包含 try-catch,并在 UI 层给出用户友好的提示。

10. 文档结束

本说明书提供了 OpenClaw 鸿蒙客户端在 API 22 平台上的完整设计规格与实施细节。请开发人员严格按照上述文件替换与代码编写要求进行开发,确保应用的稳定性与性能。如有任何因平台更新导致的 API 变更,请以最新的官方文档为准,并相应调整本方案中的实现。

Description
No description provided
Readme MIT 35 KiB