HarmonyOS APP开发:华为云AI服务对接
HarmonyOS APP开发:华为云AI服务对接
核心要点:华为云AI服务(ModelArts、HiLens、OCR服务、语音服务、NLP服务等)为HarmonyOS应用提供了强大的云端AI推理能力。本文将深入讲解HarmonyOS应用如何对接华为云AI服务,涵盖认证鉴权、API调用、结果处理、错误重试等全链路实战。
一、背景与动机
先说个真实场景:我有个朋友做了一款HarmonyOS美食APP,想加个"拍照识别菜品"的功能。他一开始用端侧模型做,准确率只有60%——毕竟端侧模型受限于体积,能力有限。后来他接了华为云的图像识别API,准确率直接飙到92%。
这就是云端AI的价值——算力不受限、模型不受限、精度不受限。
但对接云端AI服务也不是没有挑战。你至少要面对这些问题:
- 认证鉴权怎么搞? 华为云的IAM、AK/SK、Token三种认证方式各有适用场景
- 网络请求怎么封装? HTTPS请求、签名计算、超时处理
- 大文件怎么上传? 图片、音频这些二进制数据不能直接JSON传
- 并发和限流怎么处理? 云端API有QPS限制,不加控制会被限流
- 端云怎么协同? 什么时候走端侧、什么时候走云侧?
这篇文章就是来解决这些问题的。
二、核心原理
2.1 华为云AI服务架构
华为云AI服务从底层到上层分为四层:
graph TB
subgraph CLIENT["HarmonyOS应用端"]
A1[ArkTS业务逻辑]
A2[AI服务SDK封装层]
A3[网络请求引擎]
end
subgraph GATEWAY["华为云网关层"]
B1[API Gateway]
B2[IAM认证鉴权]
B3[流量控制与限流]
end
subgraph SERVICE["AI服务层"]
C1[OCR文字识别]
C2[图像识别]
C3[语音服务]
C4[NLP自然语言]
C5[ModelArts自定义模型]
end
subgraph COMPUTE["算力层"]
D1[GPU推理集群]
D2[NPU推理集群]
D3[模型仓库]
end
A1 --> A2 --> A3
A3 --> B1
B1 --> B2 & B3
B1 --> C1 & C2 & C3 & C4 & C5
C1 & C2 & C3 & C4 & C5 --> D1 & D2 & D3
classDef primary fill:#4A90D9,stroke:#2C5F8A,color:#fff
classDef warning fill:#F5A623,stroke:#C77D05,color:#fff
classDef error fill:#D0021B,stroke:#8B0000,color:#fff
classDef info fill:#7B68EE,stroke:#5B48C2,color:#fff
classDef purple fill:#9B59B6,stroke:#6C3483,color:#fff
class A1,A2,A3 primary
class B1,B2,B3 warning
class C1,C2,C3,C4,C5 info
class D1,D2,D3 purple
2.2 认证鉴权机制
华为云AI服务支持三种认证方式:
| 认证方式 | 原理 | 适用场景 | 安全等级 |
|---|---|---|---|
| AK/SK签名 | 请求中携带签名,服务端验签 | 服务端调用 | ⭐⭐⭐⭐⭐ |
| Token认证 | 先获取Token,请求中携带Token | 临时调用 | ⭐⭐⭐⭐ |
| IAM代理 | 通过IAM代理授权 | 企业多账号 | ⭐⭐⭐⭐⭐ |
在HarmonyOS应用中,推荐使用Token认证。原因很简单:AK/SK不能硬编码在客户端(会被反编译),而Token有过期时间,即使泄露影响也有限。
2.3 端云协同决策流程
flowchart TD
A[AI请求发起] --> B{判断任务类型}
B -->|简单任务| C{端侧模型可用?}
B -->|复杂任务| D{网络可用?}
C -->|可用| E[端侧推理]
C -->|不可用| D
D -->|可用| F[云侧推理]
D -->|不可用| G{端侧有降级模型?}
G -->|有| H[端侧降级推理]
G -->|无| I[返回错误提示]
E --> J[结果后处理]
F --> J
H --> J
J --> K[UI展示]
classDef primary fill:#4A90D9,stroke:#2C5F8A,color:#fff
classDef warning fill:#F5A623,stroke:#C77D05,color:#fff
classDef error fill:#D0021B,stroke:#8B0000,color:#fff
classDef info fill:#7B68EE,stroke:#5B48C2,color:#fff
classDef purple fill:#9B59B6,stroke:#6C3483,color:#fff
class A,B primary
class C,D,G warning
class E,F,H info
class I error
class J,K purple
三、代码实战
3.1 示例一:华为云AI服务认证与请求封装
这是最基础也最重要的部分——一个健壮的云端AI请求框架。
// 华为云AI服务 - 认证与请求封装
import { http } from '@kit.NetworkKit';
import { util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
// 华为云AI服务配置
interface HuaweiCloudAIConfig {
region: string; // 区域,如 cn-north-4
projectId: string; // 项目ID
domainName: string; // 账号域名
username: string; // IAM用户名
password: string; // 密码(实际应从安全存储获取)
}
// Token信息
interface TokenInfo {
token: string; // 认证Token
expiresAt: number; // 过期时间戳(ms)
userId: string; // 用户ID
}
// API响应通用结构
interface AIResponse<T> {
code: number; // 状态码
message: string; // 消息
data: T; // 业务数据
requestId: string; // 请求ID(用于排查问题)
}
// 华为云AI服务客户端
class HuaweiCloudAIClient {
private config: HuaweiCloudAIConfig;
private tokenInfo: TokenInfo | null = null;
private requestQueue: Map<string, boolean> = new Map(); // 请求去重
constructor(config: HuaweiCloudAIConfig) {
this.config = config;
}
// 获取Token(带缓存和自动续期)
async getToken(): Promise<string> {
// Token未过期则直接使用
if (this.tokenInfo && Date.now() < this.tokenInfo.expiresAt - 60000) {
return this.tokenInfo.token;
}
// 请求新Token
try {
const url = `https://iam.${this.config.region}.myhuaweicloud.com/v3/auth/tokens`;
const body = {
auth: {
identity: {
methods: ['password'],
password: {
user: {
name: this.config.username,
password: this.config.password,
domain: { name: this.config.domainName },
},
},
},
scope: {
project: { name: this.config.region },
},
},
};
const response = await http.request(url, {
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
extraData: JSON.stringify(body),
connectTimeout: 10000,
readTimeout: 10000,
});
if (response.responseCode === 201) {
// 从响应头获取Token
const token = response.header['X-Subject-Token'] as string;
const responseBody = JSON.parse(response.result as string);
const expiresAt = new Date(responseBody.token.expires_at).getTime();
this.tokenInfo = {
token: token,
expiresAt: expiresAt,
userId: responseBody.token.user.id,
};
console.info('[CloudAI] Token获取成功,有效期至: ' + new Date(expiresAt).toLocaleString());
return token;
} else {
throw new Error(`Token请求失败: ${response.responseCode}`);
}
} catch (error) {
const err = error as BusinessError;
console.error(`[CloudAI] Token获取异常: ${err.message}`);
throw err;
}
}
// 通用API调用方法(带重试和限流)
async callAPI<T>(
service: string, // 服务名,如 ocr/v1
action: string, // 接口名,如/ocr/general
body: object, // 请求体
maxRetries: number = 3 // 最大重试次数
): Promise<AIResponse<T>> {
// 请求去重:相同请求5秒内不重复发送
const requestKey = `${service}:${action}:${JSON.stringify(body)}`;
if (this.requestQueue.get(requestKey)) {
throw new Error('重复请求,请稍后再试');
}
this.requestQueue.set(requestKey, true);
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const token = await this.getToken();
const url = `https://${service}.${this.config.region}.myhuaweicloud.com${action}`;
const response = await http.request(url, {
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
'X-Auth-Token': token,
'X-Project-Id': this.config.projectId,
},
extraData: JSON.stringify(body),
connectTimeout: 15000,
readTimeout: 30000,
});
// 释放请求锁
this.requestQueue.delete(requestKey);
if (response.responseCode === 200) {
const result = JSON.parse(response.result as string) as AIResponse<T>;
return result;
} else if (response.responseCode === 429) {
// 被限流,等待后重试
const waitTime = Math.min(1000 * Math.pow(2, attempt), 10000);
console.warn(`[CloudAI] 被限流,${waitTime}ms后重试(${attempt}/${maxRetries})`);
await new Promise(resolve => setTimeout(resolve, waitTime));
continue;
} else if (response.responseCode === 401) {
// Token过期,清除缓存后重试
this.tokenInfo = null;
console.warn('[CloudAI] Token过期,重新获取');
continue;
} else {
throw new Error(`API调用失败: ${response.responseCode} - ${response.result}`);
}
} catch (error) {
lastError = error as Error;
console.error(`[CloudAI] 第${attempt}次请求失败: ${lastError.message}`);
if (attempt < maxRetries) {
const waitTime = 1000 * attempt; // 指数退避
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
}
this.requestQueue.delete(requestKey);
throw lastError || new Error('API调用失败');
}
// 释放资源
release(): void {
this.tokenInfo = null;
this.requestQueue.clear();
}
}
3.2 示例二:华为云OCR服务对接
基于上面的客户端封装,实现一个完整的云端OCR识别功能。
// 华为云OCR服务对接 - 完整实现
import { image } from '@kit.ImageKit';
import { buffer } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
// OCR识别结果
interface CloudOCRResult {
wordsBlockList: OCRWordBlock[];
direction: number; // 图片方向
totalCount: number; // 识别文字总数
}
interface OCRWordBlock {
words: string; // 识别文字
confidence: number; // 置信度
location: OCRLocation; // 文字位置
}
interface OCRLocation {
topLeft: Point;
topRight: Point;
bottomLeft: Point;
bottomRight: Point;
}
interface Point {
x: number;
y: number;
}
// OCR服务封装
class CloudOCRService {
private client: HuaweiCloudAIClient;
constructor(client: HuaweiCloudAIClient) {
this.client = client;
}
// 通用文字识别
async recognizeGeneral(pixelMap: image.PixelMap): Promise<CloudOCRResult> {
// 将PixelMap转为Base64
const base64Image = await this.pixelMapToBase64(pixelMap);
// 调用云端OCR API
const response = await this.client.callAPI<CloudOCRResult>(
'ocr',
'/v2/' + this.client.getProjectId() + '/ocr/general-text',
{
image: base64Image, // Base64编码的图片
detect_direction: true, // 检测图片方向
detect_language: true, // 检测语言
}
);
return response.data;
}
// 身份证识别(专用接口,精度更高)
async recognizeIDCard(
pixelMap: image.PixelMap,
side: 'front' | 'back'
): Promise<object> {
const base64Image = await this.pixelMapToBase64(pixelMap);
const response = await this.client.callAPI<object>(
'ocr',
'/v2/' + this.client.getProjectId() + '/ocr/id-card',
{
image: base64Image,
side: side, // 正面或反面
return_text_location: true,
}
);
return response.data;
}
// 发票识别
async recognizeInvoice(pixelMap: image.PixelMap): Promise<object> {
const base64Image = await this.pixelMapToBase64(pixelMap);
const response = await this.client.callAPI<object>(
'ocr',
'/v2/' + this.client.getProjectId() + '/ocr/vat-invoice',
{
image: base64Image,
return_text_location: true,
}
);
return response.data;
}
// PixelMap转Base64辅助方法
private async pixelMapToBase64(pixelMap: image.PixelMap): Promise<string> {
const packingApi = image.createImagePacker();
const packOpts: image.PackingOption = {
format: 'image/jpeg',
quality: 90, // 压缩质量,平衡精度和传输大小
};
const data: buffer.Buffer = await packingApi.pack(pixelMap, packOpts);
const base64Helper = new util.Base64Helper();
const base64Str = base64Helper.encodeToStringSync(data.buffer);
packingApi.release();
return base64Str;
}
}
// OCR页面组件
@Entry
@Component
struct CloudOCRPage {
@State ocrResult: CloudOCRResult | null = null;
@State isLoading: boolean = false;
@State fullText: string = '';
@State errorMessage: string = '';
private ocrService: CloudOCRService | null = null;
aboutToAppear(): void {
// 初始化云端AI客户端
const client = new HuaweiCloudAIClient({
region: 'cn-north-4',
projectId: 'your-project-id',
domainName: 'your-domain',
username: 'your-username',
password: 'your-password', // 实际应从安全存储获取
});
this.ocrService = new CloudOCRService(client);
}
// 执行云端OCR识别
private async doRecognize(pixelMap: image.PixelMap): Promise<void> {
if (!this.ocrService) {
this.errorMessage = 'OCR服务未初始化';
return;
}
this.isLoading = true;
this.errorMessage = '';
this.ocrResult = null;
this.fullText = '';
try {
const result = await this.ocrService.recognizeGeneral(pixelMap);
this.ocrResult = result;
// 拼接完整文本
this.fullText = result.wordsBlockList
.map(block => block.words)
.join('\n');
console.info(`[CloudOCR] 识别完成,共${result.totalCount}个文字块`);
} catch (error) {
const err = error as BusinessError;
this.errorMessage = `识别失败: ${err.message}`;
console.error(`[CloudOCR] 错误: ${err.code} - ${err.message}`);
} finally {
this.isLoading = false;
}
}
aboutToDisappear(): void {
this.ocrService = null;
}
build() {
Column() {
// 标题
Text('云端OCR识别')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#e0e0e0')
.margin({ bottom: 20 })
// 错误信息
if (this.errorMessage) {
Text(this.errorMessage)
.fontSize(14)
.fontColor('#D0021B')
.padding(12)
.backgroundColor('#3d1111')
.borderRadius(8)
.width('100%')
.margin({ bottom: 12 })
}
// 识别结果
if (this.ocrResult) {
// 统计信息
Row() {
Text(`识别到 ${this.ocrResult.totalCount} 个文字块`)
.fontSize(14)
.fontColor('#7B68EE')
Text(`方向: ${this.ocrResult.direction}°`)
.fontSize(14)
.fontColor('#4A90D9')
.margin({ left: 16 })
}
.width('100%')
.margin({ bottom: 12 })
// 文字块列表
List() {
ForEach(this.ocrResult.wordsBlockList, (block: OCRWordBlock, index: number) => {
ListItem() {
Row() {
Text(block.words)
.fontSize(16)
.fontColor('#e0e0e0')
.layoutWeight(1)
Text(`${(block.confidence * 100).toFixed(1)}%`)
.fontSize(12)
.fontColor(block.confidence > 0.9 ? '#4A90D9' : '#F5A623')
}
.width('100%')
.padding(12)
.backgroundColor('#1a1a2e')
.borderRadius(8)
.margin({ bottom: 4 })
}
}, (block: OCRWordBlock, index: number) => `${index}`)
}
.layoutWeight(1)
.width('100%')
} else {
// 空状态
Column() {
Text('📷')
.fontSize(48)
.margin({ bottom: 12 })
Text('选择图片开始识别')
.fontSize(16)
.fontColor('#666')
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
// 操作按钮
Button(this.isLoading ? '识别中...' : '选择图片识别')
.width('80%')
.height(50)
.fontSize(18)
.backgroundColor(this.isLoading ? '#666' : '#4A90D9')
.borderRadius(25)
.enabled(!this.isLoading)
.margin({ top: 16, bottom: 32 })
.onClick(() => {
// 实际项目中调起相册选择图片
console.info('[CloudOCR] 选择图片');
})
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#0d0d1a')
}
}
3.3 示例三:ModelArts自定义模型部署与调用
当华为云预置的AI服务不满足需求时,可以使用ModelArts训练自定义模型并部署为API。
// ModelArts自定义模型调用服务
import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 自定义模型推理请求
interface ModelInferenceRequest {
data: object; // 推理输入数据
parameters?: object; // 推理参数
}
// 自定义模型推理响应
interface ModelInferenceResponse {
predicted_label: string; // 预测标签
probabilities: Record<string, number>; // 各标签概率
meta: {
model_id: string;
model_version: string;
inference_time: number; // 推理耗时(ms)
};
}
// ModelArts服务封装
class ModelArtsService {
private client: HuaweiCloudAIClient;
private serviceId: string; // 部署的服务ID
constructor(client: HuaweiCloudAIClient, serviceId: string) {
this.client = client;
this.serviceId = serviceId;
}
// 调用自定义模型推理
async infer(inputData: object, parameters?: object): Promise<ModelInferenceResponse> {
const response = await this.client.callAPI<ModelInferenceResponse>(
'modelarts',
`/v1/${this.client.getProjectId()}/services/${this.serviceId}/infer`,
{
data: inputData,
parameters: parameters || {},
}
);
return response.data;
}
// 批量推理
async batchInfer(
inputList: object[],
batchSize: number = 5
): Promise<ModelInferenceResponse[]> {
const results: ModelInferenceResponse[] = [];
// 分批处理,避免超时
for (let i = 0; i < inputList.length; i += batchSize) {
const batch = inputList.slice(i, i + batchSize);
const batchPromises = batch.map(input => this.infer(input));
try {
const batchResults = await Promise.allSettled(batchPromises);
for (const result of batchResults) {
if (result.status === 'fulfilled') {
results.push(result.value);
} else {
console.error(`[ModelArts] 批量推理某项失败: ${result.reason}`);
}
}
} catch (error) {
console.error(`[ModelArts] 批量推理异常: ${(error as Error).message}`);
}
// 批次间短暂延迟,避免限流
if (i + batchSize < inputList.length) {
await new Promise(resolve => setTimeout(resolve, 200));
}
}
return results;
}
// 查询服务状态
async getServiceStatus(): Promise<{
status: string;
inferType: string;
startTime: string;
}> {
const token = await this.client.getToken();
const url = `https://modelarts.${this.client.getRegion()}.myhuaweicloud.com/v1/${this.client.getProjectId()}/services/${this.serviceId}`;
const response = await http.request(url, {
method: http.RequestMethod.GET,
header: { 'X-Auth-Token': token },
connectTimeout: 10000,
readTimeout: 10000,
});
if (response.responseCode === 200) {
const result = JSON.parse(response.result as string);
return {
status: result.status,
inferType: result.infer_type,
startTime: result.start_time,
};
}
throw new Error(`服务状态查询失败: ${response.responseCode}`);
}
}
// 自定义模型推理页面
@Entry
@Component
struct CustomModelPage {
@State inferenceResult: ModelInferenceResponse | null = null;
@State isInferring: boolean = false;
@State serviceStatus: string = 'unknown';
@State inferenceHistory: ModelInferenceResponse[] = [];
private modelArtsService: ModelArtsService | null = null;
aboutToAppear(): void {
const client = new HuaweiCloudAIClient({
region: 'cn-north-4',
projectId: 'your-project-id',
domainName: 'your-domain',
username: 'your-username',
password: 'your-password',
});
this.modelArtsService = new ModelArtsService(client, 'your-service-id');
this.checkServiceStatus();
}
// 检查服务状态
private async checkServiceStatus(): Promise<void> {
if (!this.modelArtsService) return;
try {
const status = await this.modelArtsService.getServiceStatus();
this.serviceStatus = status.status;
console.info(`[ModelArts] 服务状态: ${status.status}`);
} catch (error) {
this.serviceStatus = 'error';
console.error('[ModelArts] 状态检查失败');
}
}
// 执行推理
private async doInference(): Promise<void> {
if (!this.modelArtsService) return;
this.isInferring = true;
try {
// 示例:发送图片特征数据
const result = await this.modelArtsService.infer(
{ image_base64: 'your-base64-image-data' },
{ top_k: 5 }
);
this.inferenceResult = result;
this.inferenceHistory.push(result);
console.info(`[ModelArts] 推理完成: ${result.predicted_label}`);
} catch (error) {
console.error(`[ModelArts] 推理失败: ${(error as Error).message}`);
} finally {
this.isInferring = false;
}
}
build() {
Scroll() {
Column() {
// 服务状态指示器
Row() {
Circle({ width: 10, height: 10 })
.fill(this.serviceStatus === 'running' ? '#4A90D9' : '#D0021B')
Text(`服务状态: ${this.serviceStatus}`)
.fontSize(14)
.fontColor('#999')
.margin({ left: 8 })
}
.margin({ bottom: 20 })
// 推理结果
if (this.inferenceResult) {
Text('推理结果')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#e0e0e0')
.margin({ bottom: 12 })
// 预测标签
Row() {
Text('预测标签:')
.fontSize(16)
.fontColor('#999')
Text(this.inferenceResult.predicted_label)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#4A90D9')
.margin({ left: 8 })
}
.width('100%')
.margin({ bottom: 12 })
// 概率分布
Text('概率分布:')
.fontSize(14)
.fontColor('#7B68EE')
.margin({ bottom: 8 })
ForEach(
Object.entries(this.inferenceResult.probabilities),
([label, prob]: [string, number]) => {
Row() {
Text(label)
.fontSize(14)
.fontColor('#e0e0e0')
.width(80)
Progress({ value: prob * 100, total: 100, type: ProgressType.Linear })
.layoutWeight(1)
.color('#7B68EE')
Text(`${(prob * 100).toFixed(2)}%`)
.fontSize(12)
.fontColor('#F5A623')
.width(70)
.textAlign(TextAlign.End)
}
.width('100%')
.padding(8)
.backgroundColor('#1a1a2e')
.borderRadius(6)
.margin({ bottom: 4 })
}
)
// 推理元信息
Text(`模型: ${this.inferenceResult.meta.model_id} | 耗时: ${this.inferenceResult.meta.inference_time}ms`)
.fontSize(12)
.fontColor('#666')
.margin({ top: 12 })
}
// 操作按钮
Button(this.isInferring ? '推理中...' : '执行推理')
.width('80%')
.height(50)
.fontSize(18)
.backgroundColor(this.isInferring ? '#666' : '#7B68EE')
.borderRadius(25)
.enabled(!this.isInferring && this.serviceStatus === 'running')
.margin({ top: 24, bottom: 32 })
.onClick(() => this.doInference())
}
.width('100%')
.padding(20)
}
.width('100%')
.height('100%')
.backgroundColor('#0d0d1a')
}
}
四、踩坑与注意事项
4.1 AK/SK千万不要硬编码
这是最常见的安全问题。很多开发者图方便,直接把AK/SK写在代码里:
// ❌ 绝对不要这样做!
const AK = 'WRDXMSZJU...';
const SK = 'v3F7qN9pL...';
正确做法:
- 使用HarmonyOS的安全存储(
@ohos.security.huks)加密保存密钥 - 通过自有后端服务器代理API调用,客户端不直接接触AK/SK
- 使用Token认证,Token有有效期,泄露风险可控
4.2 图片Base64编码的坑
华为云OCR等视觉服务要求图片以Base64编码传入,但有几个细节容易踩坑:
- 编码后大小限制:Base64编码会使数据膨胀约33%,原图超过10MB可能超限
- 格式要求:支持JPEG/PNG/BMP/TIFF,不支持WebP
- EXIF方向:部分手机拍的照片有EXIF旋转标记,需要先校正方向再编码
4.3 网络超时与弱网处理
云端AI服务的响应时间通常在1-5秒,但在弱网环境下可能更久。必须设置合理的超时时间:
// 推荐的超时配置
const HTTP_CONFIG = {
connectTimeout: 10000, // 连接超时10秒
readTimeout: 60000, // 读取超时60秒(大模型推理可能较慢)
writeTimeout: 30000, // 写入超时30秒(上传大图片时)
};
同时,建议实现离线降级策略:网络不可用时自动切换到端侧模型。
4.4 并发请求的坑
华为云AI服务有QPS限制(不同服务不同,通常5-20 QPS),批量推理时务必控制并发:
// 并发控制器
class ConcurrencyController {
private running: number = 0;
private maxConcurrency: number;
constructor(maxConcurrency: number = 5) {
this.maxConcurrency = maxConcurrency;
}
async run<T>(task: () => Promise<T>): Promise<T> {
// 等待空闲槽位
while (this.running >= this.maxConcurrency) {
await new Promise(resolve => setTimeout(resolve, 100));
}
this.running++;
try {
return await task();
} finally {
this.running--;
}
}
}
4.5 费用监控
云端AI服务按调用次数计费,务必在代码中加入费用监控,避免因为bug导致大量无效调用:
// 简单的费用追踪器
class CostTracker {
private callCount: number = 0;
private dailyLimit: number;
constructor(dailyLimit: number = 10000) {
this.dailyLimit = dailyLimit;
}
// 每次调用前检查
canCall(): boolean {
if (this.callCount >= this.dailyLimit) {
console.warn(`[CostTracker] 已达每日调用上限(${this.dailyLimit})`);
return false;
}
return true;
}
// 记录一次调用
record(): void {
this.callCount++;
if (this.callCount % 100 === 0) {
console.info(`[CostTracker] 今日已调用${this.callCount}次`);
}
}
getUsage(): number {
return this.callCount;
}
}
五、HarmonyOS 6适配
5.1 网络安全增强
HarmonyOS 6引入了更严格的网络安全策略:
| 变更项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| 证书校验 | 系统级校验 | 新增应用级证书锁定(Certificate Pinning) |
| DNS解析 | 系统DNS | 新增DoH(DNS over HTTPS)支持 |
| 请求签名 | 手动实现 | 新增@ohos.net.signature模块 |
5.2 迁移指南
// HarmonyOS 6 证书锁定配置
import { http } from '@kit.NetworkKit';
const httpClient = http.createHttpClient({
// 证书锁定:只信任特定证书
certificatePinning: {
pins: [
{
hostname: '*.myhuaweicloud.com',
publicKeyHash: 'sha256/your-pin-hash-here',
},
],
},
// DoH配置
dnsOverHttps: {
url: 'https://dns.huaweicloud.com/dns-query',
},
});
5.3 端云无缝切换
HarmonyOS 6新增了AIServiceRouter,可以自动在端侧和云侧之间切换:
// HarmonyOS 6 端云自动路由
import { aiServiceRouter } from '@hms.core.ml-kit';
const router = aiServiceRouter.create({
strategy: aiServiceRouter.Strategy.AUTO, // 自动选择最优路径
fallback: aiServiceRouter.Fallback.ON_DEVICE, // 云端不可用时降级到端侧
latencyThreshold: 2000, // 延迟阈值,超过2秒考虑切换
});
// 使用路由器调用AI能力
const result = await router.recognizeText(imageData);
// result.source 会告诉你实际使用了端侧还是云侧
六、总结
本文深入讲解了HarmonyOS应用对接华为云AI服务的全链路实现,核心知识点如下:
华为云AI服务对接知识图谱
├── 认证鉴权
│ ├── AK/SK签名(服务端用)
│ ├── Token认证(客户端推荐)
│ ├── IAM代理(企业场景)
│ └── 安全存储密钥(禁止硬编码)
├── 请求封装
│ ├── Token缓存与自动续期
│ ├── 请求去重
│ ├── 指数退避重试
│ ├── 429限流处理
│ └── 401自动重新认证
├── 服务对接
│ ├── OCR文字识别
│ ├── 图像识别
│ ├── 语音服务
│ ├── NLP服务
│ └── ModelArts自定义模型
├── 踩坑要点
│ ├── AK/SK安全
│ ├── Base64编码细节
│ ├── 超时与弱网
│ ├── 并发控制
│ └── 费用监控
└── HarmonyOS 6适配
├── 证书锁定
├── DoH支持
├── 请求签名模块
└── AIServiceRouter端云自动路由
一句话总结:华为云AI服务让HarmonyOS应用获得了远超端侧算力的AI能力,但对接过程中必须重视认证安全、请求健壮性、并发控制和费用管理,HarmonyOS 6的端云自动路由让这一切变得更简单。
- 点赞
- 收藏
- 关注作者
评论(0)