不知不觉,向鸿蒙的迁移工作从调研开始算已经半年多了,每次总觉得提测没问题了,就会又测出新的问题
不过这次应该可能……但愿真的没问题了,故趁着提测的空档,梳理这一篇文章作为记录,内容仅供参考
我们原本是运行在 Linux 中的 C++&Qt 项目,由一个用户界面和多个底层服务构成。
公司里所有人包括笔者在内都没有做过移动端开发,为什么明明是 PC 却说这是移动端开发呢,详见正文
什么是鸿蒙PC
鸿蒙系统实际上只有一种,在鸿蒙系统上又衍生出了对手机、电视、手表、PC/二合一设备的支持
所以非平台强相关的鸿蒙应用,理论上可以通过简单适配,运行在任意鸿蒙设备中,初衷是好的
但同时也因此造成了常规Windows\Linux桌面端应用难以进行迁移适配的窘境
例如缺少工业设计相关、图像处理相关、编程开发相关、桌面端游戏等大量生态
我们也就看到了鸿蒙电脑原生运行王者而不是英雄联盟的画面
因为像桌面游戏这种大多只有X64版本,它们除了下面这些工作外还要涉及跨架构适配工作
开发工具有哪些
目前针对鸿蒙开发主要有两套开发工具,一套是DevEco Studio,属于集成式IDE,但其只能运行在Windows/macOS
如果你已经习惯了使用命令行,不想把Linux代码拷贝至上述两个陌生平台,那么就只能用另一套,也就是Command Line Tools了
但使用Command Line Tools有个缺点,它出包需要有完整的项目配置文件,但它似乎并不支持初始化一个空项目作为模板
所以你还是需要先安装DevEco Studio然后用其生成一个空项目配置文件目录,拷贝到Linux中作为模板,改造后才能使用……
生态差异及需要进行的工作
由于鸿蒙PC与传统桌面的巨大差异,导致对已有的传统桌面端项目来说,迁移并不是简单换个编译器、处理几个编译错误就可行的,原有的进程架构、权限管理、文件访问以及系统接口调用等,都可能需要做差异处理。
下面结合本次项目迁移过程中遇到的问题,整理一下主要的生态差异以及对应的适配工作。
由于不同项目的架构和业务差别很大,笔者仅仅摘取出一部分认为有通用性的场景进行阐述。
具体仍需结合自身项目取舍。
编译工具链及系统接口
传统 Linux 应用我们通常使用 GCC 作为编译器,且经常依赖 glibc 提供的 GNU 扩展接口。而鸿蒙 SDK 使用的是基于 Clang/LLVM 的编译工具链和 musl libc。这就导致需要尽可能将代码调整为标准 C/C++ 和鸿蒙支持的 POSIX 接口。对于无法直接替换的 GNU 扩展接口,则需要寻找替代方案,或单独实现平台适配层。
应用架构及进程模型
以往开发 Linux 桌面应用时,我们常会按照进程划分人机交互界面和底层服务,达到上下层分离的目的,必要时还可以使用 system、popen、execve 等接口。
而鸿蒙普通应用受到应用模型和沙箱机制的约束,不允许开发者像传统 Linux 桌面程序一样自由创建和管理子进程。
对于此迁移,只能是将原有的多进程服务改造为以多线程和动态库为主的架构,将原有的可执行程序进行库化、线程化,再导出相应的启停接口,对于调用外部程序的代码,则引入外部程序对应的库或源代码进行替换。
不过这种改造也会引入一些其它问题。比如原本每个进程都会分别初始化日志等公共模块,变成多线程后,这些模块就处于同一个进程,很容易出现多重初始化的问题。以项目中使用的 glog 为例,就需要保证多个业务主线程启动时,日志系统在进程内只会被初始化一次。
同样的,原本依赖进程隔离的全局变量、单例对象、信号处理以及退出清理逻辑,也得重新检查检查。
ArkTS 与 C++ 混合开发
传统桌面软件通常可以完全使用 C/C++ 实现,但在鸿蒙中,其本地 C/C++ 库并没有提供完整的系统能力,需要通过 ArkTS 代码层去实现。
为了应对上述状况以及为了代码的最小化改动,就需要一个桥接层(库):
一部分业务直接在桥接库中调用鸿蒙提供的 C/C++ 接口实现
另一部分则穿透桥接库,调用 ArkTS 代码实现
所以,若涉及跨语言调用,这又会引入额外的处理数据转换、线程调度、异步回调以及对象生命周期等问题。
权限体系及沙箱化
以往在 Linux 桌面开发中我们往往需要考虑 root 权限问题,对于需要高权限的操作,通常会将其交由底层高权限服务处理。
而鸿蒙有自己的一套应用权限体系。应用需要按照系统提供的权限机制申请和使用相应的能力,不再需要关注 root 权限。
和移动端开发非常相似的一点是鸿蒙应用运行在各自的沙箱环境中。这虽然提高了系统安全性,但对于习惯了 Linux 开发的人来说,要麻烦不少。
例如,原有项目经常会通过自身安装目录定位配置文件和数据库,使用 /tmp 作为下载缓存目录,通过 /etc 获取系统信息,或者根据 $HOME 环境变量推导桌面路径进行数据导出。这些方式在鸿蒙中都需要重新检查适配。
于是,像前面说的程序安装目录、配置文件、缓存和数据库等,都应改为使用鸿蒙提供的应用路径及数据目录;对于用户主动导出文件等场景,则需要结合系统提供的文件访问能力实现。
另外,在不使用 DevEco Studio 的情况下,沙箱也让调试、查看应用内部文件和日志变得烦琐。
日志系统及问题定位
常规的 Linux 桌面开发中,我们通常会引入第三方日志库,例如 Google 的 glog。鸿蒙则提供了自己的 hilog 日志系统及相应的 C/C++ 接口。对于现有项目而言,直接替换全部日志接口显然会带来很大的工作量,因此可以先检查原有日志库是否支持重定向,欣慰的是 glog 虽然比较老,毕竟大厂出品,对日志重定向还是支持的,这就节省了很大一部分工作量。
但即使解决了日志输出问题,也还有一个问题,鸿蒙的 hilog 日志量非常庞大:
如果过滤条件设置得太宽,日志很快就会膨胀到难以分析
反之,过滤条件如果设置太严格,又可能丢失业务代码与系统交互过程中产生的异常
笔者曾经为了定位一个不知道在什么具体时间出现的偶现 BUG,5 个多小时,本地的 hilog 全量日志就已经达到 1.2GB。随后经过足足 20 多次裁切、 grep -v 剔除,才将日志缩减到 33MB(24 万 3 千行),最后又通过关键字逐一筛选,终于在第 15 万 1 千行附近定位到了与系统交互有关的异常日志……
微信图片_20260920192142_69_81
调试工具及开发环境
对于习惯了 Linux 的开发者来说,gdb、strace、lsof 等工具几乎是日常排查问题的标配。
但鸿蒙 PC 并不像普通 Linux 发行版那样提供一套完整的通用命令行调试环境。
即使能够找到,也可能会因为应用沙箱、权限限制而无法直接使用。
因此,本次移植过程中,笔者单独准备了一台安装 DevEco Studio 的机器,通过无线调试连接鸿蒙 PC 设备。
DevEco Studio 提供的崩溃堆栈信息还是很丰富的,能够辅助判断问题出现在 ArkTS 层还是 C++ 层。
配合 hilog,可以解决掉大部分常规崩溃的定位问题。
UKey 及 USB 设备适配
对于普通桌面软件,这部分可能完全不需要考虑,但对于安全类软件而言,UKey 往往就绕不开了。
在传统 Linux 环境中,为了高度兼容性,往往我们只会使用 PKCS#11 方式与 UKey 交互,而鸿蒙PC目前仅仅支持 SKF兼容 方式。
所以要重新实现出 SKF 操作层,并在此基础上兼容鸿蒙,因为鸿蒙中的应用无法像传统 Linux 程序一样直接访问 USB 设备节点,使用过程中需要通过 ArkTS 层去获取设备fd,然后才能完成对 SKF 对象的初始化。
Linux 平台下的自动化出包
除了业务代码的适配,构建和发布流程也需要重新处理。
前面的开发工具小节已经提到过,本次项目主要在 Linux 环境下进行开发,而鸿蒙官方提供的开发工具和相关教程更多面向 Windows、macOS 环境。
因此,就需要结合 SDK 中携带的工具,以及 Windows、macOS 平台下的相关流程,摸索出一套适用于 Linux 的自动化出包方案。
其中包括利用ohpm、hvigorw、Java、hdc及其它 SDK 相关工具进行构建、签名、出包、推送,最后整合进自动化脚本。
开发自由度上的差异
传统 Linux 桌面开发的自由度非常高,实现某个功能通常可以找到很多种方案。
而鸿蒙更偏向于面向框架的开发,实现某个功能往往只有固定的几个接口。
这一点感受非常明显,或许移动端开发一直都是这样的吧……
关于Qt
对于界面从 Linux 平台的移植,有两个Qt版本可用:
一个是 GitCode社区 社区版本
另一个是 QT官方 版本 (需自行编译)
本人最终选用了 GitCode 的版本,原因是官方版本行为差异较大,很多行为与 Linux 系统中并不一致,虽然社区版本也有行为差异,但整体可接受。
关于 QT 与 ArkUI 画布的转接,GitCode 和官方的 Qt ,在画布转接上完完全全是两套不同的逻辑……,最后是配合 Ai 边猜边试实现的,记得一遍遍调整得(děi)有十几二十几次吧。
重点
后期测试过程中,发现一些场景下界面经常假死,经排查发现是由于QDialog界面的exec()调用引起,使用Ai进行深度分析后,得到在移动端开发中,不建议使用QDialog的exec()调用的结论,因为移动端开发中并非Qt原生管理画布而是桥接,存在异步时差,且QDialog的exec()底层机制属于事件循环嵌套,放大了由于异步导致状态不同步的概率,从而诱发界面假死,而我们所有的界面、子界面,几乎全部基于QDialog……
由于上述原因,叠加exec()的同步阻塞的特性(如下伪代码所示),导致笔者为此修改了很多处代码,将原本需要同步执行的逻辑修改为了异步open()调用。
void classX::slot_A()
{
……
func_B();
……
func_C();
……
}
void classX::func_B()
{
……
func_D();
……
func_E();
……
}
void classX::func_D()
{
// 如果exec更换为异步的open,func_F和func_G、func_H等虽然可以通过绑定Rejected的槽函数得以调用
// 但是,上层函数的func_E和func_C等都会被瞬间执行,从而打乱原有业务逻辑,必须修改为闭包……
// 实际代码中有些地方要更复杂,甚至存在exec嵌套……
QDialog tmp;
if(tmp.exec() == QDialog::Rejected)
func_F();
else
func_G();
……
func_H();
……
}
Ai工具
迁移期间,也曾经寄希望于Ai,但无论是gmini还是gpt,由于没有训练数据的支撑,它们也是依靠安卓、IOS的开发惯例去推断某些地方应该是怎么样的
很多时候需要将鸿蒙开发文档手动喂给Ai,有时候需要询问鸿蒙开发文档页面的Ai客服……
后续建议
如果团队人员充沛,建议使用 Ark UI+ TypeScript + DevEco Studio 进行原生开发,原因是无论官方还是社区的Qt,在目前(2026.09)都会有大大小小的兼容问题,如果项目不大,处理这些兼容问题的时间做本地化开发也足够了,可以把更多的精力放在业务层实现上。