AI表盘与云服务
AI表盘与云服务是 HealthAide 中基于语音交互与云端大模型(科大讯飞星火 SparkChain)实现"一句话生成表盘"及"健康云服务问答"的完整能力链路,涵盖语音识别(IAT)、语义理解、文生图、表盘图像处理、RCSP 协议传输安装与云消息本地持久化。
Purpose and Scope
本页面向该能力的端到端实现,从用户语音输入到表盘在手表上安装完成,以及 AI 云服务消息的收发与持久化:
- AI 能力入口与供应商抽象(
AIManager、AISupplier) - 语音识别链路(IAT)与 AI 对话/文生图链路(讯飞星火 SparkChain)
- AI 表盘处理与传输(
AIDialWrapper:图像裁剪缩放、BmpConvert 格式转换、FAT FS 传输、表盘安装) - AI 云服务(
AICloudServeWrapper)与云消息本地存储(AICloudMessageEntity/AICloudMessageDao) - 配置(
aiui_phone.cfg、SparkChainAAR)与失败处理
以下内容不属于本页范围,将在对应目录页中介绍:通用蓝牙协议栈(RCSP)、自定义表盘管理(CustomDialManager)、WatchManager 设备管理。本页仅说明 AI 能力如何调用它们。
Overview
AI 表盘功能的使用场景是:用户对 App 说出"帮我生成一个星空风格的表盘",系统依次完成:
- 语音识别(IAT)将音频转为文本(
BaseAIIatHandler/IflytekAIIatHandler); - AI 对话/文生图把文本(加上当前绘画风格前缀)发送给讯飞星火大模型,生成一张表盘背景图;
- 图像处理与传输:把生成图裁剪缩放到手表屏幕尺寸与 240×240 缩略图,经 BmpConvert 转为芯片所需位图格式,通过 FAT FS 写入手表,并触发表盘安装;
- 安装与状态同步:设备端 RCSP 命令回传安装状态,App 侧
AIDialWrapper通过AIDialListener驱动整个流程。
AI 云服务则是同一语音/对话框架下的另一分支(BaseAICloudServeHandler / IflytekAICloudServeHandler),用于健康问答类云服务消息,消息通过 AICloudMessageEntity 保存在本地 Room 数据库(AICloudMessageDao)。
整个能力构建在一个供应商抽象之上:AIDialWrapper 中 AISupplier = 1(0 表示杰理、1 表示科大讯飞),当前实现以科大讯飞为主。
Architecture
flowchart TD
subgraph sg_Entry["入口层"]
AIManager["AIManager<br/>AI能力总入口"]
end
subgraph sg_Handler["Handler 层(职责链抽象)"]
BaseAIHandler["BaseAIHandler"]
BaseAIChatHandler["BaseAIChatHandler<br/>AI对话基类"]
BaseAIIat["BaseAIIatHandler<br/>语音识别基类"]
BaseAIDial["BaseAIDialHandler<br/>AI表盘基类"]
BaseACloud["BaseAICloudServeHandler<br/>云服务基类"]
IflyAIDial["IflytekAIDialHandler<br/>讯飞表盘实现"]
IflyACloud["IflytekAICloudServeHandler<br/>讯飞云服务实现"]
end
subgraph sg_SDK["云端 SDK 封装"]
T2I["IflytekTextToImageWrapper<br/>星火文生图"]
AIUI["AIUI / IAT 识别<br/>aiui_phone.cfg"]
Spark["SparkChain_V2.0.1_rc1.aar"]
end
subgraph sg_RCSP["RCSP 封装层"]
AIDialWrapper["AIDialWrapper<br/>AI表盘协议"]
AICloudWrapper["AICloudServeWrapper<br/>云服务协议"]
AIRecordWrapper["AIRecordWrapper<br/>录音封装"]
AIDialListener["AIDialListener<br/>流程回调"]
end
subgraph sg_Device["设备与存储"]
WatchManager["WatchManager"]
BmpConvert["BmpConvert<br/>位图格式转换"]
FATFS["FAT FS 文件系统<br/>缩略图/背景图"]
Room["Room DB<br/>AICloudMessageEntity"]
end
AIManager --> BaseAIHandler
BaseAIHandler --> BaseAIChatHandler
BaseAIChatHandler --> BaseAIIat
BaseAIChatHandler --> BaseAIDial
BaseAIChatHandler --> BaseACloud
BaseAIDial --> IflyAIDial
BaseACloud --> IflyACloud
IflyAIDial --> T2I
T2I --> Spark
BaseAIIat --> AIUI
IflyAIDial --> AIDialWrapper
AIDialWrapper --> AIDialListener
AIDialWrapper --> WatchManager
AIDialWrapper --> BmpConvert
AIDialWrapper --> FATFS
IflyACloud --> AICloudWrapper
AICloudWrapper --> Room
架构说明:
- Handler 层是核心抽象:
BaseAIHandler→BaseAIChatHandler→ 三个分支(IAT、AI 表盘、AI 云服务)。BaseAIDialHandler继承BaseAIChatHandler,因此表盘生成复用了 AI 对话的文本链路(startAIChat/retryAIChat),只在其上叠加了表盘特有的图像处理与传输逻辑。 - RCSP 封装层屏蔽了与手表的协议细节。
AIDialWrapper是表盘能力的枢纽:既接收设备端 RCSP 命令(CMD_AI_OPERATE),也通过AIDialListener向 Handler 层回调"生成表盘 / 重新生成 / 安装完成"等事件。 - 设备与存储层:
WatchManager负责连接与 FAT FS 文件传输;BmpConvert(杰理位图转换库)把通用位图转为 701N/707N/695N 等芯片 RGB/ARGB 格式;AI 云消息通过 Room 持久化。
语音链路:IAT 识别 → AI 对话
AI 表盘的入口是语音。BaseAIDialHandler 继承自 BaseAIChatHandler,因此它天然具备语音识别与 AI 对话能力,关键回调如下:
@Override
void onIatText(String iatText) {
mIatText = iatText;
transferIatText(mIatText);
}
@Override
void onIatStartRecord() {
mIatText = null;
}
Source: BaseAIDialHandler.java
onIatText:语音识别出文本后,先保存到mIatText,再调用transferIatText()把文本交给 AI 对话/云端语义理解;识别文本会被稍后用于表盘生成(onGenerateDial时读取mIatText)。onIatStartRecord:开始录音时清空上一次的识别文本,避免把旧文本混入新一轮表盘生成。- 识别为空时走
onIatRecognizeEmptyError(),向 UI 抛出R.string.ai_no_speak("没有听到说话");网络异常时onIatNetworkError()/onTTINetworkError()抛出R.string.ai_network_wrong。
@Override
void onIatNetworkError() {
String error = mContext.getString(R.string.ai_network_wrong);
mAIDialWrapper.asyncMessageAIError(error, null);
}
Source: BaseAIDialHandler.java
设计意图:表盘生成与错误提示都通过 AIDialWrapper.asyncMessageAIError() 回传 UI,Handler 层不直接持有界面引用,保持视图与业务解耦。
AI 表盘生成(BaseAIDialHandler / IflytekAIDialHandler)
流程驱动:AIDialListener
BaseAIDialHandler 构造时向 AIDialWrapper 注册一个 AIDialListener,把设备侧/协议侧事件翻译成 AI 对话动作:
private final AIDialListener mAIDialListener = new AIDialListener() {
@Override
public void onGenerateDial() {
super.onGenerateDial();
//开始生成AI表盘
startAIChat(mIatText);
}
@Override
public void onInstallDialStart() {
super.onInstallDialStart();
//开始安装表盘:1.生成自定义表盘文件,2.传输表盘
}
@Override
public void onReGenerateDial() {
super.onReGenerateDial();
//重新生成表盘
retryAIChat();
}
};
public BaseAIDialHandler(AIDialWrapper aiDialWrapper, WatchManager rcspOp, Context context) {
super(rcspOp, context);
mAIDialWrapper = aiDialWrapper;
mAIDialWrapper.registerListener(mAIDialListener);
testSrc = (int) (Math.random() * 9);
}
Source: BaseAIDialHandler.java
onGenerateDial→startAIChat(mIatText):用户确认生成后,把识别文本送入 AI 对话/文生图;onReGenerateDial→retryAIChat():用户点击"重新生成",复用同一文本再跑一次;onRecordingAgain无需处理(录音重来由 IAT Handler 负责);testSrc随机数(0–8)疑似用于内部测试图片来源,生产逻辑中未看到其实际分支调用。
release() 时对称地 unregisterListener,避免内存泄漏。
文生图实现(讯飞星火)
IflytekAIDialHandler 是讯飞供应商的具体实现,核心在 onStartAIChat:
@Override
void onStartAIChat(String userText) {
String chatText = getCurrentAIDialStyle() + userText;
mIflytekTextToImageWrapper.execute(chatText, stateResult -> {
if (stateResult.getState() == BasicWrapper.STATUS_FINISH) {
if (stateResult.isSuccess()) { //生产图片成功
final LLMResult result = stateResult.getResult();
final byte[] imageData = result.getImage();
String savePath = FileUtil.createFilePath(mContext, "aidial")
+ File.separator + "aiSrc_cache";
File file = new File(savePath);
if (file.exists()) {
file.delete();
}
if (FileUtil.bytesToFile(imageData, savePath)) {
handleAIChatImage(savePath);
} else {
JL_Log.w(TAG, "onStartAIChat", "Failed to save bitmap. " +
"data size : " + imageData.length + ", \nsave Path : " + savePath);
onTTINetworkError();
}
return;
}
onTTINetworkError();
}
});
}
Source: IflytekAIDialHandler.java
要点:
- 提示词拼接:
chatText = getCurrentAIDialStyle() + userText,把当前绘画风格(如"卡通/水彩/油画")作为前缀拼进用户文本,让大模型按风格生成; - 同步停止:
IflytekAIDialHandler注册了OnWatchCallback,当目标设备断连或连接失败(CONNECTION_DISCONNECT/CONNECTION_FAILED)时调用mIflytekTextToImageWrapper.stop(),避免设备不在线时继续消耗云端算力; - 结果落盘:
LLMResult.getImage()拿到图片字节流,写入aidial/aiSrc_cache文件,成功后再交给handleAIChatImage()进入表盘图像处理管线;写盘失败或生成失败统一走onTTINetworkError()。
getCurrentAIDialStyle() / setCurrentAIDialStyle(String) 透传 AIDialWrapper 的 getPainStyle() / setPaintStyle(),绘画风格是全局状态,由 MSG_SET_PAIN_STYLE 消息在设备初始化后同步给手表(见下节)。
表盘图像处理与传输(AIDialWrapper)
AIDialWrapper(com.jieli.healthaide.tool.aiui.rcsp)是表盘能力的协议枢纽,头部常量定义了整套传输约定:
private final int AISupplier = 1;//ai供应商,0:杰理,1:科大讯飞
private final String THUMB_PATH = "/AITHUMB";
private final static String WATCH_PREFIX = "WATCH";
private final static String CUSTOM_BG_PREFIX = "bgp_w";
private final static String JPG_FORMAT = ".jpg";
private final LinkedBlockingQueue<SendTaskParam> mSendTaskQueue = new LinkedBlockingQueue<>();
private final int MSG_SET_PAIN_STYLE = 101;
private int thumbWidth = 240;
private int thumbHeight = 240;
Source: AIDialWrapper.java
图片裁剪与缩略图生成:handleNlpImage
AI 对话返回图片后,handleNlpImage() 负责生成"待安装背景图"和"240×240 缩略图":
public void handleNlpImage(String srcPath, String tempThumbPath, String tempDialPath) {
if (!mWatchManager.isConnected()) return;
File tempThumbFile = new File(tempThumbPath);
if (tempThumbFile.exists()) tempThumbFile.delete();
File tempDialFile = new File(tempDialPath);
if (tempDialFile.exists()) tempDialFile.delete();
dialImagePath = tempDialPath;
AppUtil.copyFile(srcPath, tempDialPath);
AppUtil.copyFile(srcPath, tempThumbPath);
Bitmap thumb = HealthUtil.createScaleBitmap(tempThumbPath, thumbWidth, thumbHeight);
thumb = getCropBitmap(thumb);
BitmapUtil.bitmapToFile(thumb, tempThumbPath, 100);
thumb.recycle();
transferThumb(tempThumbPath);
}
Source: AIDialWrapper.java
- 未连接手表时直接返回(
if (!mWatchManager.isConnected()) return;),避免无意义的文件操作; - 生成图先复制两份:
tempDialPath(背景图原样保留,后续安装用)与tempThumbPath(缩放 240×240 再裁剪); transferThumb()先传缩略图给手表,让用户在生成背景图期间就能看到预览效果;- 文件被注释掉的旧版实现展示了更精细的"按手表宽高等比缩放居中裁剪"算法(
scale = Math.min(widthScale, heightScale)、offsetX/offsetY补偿),可作为后续优化参考。
位图格式转换与传输:transferThumb
public void transferThumb(String thumbFilePath) {
DeviceInfo deviceInfo = mWatchManager.getDeviceInfo(mWatchManager.getConnectedDevice());
if (deviceInfo == null) return;
onTransferThumbStart();
int type;
WatchConfigure watchConfigure = mWatchManager.getWatchConfigure(mWatchManager.getConnectedDevice());
switch (deviceInfo.getSdkType()) {
case JLChipFlag.JL_CHIP_FLAG_701X_WATCH:
type = BmpConvert.TYPE_701N_RGB;
if (watchConfigure != null && watchConfigure.getFunctionOption().isSupportDialExpandInfo()) {
type = BmpConvert.TYPE_701N_ARGB;
}
break;
case JLChipFlag.JL_CHIP_FLAG_707N_WATCH:
type = BmpConvert.TYPE_707N_RGB;
if (watchConfigure != null && watchConfigure.getFunctionOption().isSupportDialExpandInfo()) {
type = BmpConvert.TYPE_707N_ARGB;
}
break;
default:
type = BmpConvert.TYPE_695N_RBG;
break;
}
String outPath = getOutPath(thumbFilePath);
mBmpConvert.bitmapConvert(type, thumbFilePath, outPath, new OnConvertListener() {
@Override
public void onStop(boolean result, String s) {
if (result) {
mWatchManager.addFatFile(s, true, new OnFatFileProgressListener() { ... });
}
}
});
}
Source: AIDialWrapper.java
设计要点:
- 按芯片型号选格式:701X/707N 系列支持 RGB 与 ARGB 两种位图格式,是否启用 ARGB 取决于设备配置
WatchConfigure.functionOption.isSupportDialExpandInfo()(是否支持表盘扩展信息);默认 695N 走TYPE_695N_RBG; - FAT FS 写入:
bitmapConvert转换成功后通过mWatchManager.addFatFile()把文件写入手表文件系统,并带进度监听OnFatFileProgressListener; - 串行发送队列:
LinkedBlockingQueue<SendTaskParam> mSendTaskQueue配合volatile boolean isSendData保证多个发送任务(缩略图/背景图/风格同步)按序执行,避免并发写文件系统导致冲突; - 风格同步:
mUIHandler收到MSG_SET_PAIN_STYLE(设备onWatchSystemInit后延迟 1 秒触发)时,先校验isSupportAIDial(),再调用notifyDevPaintStyle(mPainStyle)把当前绘画风格下发给手表; - 设备事件:
onRcspCommand监听CMD_AI_OPERATE且AI_OP_AI_DIAL的命令,其中AI_DIAL_OP_UI标志携带scaleZoomWidth/Height,用于设备端表盘 UI 的缩放状态同步; - 文件命名约定:缩略图固定为
AITHUMB.jpg(THUMB_PATH.replaceAll(File.separator, "") + JPG_FORMAT),背景图用bgp_w + 序号 + .jpg(CUSTOM_BG_PREFIX),配合WATCH_PREFIX体系在手表端识别文件归属。
AI 云服务(AICloudServeWrapper / BaseAICloudServeHandler)
AI 云服务是与表盘并列的另一条 AI 能力线,目录结构如下:
handle/BaseAICloudServeHandler.java、handle/IflytekAICloudServeHandler.java:云服务 Handler,继承BaseAIChatHandler,复用语音识别与 AI 对话管线,输出健康云服务消息;rcsp/AICloudServeWrapper.java、rcsp/AICloudServeWrapperListener.java:与手表侧云服务功能交互的 RCSP 封装及监听器;data/entity/AICloudMessageEntity.java、data/dao/AICloudMessageDao.java:云服务消息的 Room 实体与 DAO,实现本地持久化,支持历史消息查询与展示。
说明:受本次文档探索预算限制,上述云服务类与 DAO 的具体字段、方法签名未逐一展开阅读;更详细的实现请直接查阅对应源文件。表盘与云服务共用同一套"Handler 抽象 + RCSP Wrapper + 监听器"骨架,理解本页的
AIDialWrapper结构即可快速迁移到云服务分支。
配置与资源
| 资源 | 位置 | 作用 |
|---|---|---|
aiui_phone.cfg | app/src/main/assets/cfg/ | AIUI 语音识别引擎的 App 端配置文件 |
SparkChain_V2.0.1_rc1.aar | app/libs/ | 讯飞星火大模型 SDK(文生图/对话能力来源) |
AISupplier = 1 | AIDialWrapper 字段 | 供应商开关:0=杰理,1=科大讯飞 |
Core Flow:从一句话到表盘安装
sequenceDiagram
participant U as 用户
participant IAT as BaseAIIatHandler<br/>(讯飞IAT)
participant D as BaseAIDialHandler
participant T2I as IflytekTextToImageWrapper<br/>(星火文生图)
participant W as AIDialWrapper
participant BC as BmpConvert
participant WM as WatchManager / FAT FS
participant WD as 手表
U->>IAT: 说出"星空风格表盘"
IAT-->>D: onIatText(文本) / transferIatText
D-->>W: 用户确认生成
W-->>D: onGenerateDial()
D->>D: startAIChat(mIatText)
D->>T2I: execute(风格前缀 + 文本)
T2I-->>D: STATUS_FINISH / image bytes
D->>D: 保存 aiSrc_cache 图片
D->>W: handleAIChatImage / handleNlpImage
W->>W: 缩放240x240 + 裁剪缩略图
W->>BC: bitmapConvert(芯片RGB/ARGB)
BC-->>W: 转换完成
W->>WM: addFatFile(缩略图, 进度监听)
WM->>WD: FAT FS 写入 AITHUMB.jpg
WD-->>W: onTransferThumbFinish
W-->>D: onInstallDialStart
W->>WM: 生成并传输自定义表盘文件
WD-->>W: onInstallDialFinish(是否成功)
D-->>W: 失败→asyncMessageAIError<br/>成功→流程结束
步骤拆解(对应真实代码):
- 识别:
BaseAIIatHandler完成语音识别,BaseAIDialHandler.onIatText()保存文本并调用transferIatText()(BaseAIDialHandler.java#L109-L113); - 确认生成:用户触发生成后,
AIDialWrapper通过AIDialListener.onGenerateDial()回调,Handler 调用startAIChat(mIatText)(BaseAIDialHandler.java#L28-L32); - 文生图:
IflytekAIDialHandler.onStartAIChat()拼接绘画风格前缀后调用IflytekTextToImageWrapper.execute(),成功则落盘图片字节并调用handleAIChatImage()(IflytekAIDialHandler.java#L55-L81); - 图像处理:
AIDialWrapper.handleNlpImage()生成背景图副本与 240×240 缩略图,调用transferThumb()(AIDialWrapper.java#L254-L271); - 格式转换:
transferThumb()按芯片型号(701N/707N/695N)选择BmpConvert.TYPE_*,转换成功后addFatFile写入手表(AIDialWrapper.java#L326-L370); - 安装:缩略图传输完成后进入安装阶段(
onInstallDialStart),背景图按同样管线传输,最终onInstallDialFinish(isSuccess)回调 Handler;任一步失败均由asyncMessageAIError()提示用户。
Usage Examples
1. 注册表盘流程监听(Handler 侧)
private final AIDialListener mAIDialListener = new AIDialListener() {
@Override
public void onGenerateDial() {
super.onGenerateDial();
startAIChat(mIatText); // 用识别文本生成表盘
}
@Override
public void onReGenerateDial() {
super.onReGenerateDial();
retryAIChat(); // 重新生成
}
};
public BaseAIDialHandler(AIDialWrapper aiDialWrapper, WatchManager rcspOp, Context context) {
super(rcspOp, context);
mAIDialWrapper = aiDialWrapper;
mAIDialWrapper.registerListener(mAIDialListener);
}
@Override
public void release() {
super.release();
mAIDialWrapper.unregisterListener(mAIDialListener);
}
Source: BaseAIDialHandler.java
2. 讯飞文生图并落盘(供应商实现)
@Override
void onStartAIChat(String userText) {
String chatText = getCurrentAIDialStyle() + userText; // 风格 + 用户描述
mIflytekTextToImageWrapper.execute(chatText, stateResult -> {
if (stateResult.getState() == BasicWrapper.STATUS_FINISH) {
if (stateResult.isSuccess()) {
final byte[] imageData = stateResult.getResult().getImage();
String savePath = FileUtil.createFilePath(mContext, "aidial")
+ File.separator + "aiSrc_cache";
if (FileUtil.bytesToFile(imageData, savePath)) {
handleAIChatImage(savePath); // 进入表盘图像管线
} else {
onTTINetworkError();
}
return;
}
onTTINetworkError();
}
});
}
Source: IflytekAIDialHandler.java
3. 缩略图裁剪与传输(Wrapper 侧)
public void handleNlpImage(String srcPath, String tempThumbPath, String tempDialPath) {
if (!mWatchManager.isConnected()) return;
dialImagePath = tempDialPath;
AppUtil.copyFile(srcPath, tempDialPath);
AppUtil.copyFile(srcPath, tempThumbPath);
Bitmap thumb = HealthUtil.createScaleBitmap(tempThumbPath, thumbWidth, thumbHeight);
thumb = getCropBitmap(thumb);
BitmapUtil.bitmapToFile(thumb, tempThumbPath, 100);
thumb.recycle();
transferThumb(tempThumbPath);
}
Source: AIDialWrapper.java
4. 按芯片型号选择位图格式
switch (deviceInfo.getSdkType()) {
case JLChipFlag.JL_CHIP_FLAG_701X_WATCH:
type = BmpConvert.TYPE_701N_RGB;
if (watchConfigure != null && watchConfigure.getFunctionOption().isSupportDialExpandInfo()) {
type = BmpConvert.TYPE_701N_ARGB; // 支持表盘扩展信息时启用 ARGB
}
break;
case JLChipFlag.JL_CHIP_FLAG_707N_WATCH:
type = BmpConvert.TYPE_707N_RGB;
if (watchConfigure != null && watchConfigure.getFunctionOption().isSupportDialExpandInfo()) {
type = BmpConvert.TYPE_707N_ARGB;
}
break;
default:
type = BmpConvert.TYPE_695N_RBG;
break;
}
Source: AIDialWrapper.java
Configuration Options
AI 表盘能力的可配置项主要散布在 AIDialWrapper 常量与基类字段中:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
AISupplier | int | 1 | AI 供应商开关:0=杰理,1=科大讯飞 |
thumbWidth / thumbHeight | int | 240 / 240 | 缩略图目标尺寸(缩放后裁剪) |
THUMB_PATH | String | "/AITHUMB" | 手表端缩略图存放路径 |
WATCH_PREFIX | String | "WATCH" | 手表文件前缀标识 |
CUSTOM_BG_PREFIX | String | "bgp_w" | 自定义背景图文件名前缀 |
JPG_FORMAT | String | ".jpg" | 图像文件格式后缀 |
MSG_SET_PAIN_STYLE | int | 101 | 主线程 Handler 消息码:同步绘画风格 |
| 风格同步延迟 | long | 1000ms | onWatchSystemInit 后延迟 1 秒下发风格 |
| 资源字符串 | string | ai_network_wrong / ai_no_speak | 网络错误 / 未识别到语音的提示文案 |
API Reference
BaseAIDialHandler(com.jieli.healthaide.tool.aiui.handle)
抽象类,继承 BaseAIChatHandler,封装 AI 表盘的公共流程。
构造: BaseAIDialHandler(AIDialWrapper aiDialWrapper, WatchManager rcspOp, Context context)
方法:
getCurrentAIDialStyle(): String— 获取当前绘画风格(透传AIDialWrapper.getPainStyle())setCurrentAIDialStyle(String paintStyle)— 设置绘画风格(透传setPaintStyle(),作为文生图提示词前缀)onTTINetworkError()/onIatNetworkError()— 网络异常时通过asyncMessageAIError(R.string.ai_network_wrong)提示 UIonIatRecognizeEmptyError()— 未识别到语音时提示R.string.ai_no_speakonIatText(String iatText)— 保存识别文本并交给 AI 对话onIatStartRecord()— 开始录音时清空识别文本release()— 注销监听、释放资源
Throws(业务错误路径): 不抛 Java 异常;所有失败统一回调 AIDialWrapper.asyncMessageAIError(String error, Object extra)。
IflytekAIDialHandler(com.jieli.healthaide.tool.aiui.handle)
BaseAIDialHandler 的科大讯飞实现。
构造: IflytekAIDialHandler(AIDialWrapper aiDialWrapper, WatchManager rcspOp, Context context) — 创建 IflytekTextToImageWrapper,注册设备连接状态回调。
方法:
onStartAIChat(String userText)— 拼接风格前缀后调用星火文生图;STATUS_FINISH且成功后保存图片字节并调用handleAIChatImage(savePath),失败走onTTINetworkError()release()— 注销设备回调并释放上层资源
行为: 设备断连/连接失败时自动 stop() 文生图任务。
AIDialWrapper(com.jieli.healthaide.tool.aiui.rcsp)
RCSP 协议封装,负责表盘全流程的设备交互。
关键方法:
registerListener(AIDialListener)/unregisterListener(AIDialListener)— 注册/注销流程回调handleNlpImage(String srcPath, String tempThumbPath, String tempDialPath)— 生成背景图副本与缩略图并触发传输;未连接时直接返回transferThumb(String thumbFilePath)— 按芯片型号做 BmpConvert 转换后addFatFile写入手表getThumbName(): String— 返回AITHUMB.jpg(缩略图文件名)getCustomBgName(int seq): String— 返回bgp_w + seq + .jpg(背景图文件名)getPainStyle()/setPaintStyle(String)— 绘画风格读写asyncMessageAIError(String error, Object extra)— 向 UI 异步投递错误消息
设备回调(内部):
onWatchSystemInit(int code)— 设备系统初始化后延迟 1s 同步绘画风格(需isSupportAIDial())onRcspCommand(device, command)— 处理CMD_AI_OPERATE中AI_OP_AI_DIAL的AI_DIAL_OP_UI事件,携带scaleZoomWidth/Height缩放状态
AIDialListener(com.jieli.healthaide.tool.aiui.rcsp)
表盘流程回调接口,全部方法带空实现(super 可调用),子类按需覆写:
onGenerateDial()— 开始生成 AI 表盘onReGenerateDial()— 重新生成表盘onRecordingAgain()— 重新录音(默认不处理)onTransferThumbStart()/onTransferThumbFinish(boolean isSuccess)— 缩略图传输起止onInstallDialStart()/onInstallDialFinish(boolean isSuccess)— 表盘安装起止
Failure Modes、边界情况与并发
错误处理路径
| 场景 | 处理方式 | 源码位置 |
|---|---|---|
| AI 对话/文生图网络异常 | onTTINetworkError() → asyncMessageAIError(R.string.ai_network_wrong) | BaseAIDialHandler.java#L92-L101 |
| IAT 识别为空 | onIatRecognizeEmptyError() → ai_no_speak 提示 | BaseAIDialHandler.java#L103-L107 |
| 生成图写盘失败 | 日志警告 + onTTINetworkError() | IflytekAIDialHandler.java#L69-L75 |
| 手表未连接 | handleNlpImage 直接 return,跳过图像处理 | AIDialWrapper.java#L255 |
| 设备信息为空 | transferThumb 直接 return | AIDialWrapper.java#L328-L331 |
| 设备断连/连接失败 | IflytekAIDialHandler 停止文生图任务 | IflytekAIDialHandler.java#L28-L41 |
边界情况
- 设备不支持 AI 表盘:
MSG_SET_PAIN_STYLE处理时先校验configure.getFunctionOption().isSupportAIDial(),不支持则跳过风格下发,避免向旧固件发送无效命令; - 表盘扩展信息差异:同一芯片(701N/707N)下是否使用 ARGB 取决于
isSupportDialExpandInfo(),必须按设备能力动态选择位图格式,否则可能花屏或无法显示; - 旧文本串扰:
onIatStartRecord()清空mIatText,保证"上一轮文本"不会污染新一轮生成; - 临时文件残留:
handleNlpImage对tempThumbPath/tempDialPath先删后写,避免旧图残留导致安装错误图片。
并发与一致性
- 串行发送队列:
mSendTaskQueue(LinkedBlockingQueue)配合volatile isSendData保证缩略图、背景图、风格同步等 FAT FS 写操作严格串行,防止并发写文件系统破坏手表端数据一致性; - 主线程约束:
mUIHandler绑定主线程 Looper,MSG_SET_PAIN_STYLE在 UI 线程执行,规避跨线程访问WatchManager状态的风险; - 生命周期对称:
BaseAIDialHandler构造注册AIDialListener,release()注销;IflytekAIDialHandler同样对称注册/注销OnWatchCallback,防止回调泄漏导致的内存问题。
Performance 与运维考量
- 缩略图先行:先传 240×240 缩略图(体积小、转换快),用户在背景图生成/传输期间即可看到预览,缩短感知等待时间;
- 位图转换成本:
BmpConvert按芯片格式转换是 CPU 密集型操作,转换结果写入临时文件后通过 FAT FS 传输,避免大 Bitmap 常驻内存(生成后及时recycle()); - 云端成本控制:断连即
stop()文生图,避免无效请求消耗讯飞星火配额;文生图为异步回调(STATUS_FINISH),不阻塞主线程; - 日志可观测:关键路径均使用
JL_Log.d/w输出(如transferThumb的文件路径与转换结果),线上问题可按 TAG 定位; - 网络依赖:IAT、文生图全部依赖云端,弱网环境下
ai_network_wrong提示会频繁出现,需要 App 层做好网络状态引导。
Extension Points
- 新增 AI 供应商:
AIDialWrapper.AISupplier已预留供应商枚举(0=杰理,1=科大讯飞)。仿照IflytekAIDialHandler实现一个新的BaseAIDialHandler子类,替换文生图执行器(如杰理自研模型),即可接入新供应商而不改动 Handler 骨架与AIDialWrapper协议层。 - 扩展表盘流程回调:
AIDialListener的方法均带空实现,可在不破坏现有子类的情况下新增回调(如进度百分比回调),AIDialWrapper在对应时机调用即可。 - 绘画风格体系:
getPainStyle()/setPaintStyle()是开放字符串状态,IflytekAIDialStyleHelper(tool.aiui.iflytek包)提供风格辅助;新增风格只需扩展风格枚举与提示词映射。 - 云服务复用:
BaseAICloudServeHandler与AIDialWrapper共用BaseAIChatHandler管线,新的 AI 业务(如健康建议、运动报告)可按同一模式扩展 Handler + Wrapper + Listener 三件套,并用AICloudMessageEntity持久化。
Tests
本次探索未在源码中发现针对 aiui 包(AI 表盘/云服务)的专门测试文件;该模块高度依赖蓝牙设备与云端 SDK,测试覆盖主要体现在设备真机联调与协议回调验证上。建议的测试策略:对 handleNlpImage 的缩放裁剪算法做纯本地单元测试,对 transferThumb 的 BmpConvert 类型选择做参数化测试(701N/707N/695N × RGB/ARGB),对 AIDialListener 回调编排做 Mock 测试。
Related Links
- AIManager.java(AI 能力总入口)
- BaseAIDialHandler.java
- IflytekAIDialHandler.java
- AIDialWrapper.java
- AIDialListener.java
- BaseAICloudServeHandler.java / IflytekAICloudServeHandler.java
- AICloudServeWrapper.java
- AICloudMessageEntity.java(云消息持久化实体)
- AICloudMessageDao.java
- aiui_phone.cfg(AIUI 识别配置)
- IflytekAIDialStyleHelper.java(绘画风格辅助)
相关目录页:AI 语音识别(IAT)细节、AI 对话(Chat)细节见 8-ai-capabilities 目录下对应子页;手表协议与文件系统传输的通用机制见 WatchManager / 自定义表盘相关页面。