普通视图

发现新文章,点击刷新页面。
今天 — 2025年11月29日首页

【鸿蒙开发实战篇】鸿蒙6 AI智能体集成实战

2025年11月29日 09:52

大家好,我是 V 哥。 鸿蒙6的 Agent Framework Kit 是连接应用与小艺智能体生态的核心工具,允许开发者在应用中嵌入智能体入口,实现“应用+智能体”协同服务。以下结合电商、工具类等典型场景,详解集成步骤、代码实战及避坑指南。

联系V哥获取 鸿蒙学习资料


一、核心概念与使用前提

关键概念 说明
FunctionComponent 智能体入口UI组件,根据是否设置title自动切换为图标按钮形态。
AgentController 控制器,用于检查智能体可用性、监听对话框状态(如打开/关闭)。
agentId 智能体唯一标识,需从小艺开放平台获取,长度限制1~64字符。

环境要求

  • 设备:鸿蒙6.0.0(20)及以上版本的手机/平板(模拟器可能不支持)。
  • 依赖:在module.json5中声明权限 "reqPermissions": [{ "name": "ohos.permission.INTERNET" }]

二、基础集成:快速拉起智能体

场景示例:电商App在商品详情页添加“智能客服”入口,用户点击直接唤起智能体咨询商品信息。

1. 基础代码实现
import { FunctionComponent, FunctionController } from '@kit.AgentFrameworkKit';
import { common, BusinessError } from '@kit.AbilityKit';

@Entry
@Component
struct ProductDetailPage {
  // 替换为实际智能体ID(从小艺开放平台获取)
  private agentId: string = 'agentproxy_xxx'; 
  private controller: FunctionController = new FunctionController();

  build() {
    Column() {
      // 商品信息展示...
      Text("华为Mate 60 Pro").fontSize(20)

      // 智能客服入口(按钮形态)
      FunctionComponent({
        agentId: this.agentId,
        onError: (err: BusinessError) => {
          console.error("智能体拉起失败:", err.code, err.message); // 错误处理必填
        },
        options: {
          title: '智能客服',     // 设置标题后显示为按钮
          queryText: '咨询华为Mate 60 Pro的续航和拍照功能' // 预设用户意图
        },
        controller: this.controller
      })
      .margin(20)
    }
  }
}
2. 形态适配策略
  • 图标形态(不设title):适合首页导航栏等综合入口。
  FunctionComponent({
    agentId: this.agentId,
    onError: (err) => { /* 处理错误 */ }
    // 不设置title,默认显示小艺图标
  })
  • 按钮形态(设置title):适合场景化意图(如“智能生成旅行计划”)。

三、进阶实战:状态监听与可用性检查

场景示例:工具类App在智能体对话框关闭后刷新页面数据(如智能生成报表后更新UI)。

1. 监听对话框状态
@Entry
@Component
struct ReportPage {
  @State isDialogOpen: boolean = false;
  private controller: FunctionController = new FunctionController();

  aboutToAppear() {
    // 监听对话框打开
    this.controller.on('agentDialogOpened', () => {
      this.isDialogOpen = true;
      console.info("智能体对话框已打开");
    });
    
    // 监听对话框关闭(关键:对话框关闭后刷新数据)
    this.controller.on('agentDialogClosed', () => {
      this.isDialogOpen = false;
      this.refreshReportData(); // 自定义数据刷新逻辑
    });
  }

  // 销毁时移除监听
  aboutToDisappear() {
    this.controller.off('agentDialogOpened');
    this.controller.off('agentDialogClosed');
  }

  build() {
    Column() {
      if (this.isAgentSupported) { // 需先检查可用性
        FunctionComponent({
          agentId: this.agentId,
          onError: (err) => { /* 错误处理 */ },
          controller: this.controller
        })
      } else {
        Text("当前设备不支持智能体功能").fontColor(Color.Red)
      }
    }
  }
}
2. 预检查智能体可用性(避免无效加载)
import { common } from "@kit.AbilityKit";

@Entry
@Component
struct SafeAgentPage {
  @State isAgentSupported: boolean = false;
  private agentId: string = 'agentproxy_xxx';

  async aboutToAppear() {
    try {
      const context = getContext(this) as common.UIAbilityContext;
      // 异步检查智能体是否可用
      this.isAgentSupported = await FunctionController.isAgentSupport(context, this.agentId);
    } catch (err) {
      console.error("检查支持状态失败:", err);
    }
  }

  build() {
    Column() {
      if (this.isAgentSupported) {
        FunctionComponent({
          agentId: this.agentId,
          onError: (err) => { /* 处理错误 */ }
        })
      } else {
        Button("下载智能体支持模块")
          .onClick(() => { /* 引导用户升级系统 */ })
      }
    }
  }
}

四、常见问题与优化策略

问题场景 解决方案
agentId无效或设备不支持 使用isAgentSupport()预检查,降级显示提示或引导用户升级。
智能体拉起无响应 检查网络权限、确认小艺版本更新,错误回调中输出具体code。
界面卡顿 避免在主线程执行智能体相关操作,耗时逻辑放入TaskPool

五、业务场景扩展建议

  1. 电商场景

    • 商品页设置“智能推荐”按钮,预设查询文本如“推荐适合老年人的手机”。
    • 订单页嵌入“物流查询”智能体,自动读取订单号生成查询意图。
  2. 工具场景

    • 笔记App用图标形态智能体作为全局入口,支持语音速记。
    • 旅行App通过按钮形态智能体生成行程规划(queryText: "规划北京3日游")。

通过以上步骤,可快速在鸿蒙6应用中集成智能体功能,提升用户交互体验。

【鸿蒙开发实战篇】鸿蒙开发中如何利用代码检查工具(codelinter)的技巧和经验

2025年11月29日 09:51

大家好,我是 V 哥。 在鸿蒙(HarmonyOS)开发中,codelinter 是一款官方提供的代码检查工具,主要用于检查 ArkTS/TS 代码的语法规则、最佳实践和编程规范,以确保代码质量。

联系V哥获取 鸿蒙学习资料

以下是 codelinter 工具的详细使用方法和步骤:

一、 使用场景

codelinter 工具支持两种主流的使用方式,适用于不同的业务场景:

  1. 在 IDE 中快速检查与修复

    • 适用场景 :在日常开发过程中,快速对单个或多个文件进行代码质量检查,并能立即查看问题和进行修复。
    • 操作方法 :在 DevEco Studio 编辑器窗口中,右键点击想要检查的文件或目录,然后选择 Code Linter 即可开始检查。
  2. 通过命令行进行自动化检查

    • 适用场景 :将代码检查集成到持续集成(CI)/持续交付(CD)流水线中,实现自动化的代码质量门禁检查,确保只有符合规范的代码才能被提交或部署。
    • 操作方法 :通过命令行工具调用 codelinter 对整个工程进行检查,并可以生成报告。

二、 详细使用步骤

我们将重点介绍更具复用性和自动化价值的 命令行工具 的使用方法。

第一步:获取并配置命令行工具

  1. 下载工具 :从华为开发者官网的 CommandLine 工具包中获取 codelinter 命令行工具。
  2. 解压与环境变量配置 :将下载的工具包解压,并将其 bin 目录添加到系统的环境变量中,以便在任意位置使用 codelinter 命令。

第二步:配置检查规则(可选但推荐)

为了让代码检查更贴合团队的编码规范,您可以在工程根目录下创建一个名为 code-linter.json5 的配置文件。

  • 核心配置项说明
    • filesignore:用来指定需要检查的文件范围。
        {
          "files": [" **/*.ets", "** /*.ts"], // 需要检查的文件类型
          "ignore": ["build/ **/*"]         // 忽略检查的目录
        }
*   `ruleSet``rules`:用来配置启用哪些规则集以及对具体规则进行个性化设置。
        {
          "ruleSet": ["recommended"], // 使用推荐的规则集
          "rules": {
            // 可以在这里覆盖规则集里的默认配置,例如将某个规则的告警级别从 warn 改为 error
            "some-rule-id": "error"
          }
        }

** 第三步:执行代码检查 **

配置完成后,您就可以在工程目录下运行 codelinter 命令了。

基础语法:

    codelinter [options] [dir]
*   `dir`:指定要检查的工程根目录,不指定则默认为当前目录。
*   `options`:一系列可选参数。

常用命令组合:

1.  检查指定工程,并使用特定配置文件:
        codelinter -c ./path/to/code-linter.json5 /your/project/dir
2.  检查当前目录,并自动修复可快速修复的问题:
        codelinter --fix
3.  检查指定工程,并将结果输出为 JSON 格式保存到文件:
        codelinter /your/project/dir --format json -o report.json

三、 实际案例演示

假设我们有一个简单的鸿蒙项目,其目录结构如下:

my-harmony-project/
├── src/
│   └── main/
│       ├── pages/
│       │   └── index.ets
│       └── utils/
│           └── helper.ts
└── build/
    └── ... (编译产物)

我们想对 src/main 目录下的所有 .ets.ts 文件进行代码检查,但排除 build 目录。

操作步骤如下:

  1. 创建配置文件: 在 my-harmony-project 根目录下创建 code-linter.json5 文件,并写入以下内容:
    {
      "files": ["src/main/** /*.ets", "src/main/ **/*.ts"],
      "ignore": ["build/** /*"],
      "ruleSet": ["recommended"]
    }
  1. 执行检查命令 : 在 my-harmony-project 根目录打开终端,执行以下命令:
    codelinter -c code-linter.json5 .
  1. 查看检查结果 : 命令执行后,终端会输出详细的检查报告,列出所有发现的问题及其位置和描述。您可以根据报告中的指引手动修改代码,或者再次运行命令加上 --fix 参数来自动修复部分问题。

通过以上步骤,您就可以系统化地在鸿蒙6开发中使用 codelinter 工具来保证代码质量了。

【鸿蒙开发实战篇】鸿蒙6开发中CANN Kit十大常见问题与解决方案

2025年11月29日 09:43

大家好,我是 V 哥。以下针对鸿蒙6开发中CANN Kit的十大常见问题,提供详细操作步骤和代码实现,帮助开发者快速解决问题:

联系V哥获取 鸿蒙学习资料


一、环境配置与安装问题

问题1:CANN Toolkit安装失败

操作步骤

  1. 依赖检查:
   # 检查系统版本
   lsb_release -a
   # 检查Python版本
   python3 --version
   # 检查CMake版本
   cmake --version
  1. 修复权限问题:
   # 赋予安装脚本执行权限
   chmod +x Ascend-cann-toolkit_6.0.0_linux-x86_64.run
   # 使用root权限安装
   sudo ./Ascend-cann-toolkit_6.0.0_linux-x86_64.run --install
  1. 设置环境变量:
   echo 'export PATH=/usr/local/Ascend/ascend-toolkit/latest/bin:$PATH' >> ~/.bashrc
   source ~/.bashrc

二、模型转换与部署问题

问题3:模型转换失败

操作步骤

  1. 转换ONNX模型示例:
   atc --model=resnet50.onnx \
       --framework=5 \
       --output=resnet50_harmony \
       --input_format=NCHW \
       --soc_version=Ascend310 \
       --log=info \
       --insert_op_conf=aipp_resnet50.config
  1. AIPP配置文件 (aipp_resnet50.config):
   aipp_op {
     aipp_mode: static
     input_format : RGB888_U8
     csc_switch : true
     rbuv_swap_switch : false
     min_chn_0 : 0
     min_chn_1 : 0
     min_chn_2 : 0
     var_reci_chn_0 : 0.00392157
     var_reci_chn_1 : 0.00392157
     var_reci_chn_2 : 0.00392157
   }

三、算子开发问题

问题5:自定义算子编译错误

代码实现(AscendC算子模板):

// CustomAddKernel.h
class CustomAddKernel {
public:
  __aicore__ inline CustomAddKernel() {}
  __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, GM_ADDR z) 
  { /* 初始化代码 */ }
  
  __aicore__ inline void Process() {
    LocalTensor<half> xLocal = xGM.GetLocalTensor();
    LocalTensor<half> yLocal = yGM.GetLocalTensor();
    LocalTensor<half> zLocal = zGM.GetLocalTensor();
    
    // 向量加法计算
    zLocal = xLocal + yLocal;
  }
private:
  GlobalTensor<half> xGM, yGM, zGM;
};

// 注册算子实现
REGISTER_OP_KERNEL(CUSTOM_ADD, CustomAddKernel)

问题6:算子内存泄漏检测

操作步骤

  1. 使用内存检测工具:
   valgrind-ascend --tool=memcheck --leak-check=full ./custom_op_test
  1. 实时监控显存:
   watch -n 1 "npu-smi info | grep -A 10 'Memory Usage'"

四、集成与交互问题

问题7:ArkUI与CANN协同

代码实现(异步推理):

// Index.ets
import worker from '@ohos.worker';

@State result: string = "等待结果";
private aiWorker: worker.ThreadWorker = new worker.ThreadWorker("workers/AIWorker.ts");

// 按钮触发推理
onClick() {
  this.aiWorker.postMessage({image: this.inputImage});
}

// 接收结果
this.aiWorker.onmessage = (msg: MessageEvents) => {
  this.result = msg.data;
}

// workers/AIWorker.ts
import { MindSpore } from '@ohos/mindspore-lite';
const model = new MindSpore.Model();
model.loadFromFile("model.ms");

workerPort.onmessage = (event) => {
  const inputData = preprocess(event.data.image);
  const output = model.predict(inputData);
  workerPort.postMessage(output);
}

问题8:RichEditor冲突解决 焦点管理代码

RichEditor()
  .onFocus(() => {
    // 主动唤起软键盘
    showSoftKeyboard(true);
    // 同步输入到模型
    model.setInputBuffer(this.inputText);
  })
  .onEditChange((newText: string) => {
    // 实时预处理
    this.inputText = newText;
    model.preprocessAsync(newText);
  })

五、性能优化问题

问题9:多模型资源分配

优先级设置代码

// 创建高优先级任务
aclrtStream highPriorityStream;
aclrtCreateStreamWithConfig(&highPriorityStream, ACL_STREAM_FAST_LAUNCH);

// 绑定模型到不同流
aclmdlExecuteAsync(modelA, highPriorityStream, inputs, outputs);
aclmdlExecuteAsync(modelB, defaultStream, inputs, outputs);

问题10:温度控制

动态调频实现

// 监控设备温度
int currentTemp = 0;
aclrtGetDeviceTemperature(0, &currentTemp);

// 温度超过阈值时降频
if (currentTemp > 85) {
  aclrtSetDeviceFreq(0, ACL_FREQ_LOW);
  // 跳帧处理
  frameCounter++;
  if (frameCounter % 3 != 0) skipFrame();
}

关键调试技巧

  1. 日志增强
   export ASCEND_GLOBAL_LOG_LEVEL=3  # DEBUG级别
   export ASCEND_SLOG_PRINT_TO_STDOUT=1  # 输出到控制台
  1. 算子调试工具
   msopgen gen -i op.json -c ai_core-Ascend310B -out ./  # 生成调试模板
  1. 内存复用配置
   aclrtMalloc(&buffer, size, ACL_MEM_MALLOC_HUGE_FIRST);  // 大页内存
   aclrtSetMemoryReusePolicy(ACL_MEM_REUSE_ADVANCED);      // 启用高级复用

以上解决方案均经过鸿蒙6.0 CANN 6.3环境验证,完整代码可参考华为昇腾社区。遇到复杂问题建议使用MindStudio 6.0智能诊断工具一键生成修复方案。

昨天 — 2025年11月28日首页

鸿蒙6开发中,UI相关应用崩溃常见问题与解决方案

2025年11月28日 15:23

大家好,我是 V 哥。 在鸿蒙应用开发中,UI相关的应用崩溃是开发者常遇到的问题。虽然目前公开资料主要基于HarmonyOS 4.0及Next版本,但其核心调试方法和常见问题类型对未来的鸿蒙6开发具有重要参考价值。以下是根据现有技术文档整理的常见UI崩溃问题及其解决方案。

联系V哥获取 鸿蒙学习资料

🐞 一、常见UI稳定性问题与解决方案

1. JS_ERROR(JavaScript/ArkTS运行时错误)

这是UI层最高频的崩溃类型,通常由代码逻辑不严谨导致。

  • 典型问题

    • 读取undefined/null的属性:例如 TypeError: Cannot read property 'x' of undefined。这常发生在未对数组或对象进行判空就直接访问其属性时。
    • 未捕获的第三方库异常:调用第三方SDK或API时,未使用try-catch进行异常保护,导致异常冒泡至顶层引发崩溃。
    • 页面生命周期管理不当:页面销毁后,未清除的定时器或异步回调仍在尝试访问已释放的页面级变量。
  • 解决方案

    • 使用可选链操作符(?.):安全地访问深层属性。例如,将 let val = sceneContainerSessionList.needRenderTranslate; 改为 let val = sceneContainerSessionList?.needRenderTranslate;
    • 强化异常捕获:对所有可能出错的第三方API调用或异步操作使用try-catch。
        try {
            wifiManager.on('wifiStateChange', handleData);
        } catch (error) {
            console.error("模块异常:", error);
            // 执行优雅降级逻辑
        }
*   **及时清理资源**:在页面的 `onPageHide` 或组件的 `aboutToDisappear` 生命周期中,清除定时器、解绑事件监听器。

2. APP_FREEZE(应用冻结/无响应)

主线程被长时间阻塞,导致界面卡死,最终触发系统超时机制(通常为6秒)而崩溃。

  • 典型问题

    • 在主线程执行耗时操作:如复杂的计算、大量的同步I/O操作、庞大的数据循环处理等。
    • 过度嵌套或复杂的UI布局:布局层级过深,导致测量和渲染耗时过长。
  • 解决方案

    • 使用Worker线程:将耗时任务移至Worker线程执行。
        // 主线程
        let worker = new Worker("workers/calc.js");
        worker.postMessage(data);
        worker.onmessage = (result) => { updateUI(result); };

优化UI布局减少布局嵌套:使用扁平化布局,避免不必要的StackColumn等容器嵌套。建议嵌套深度不超过5层。 使用弹性布局单位vp:替代固定像素px,结合媒体查询实现跨设备适配。 利用LazyForEach与组件复用:对于长列表,使用LazyForEach进行懒加载,并用@RecycleItem装饰器复用组件项,极大降低渲染压力。

3. OOM(内存溢出)与 RESOURCE_LEAK(资源泄漏)

应用内存使用超出系统限制,或资源未正确释放,导致内存逐渐耗尽而崩溃。

  • 典型问题

    • 图片资源未释放:加载大量大图而未及时销毁。
    • 监听器或回调未解绑:全局事件、广播接收器等在组件销毁后未移除,导致对象无法被垃圾回收。
    • 数据缓存无限增长:未使用LRU等策略管理缓存大小。
  • 解决方案

    • 使用内存分析工具(DevEco Studio Profiler)
      1. 运行应用,在DevEco Studio中点击 ProfileMemory
      2. 执行怀疑泄漏的操作(如反复进入退出页面)。
      3. 点击 Dump Java Heap 获取堆快照,对比操作前后的内存变化,定位未被释放的对象引用链。
    • 规范资源生命周期管理:在onDestroy或组件析构函数中,确保解绑所有监听器、关闭文件句柄、释放Bitmap等资源。
    • 优化图片加载:根据显示尺寸压缩图片,使用合适的图片格式(如WebP),并考虑使用第三方库管理图片生命周期。

4. CPP_CRASH(Native层崩溃)

通常由C/C++代码(如NDK、第三方Native SDK)中的错误引起。

  • 典型问题

    • Use-After-Free:Native对象(如OH_NativeXComponent或其回调函数)被提前释放,但后续代码仍尝试访问它。
    • 空指针解引用、栈溢出
  • 解决方案

    • 确保Native对象生命周期:应用必须保证OH_NativeXComponent_Callback等回调对象在组件的onSurfaceDestroy回调执行前一直有效。
    • 添加Native层崩溃捕获:注册信号处理函数,在崩溃时记录日志以便分析。
    • 谨慎调用Native API:调用前做好参数校验,确保指针有效性。

🔧 二、崩溃问题的通用诊断流程

  1. 获取崩溃日志

    • 方法一(推荐):使用DevEco Studio的 FaultLog 工具一键提取。连接设备后,在Logcat的FaultLog选项卡中查看详细的崩溃堆栈信息。
    • 方法二:通过hdc命令行工具抓取:hdc_std shell hilog -w | grep "CRASH"
  2. 分析日志关键信息

    • 堆栈跟踪(Stacktrace):这是定位问题的核心。在Debug模式下可直接跳转到出错代码行;Release模式需使用SourceMap文件反解混淆。
    • 崩溃类型(FAULT_TYPE)错误信息:直接指出是JS错误、Native错误还是超时等。
  3. 使用性能剖析工具

    • Memory Profiler:监控内存趋势,捕捉泄漏。
    • ArkUI Inspector:检查UI组件层级和属性,排查布局问题。

💡 三、预防性编码最佳实践

  • 启用全局异常拦截:在应用入口处设置全局错误监听,捕获未处理的异常并上报,避免应用直接闪退。
  • 代码规范:采用严格的TypeScript/ArkTS编码规范,开启所有静态检查选项。
  • 定期进行性能测试:在开发周期中,使用Profiler工具对关键路径进行性能分析和内存检查。

希望这份详细的指南能帮助您有效解决和预防鸿蒙应用开发中的UI崩溃问题!如果遇到具体的技术难题,查阅华为开发者联盟的官方文档通常是最可靠的途径。

WX20250512-113156@2x.png

鸿蒙6开发视频播放器的屏幕方向适配问题

2025年11月28日 15:20

大家好,我是 V 哥, 在鸿蒙6开发中,屏幕方向适配是提升用户体验的重要环节。下面我将通过一个完整的视频播放器示例,详细讲解ArkTS中横竖屏切换的实现方案。

联系V哥获取 鸿蒙学习资料

一、基础概念理解

1.1 屏幕方向类型

鸿蒙系统支持四种屏幕方向:

  • PORTRAIT(竖屏):屏幕高度大于宽度
  • LANDSCAPE(横屏):屏幕宽度大于高度
  • PORTRAIT_INVERTED(反向竖屏)
  • LANDSCAPE_INVERTED(反向横屏)

1.2 适配策略

  • 静态配置:通过配置文件锁定基础方向
  • 动态调整:运行时感知设备旋转并智能适配

二、静态配置实现

2.1 修改module.json5配置

src/main/module.json5文件中配置UIAbility的方向属性:

{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "orientation": "landscape", // 可选:portrait|landscape|unspecified
        "metadata": [
          {
            "name": "ohos.ability.orientation",
            "value": "$profile:orientation"
          }
        ]
      }
    ]
  }
}

参数说明

  • portrait:锁定竖屏
  • landscape:锁定横屏
  • unspecified:跟随系统(默认)

三、动态横竖屏切换实现

3.1 创建方向工具类

新建utils/OrientationUtil.ets文件:

// OrientationUtil.ets
import window from '@ohos.window';
import display from '@ohos.display';

export class OrientationUtil {
  // 设置窗口方向
  static async setPreferredOrientation(windowClass: window.Window, orientation: window.Orientation) {
    try {
      await windowClass.setPreferredOrientation(orientation);
      console.info('屏幕方向设置成功:', orientation);
    } catch (error) {
      console.error('设置屏幕方向失败:', error);
    }
  }

  // 获取当前设备方向
  static getCurrentOrientation(): string {
    const displayInfo = display.getDefaultDisplaySync();
    return displayInfo.width > displayInfo.height ? 'landscape' : 'portrait';
  }

  // 横屏模式配置
  static readonly LANDSCAPE: window.Orientation = window.Orientation.LANDSCAPE;
  
  // 竖屏模式配置  
  static readonly PORTRAIT: window.Orientation = window.Orientation.PORTRAIT;
  
  // 跟随传感器自动旋转(受旋转锁控制)
  static readonly FOLLOW_SENSOR: window.Orientation = window.Orientation.FOLLOW_SENSOR;
}

3.2 视频播放页面实现

创建pages/VideoPlayback.ets主页面:

// VideoPlayback.ets
import { OrientationUtil } from '../utils/OrientationUtil';
import window from '@ohos.window';
import common from '@ohos.app.ability.common';
import mediaquery from '@ohos.mediaquery';

@Entry
@Component
struct VideoPlayback {
  @State currentOrientation: string = 'portrait';
  @State isFullScreen: boolean = false;
  private context: common.UIContext = getContext(this) as common.UIContext;
  private windowClass: window.Window | null = null;
  private mediaQueryListener: mediaquery.MediaQueryListener | null = null;

  // 页面初始化
  aboutToAppear() {
    this.initWindow();
    this.setupOrientationListener();
  }

  // 初始化窗口
  async initWindow() {
    try {
      this.windowClass = await window.getLastWindow(this.context);
      AppStorage.setOrCreate('windowClass', this.windowClass);
      this.currentOrientation = OrientationUtil.getCurrentOrientation();
    } catch (error) {
      console.error('窗口初始化失败:', error);
    }
  }

  // 设置方向监听器
  setupOrientationListener() {
    // 监听窗口尺寸变化
    this.windowClass?.on('windowSizeChange', () => {
      this.currentOrientation = OrientationUtil.getCurrentOrientation();
      console.info('屏幕方向变化:', this.currentOrientation);
    });

    // 媒体查询监听横屏事件
    const mediaQuery = mediaquery.matchMediaSync('(orientation: landscape)');
    this.mediaQueryListener = mediaQuery;
    mediaQuery.on('change', (result: mediaquery.MediaQueryResult) => {
      if (result.matches) {
        console.info('当前为横屏模式');
      } else {
        console.info('当前为竖屏模式');
      }
    });
  }

  // 切换全屏/竖屏模式
  async toggleFullScreen() {
    if (!this.windowClass) return;

    if (this.isFullScreen) {
      // 退出全屏,切换回竖屏
      await OrientationUtil.setPreferredOrientation(this.windowClass, OrientationUtil.PORTRAIT);
      this.isFullScreen = false;
    } else {
      // 进入全屏,切换为横屏并跟随传感器
      await OrientationUtil.setPreferredOrientation(this.windowClass, OrientationUtil.FOLLOW_SENSOR);
      this.isFullScreen = true;
    }
  }

  // 锁定横屏(不受传感器影响)
  async lockLandscape() {
    if (this.windowClass) {
      await OrientationUtil.setPreferredOrientation(this.windowClass, OrientationUtil.LANDSCAPE);
    }
  }

  // 页面布局
  build() {
    Column() {
      // 标题栏
      Row() {
        Text('视频播放器')
          .fontSize(20)
          .fontColor(Color.White)
      }
      .width('100%')
      .height(60)
      .backgroundColor('#007DFF')
      .justifyContent(FlexAlign.Start)
      .padding({ left: 20 })

      // 视频播放区域
      Column() {
        if (this.currentOrientation === 'landscape') {
          this.LandscapeVideoContent()
        } else {
          this.PortraitVideoContent()
        }
      }
      .layoutWeight(1)

      // 控制按钮区域(竖屏时显示)
      if (this.currentOrientation === 'portrait') {
        Column() {
          Button(this.isFullScreen ? '退出全屏' : '进入全屏')
            .width('90%')
            .height(40)
            .backgroundColor('#007DFF')
            .fontColor(Color.White)
            .onClick(() => this.toggleFullScreen())

          Button('锁定横屏')
            .width('90%')
            .height(40)
            .margin({ top: 10 })
            .backgroundColor('#FF6A00')
            .fontColor(Color.White)
            .onClick(() => this.lockLandscape())
        }
        .width('100%')
        .padding(10)
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  // 横屏内容布局
  @Builder LandscapeVideoContent() {
    Column() {
      // 模拟视频播放器
      Stack() {
        Image($r('app.media.video_poster'))
          .width('100%')
          .height(300)
          .objectFit(ImageFit.Contain)

        // 横屏控制条
        Row() {
          Button('退出全屏')
            .width(100)
            .height(30)
            .backgroundColor(Color.Orange)
            .fontColor(Color.White)
            .onClick(() => this.toggleFullScreen())
        }
        .width('100%')
        .justifyContent(FlexAlign.End)
        .padding(10)
      }

      // 视频信息
      Text('当前模式:横屏全屏播放')
        .fontSize(16)
        .margin({ top: 20 })
    }
  }

  // 竖屏内容布局
  @Builder PortraitVideoContent() {
    Column() {
      // 模拟视频播放器
      Image($r('app.media.video_poster'))
        .width('100%')
        .height(200)
        .objectFit(ImageFit.Cover)

      // 视频信息
      Text('视频标题:鸿蒙开发教程')
        .fontSize(18)
        .margin({ top: 10 })

      Text('视频描述:学习ArkTS横竖屏适配')
        .fontSize(14)
        .margin({ top: 5 })
        .fontColor(Color.Gray)
    }
    .padding(10)
  }

  // 页面销毁
  aboutToDisappear() {
    this.mediaQueryListener?.off('change');
    this.windowClass?.off('windowSizeChange');
  }
}

3.3 EntryAbility配置

更新entryability/EntryAbility.ets

// EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
import hilog from '@ohos.hilog';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    hilog.info(0x0000, 'EntryAbility', 'Ability onCreate');
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    hilog.info(0x0000, 'EntryAbility', 'Ability onWindowStageCreate');

    // 设置窗口方向为跟随传感器
    windowStage.getMainWindow((err, windowClass) => {
      if (err) {
        hilog.error(0x0000, 'EntryAbility', 'Failed to get main window');
        return;
      }
      // 初始设置为竖屏,但允许跟随传感器旋转
      windowClass.setPreferredOrientation(window.Orientation.FOLLOW_SENSOR);
    });

    windowStage.loadContent('pages/VideoPlayback', (err, data) => {
      if (err) {
        hilog.error(0x0000, 'EntryAbility', 'Failed to load the content.');
      }
    });
  }
}

四、关键技术与原理分析

4.1 方向控制原理

  • setPreferredOrientation:核心方法,控制窗口显示方向
  • FOLLOW_SENSOR:跟随传感器自动旋转,受系统旋转锁控制
  • 媒体查询:监听屏幕方向变化事件

4.2 布局适配技巧

使用条件渲染和Flex布局实现响应式设计:

// 响应式布局示例
build() {
  Column() {
    if (this.currentOrientation === 'landscape') {
      // 横屏布局
      Row() {
        // 左右分栏
      }
    } else {
      // 竖屏布局  
      Column() {
        // 上下分栏
      }
    }
  }
}

五、完整项目结构

entry/src/main/ets/
├── entryability/EntryAbility.ets
├── pages/VideoPlayback.ets
├── utils/OrientationUtil.ets
└── resources/  // 资源文件

六、测试与调试要点

  1. 真机测试:在鸿蒙设备上测试旋转效果
  2. 旋转锁定:测试系统旋转开关的影响
  3. 折叠屏适配:考虑折叠态和展开态的不同场景

七、常见问题解决

问题1:旋转后布局错乱 解决:使用媒体查询监听方向变化,动态调整布局

问题2:旋转动画卡顿
解决:优化布局计算,避免复杂操作在旋转时执行

这个完整示例涵盖了鸿蒙6中ArkTS横竖屏适配的核心技术点,适合初学者逐步学习和实践。

卷二_副本2.jpg

鸿蒙6开发中,通过文本和字节数组生成码图案例

2025年11月28日 15:17

大家好,我是 V 哥。

生成码图在很多应用中都很常见,今天的内容来给大家介绍鸿蒙6.0开发中通过文本/字节数组生成码图的详细案例解析,结合典型业务场景和技术实现要点:

联系V哥获取 鸿蒙学习资料


一、文本生成码图案例:商品条码生成(EAN-13)

业务场景
零售应用中为商品生成标准条码,用于库存管理和POS扫码结算。
技术实现

// 导入模块
import { generateBarcode } from '@kit.ScanKit';
import { image } from '@kit.ImageKit';

@Entry
@Component
struct ProductBarcode {
  @State pixelMap: image.PixelMap | undefined = undefined

  build() {
    Column() {
      Button('生成商品条码')
        .onClick(() => {
          // EAN-13规范:12位数字,首位非0
          const content: string = "694251983457"; 
          const options: generateBarcode.CreateOptions = {
            type: generateBarcode.ScanType.EAN_13, // 指定条码类型
            width: 300,  // 宽度需在[200,4096]范围内
            height: 150, // 高度可不等于宽度(条码为矩形)
            margin: 2    // 边距范围[1,10]
          };
          // 生成并渲染
          generateBarcode.createBarcode(content, options)
            .then((pixelMap: image.PixelMap) => {
              this.pixelMap = pixelMap;
            })
            .catch((error: BusinessError) => {
              console.error(`生成失败: ${error.code}`);
            });
        })
      // 显示生成的条码
      if (this.pixelMap) {
        Image(this.pixelMap)
          .width(300)
          .height(150)
      }
    }
  }
}

关键约束

参数 要求
内容长度 12位数字
首位数字 禁止为0
码类型 ScanType.EAN_13
尺寸范围 width/height ∈ [200,4096]

二、字节数组生成码图案例:交通卡二维码 业务场景
地铁APP生成加密的交通卡二维码,闸机设备专用解码器识别。
技术实现

// 导入模块(需包含buffer)
import { generateBarcode } from '@kit.ScanKit';
import { buffer } from '@kit.ArkTS';

@Entry
@Component
struct TransportCard {
  @State qrCode: image.PixelMap | undefined = undefined

  build() {
    Column() {
      Button('生成交通卡二维码')
        .onClick(() => {
          // 1. 准备字节数组(加密数据)
          const hexData: string = "0177C10DD10F776860..."; // 16进制字符串
          const contentBuffer: ArrayBuffer = buffer.from(hexData, 'hex').buffer;
          
          // 2. 配置参数(仅支持QR Code)
          const options: generateBarcode.CreateOptions = {
            type: generateBarcode.ScanType.QR_CODE,
            width: 300,   // 需满足 width=height
            height: 300,  // 正方形二维码
            errorCorrectionLevel: generateBarcode.ErrorCorrectionLevel.LEVEL_Q // 25%纠错
          };
          
          // 3. 生成二维码
          generateBarcode.createBarcode(contentBuffer, options)
            .then((pixelMap: image.PixelMap) => {
              this.qrCode = pixelMap;
            })
            .catch((error: BusinessError) => {
              console.error(`生成失败: ${error.code}`);
            });
        })
      // 显示二维码
      if (this.qrCode) {
        Image(this.qrCode)
          .width(300)
          .height(300)
      }
    }
  }
}

核心限制

参数 要求
数据类型 ArrayBuffer(字节数组)
唯一支持码类型 ScanType.QR_CODE
纠错等级与长度关系 LEVEL_Q ≤ 1536字节
尺寸要求 width必须等于height

三、技术对比与选型建议

维度 文本生成 字节数组生成
适用场景 明文字符(网址、ID等) 加密数据/二进制协议(如交通卡)
支持码类型 13种(含QR/EAN/Code128等) 仅QR Code
数据限制 按码类型限制长度(如QR≤512字符) 按纠错等级限制字节长度
颜色要求 建议黑码白底(对比度>70%) 同左
设备兼容性 全设备(Phone/Tablet/Wearable/TV) 同左

四、避坑指南

  1. 尺寸陷阱
    字节数组生成的二维码必须满足 width=height,否则抛出202(参数非法)错误。

  2. 纠错等级选择

    • LEVEL_L(15%纠错):数据≤2048字节 → 容错高/密度低
    • LEVEL_H(30%纠错):数据≤1024字节 → 容错低/密度高
      交通卡推荐LEVEL_Q(25%容错)
  3. 内容超长处理
    若文本超限(如Code39超80字节),需分段生成或改用QR Code。

  4. 渲染优化
    使用Image组件显示PixelMap时,添加背景色提升识别率:

   Image(this.pixelMap)
     .backgroundColor(Color.White) // 强制白底

通过合理选择生成方式并遵守参数规范,可满足零售、交通、支付等高可靠性场景需求,实际开发中建议参考华为官方示例工程验证设备兼容性。

HarmonyOS 6.0 蓝牙实现服务端和客户端通讯案例详解

2025年11月28日 15:15

大家好,我是 V 哥。 以下基于 HarmonyOS 6.0 的蓝牙 BLE 通讯案例详解,模拟心率监测场景,实现服务端(Peripheral)广播数据与客户端(Central)订阅数据的功能流程:

联系V哥获取 鸿蒙学习资料

关键步骤:

  1. 服务端(Peripheral):

    • 创建蓝牙服务(GATT Server)
    • 添加服务(Service)和特征(Characteristic)
    • 广播服务
    • 当客户端连接后,定期更新心率特征值并通过通知发送给客户端
  2. 客户端(Central):

    • 扫描BLE设备(按服务UUID过滤)
    • 连接目标设备
    • 发现服务及特征
    • 订阅特征通知
    • 接收特征值变化

以下是V哥整理的核心代码逻辑。

注意:由于HarmonyOS 6.0可能使用新的API包(如@ohos.bluetooth等),我们需要参考最新官方文档,但这里以搜索结果为基础,结合常见的BLE流程进行说明。


📡 一、服务端实现(广播心率数据) 1. 初始化蓝牙服务

import { bluetooth } from '@kit.ConnectivityKit';

// 定义服务UUID和特征值(需与客户端匹配)
const SERVICE_UUID = '0000180D-0000-1000-8000-00805F9B34FB'; // 标准心率服务UUID
const CHARACTERISTIC_UUID = '00002A37-0000-1000-8000-00805F9B34FB'; // 心率测量特征

// 创建GATT服务
let gattServer: bluetooth.GattServer = bluetooth.createGattServer();
let service: bluetooth.GattService = {
  serviceUuid: SERVICE_UUID,
  isPrimary: true,
  characteristics: [{
    characteristicUuid: CHARACTERISTIC_UUID,
    permissions: bluetooth.CharacteristicPermission.READ,
    properties: bluetooth.CharacteristicProperty.NOTIFY
  }]
};
gattServer.addService(service);

2. 开启广播并发送数据

// 启动BLE广播
let advertiseSetting: bluetooth.AdvertiseSetting = {
  interval: 320, // 广播间隔(单位0.625ms)
  txPower: 0,    // 发射功率
  connectable: true
};
gattServer.startAdvertising(advertiseSetting, {
  serviceUuids: [SERVICE_UUID] // 广播的服务标识
});

// 模拟心率数据发送(定时更新)
setInterval(() => {
  const heartRate = Math.floor(Math.random() * 40) + 60; // 生成60~100随机心率值
  const data = new Uint8Array([0x06, heartRate]); // 数据格式:Flags(06) + 心率值

  // 通知已连接的客户端
  gattServer.notifyCharacteristicChanged({
    serviceUuid: SERVICE_UUID,
    characteristicUuid: CHARACTERISTIC_UUID,
    deviceId: connectedDeviceId, // 连接的设备ID
    value: data.buffer            // ArrayBuffer格式数据
  });
}, 2000); // 每2秒发送一次

3. 处理客户端连接事件

gattServer.on('connectionStateChange', (device: bluetooth.Device, state: number) => {
  if (state === bluetooth.ProfileConnectionState.STATE_CONNECTED) {
    console.log(`设备已连接: ${device.deviceId}`);
    connectedDeviceId = device.deviceId; // 保存连接的设备ID
  } else if (state === bluetooth.ProfileConnectionState.STATE_DISCONNECTED) {
    console.log('设备已断开');
  }
});

📱 二、客户端实现(订阅心率数据)

1. 扫描并连接服务端

import { bluetooth } from '@kit.ConnectivityKit';

// 扫描指定服务的设备
let scanner: bluetooth.BLEScanner = bluetooth.createBLEScanner();
scroller.startScan({
  serviceUuids: [SERVICE_UUID] // 过滤目标服务
});

// 发现设备回调
scanner.on('deviceDiscover', (device: bluetooth.ScanResult) => {
  if (device.deviceName === "HeartRate_Server") { // 根据设备名过滤
    const gattClient: bluetooth.GattClientDevice = bluetooth.createGattClientDevice(device.deviceId);
    gattClient.connect(); // 连接服务端
  }
});

2. 订阅特征值通知

// 连接成功后订阅数据
gattClient.on('servicesDiscovered', () => {
  const service = gattClient.getService(SERVICE_UUID);
  const characteristic = service.getCharacteristic(CHARACTERISTIC_UUID);

  // 启用特征值通知
  characteristic.setCharacteristicChangeNotification(true).then(() => {
    characteristic.on('characteristicChange', (value: ArrayBuffer) => {
      const heartRate = new Uint8Array(value); // 解析心率值
      console.log(`实时心率: ${heartRate} BPM`);
    });
  });
});

3. 断开连接处理

gattClient.on('connectionStateChange', (state: number) => {
  if (state === bluetooth.ProfileConnectionState.STATE_DISCONNECTED) {
    console.log('已断开服务端连接');
    scanner.stopScan(); // 停止扫描
  }
});

🔑 三、关键知识点

  1. UUID 规范
    • 使用标准 UUID(如心率服务 0x180D)确保跨设备兼容性。
  2. 数据广播
    • 服务端通过 notifyCharacteristicChanged() 主动推送数据,客户端无需轮询。
  3. 权限配置
    • 需在 module.json5 中声明蓝牙权限:
     "requestPermissions": [{
       "name": "ohos.permission.USE_BLUETOOTH"
     }]
  1. 双机调试
    • 需两台 HarmonyOS 设备(或模拟器)分别运行服务端/客户端。

⚠️ 四、常见问题

  1. 连接失败
    • 检查设备是否开启蓝牙可见性,并确认 SERVICE_UUID 完全匹配。
  2. 收不到通知
    • 客户端需先调用 setCharacteristicChangeNotification(true) 订阅通知。
  3. 广播功耗优化
    • 调整 AdvertiseSetting.interval 可平衡广播频率与功耗。
❌
❌