一、编译时报错"找不到符号"
1.1 NDK工程编译时找不到符号
问题现象
NDK工程使用CMake编译时,报链接错误,找不到符号:
typescript
ld.lld: error: undefined symbol:XXX
背景知识
在项目中引入C++源码构建so并调用,参考使用命令行CMake构建NDK工程。在项目中引用其他使用HarmonyOS工具链编译好的so,请参考在NDK工程中使用预构建库。使用其他工具构建HarmonyOS可用的so,参考通过lycium工具快速编译三方库。
问题定位
场景一:未找到的符号是项目工程里的符号
- 可能的原因是编译时只依赖了对应的头文件,没有将对应的实现编译进so导致
- 可按如下步骤排查:使用命令行CMake构建NDK工程
- 在IDE工程里通过'Find in Files'(默认快捷键CTRL+SHIFT+F)查找工程是否包含了该符号对应的C/CPP文件
- 在工程编译的CMakeLists.txt里查看,编译时依赖的源码文件是否加入了该文件
- 可能是编译缓存或者构建文件损坏导致的
场景二:未找到的符号是预构建库中的符号
- 按如下步骤排查:
- 参考引入预构建库的指导文档,检查预构建so是否正确引入工程并在NDK的CMakeLists.txt中被依赖
- 通过file命令查看预构建库的ABI信息,是否为软链接文件,是否为HarmonyOS工程支持的arm64版本
vbnet
arm64-v8a/lib# file libxxx.so
libxxx.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[sha1]=xxxxxxxxx, not stripped
- 对预构建的so,使用nm命令查看未找到的符号,是否包含在so导出的符号中(strip的库无法查看符号)
arduino
# nm libxxx.so
0000000000000270 r abitag
000000000000aa78 t add_format_xxx
000000000000ab98 t add_sheet_xxx
000000000000ab1c t add_xf_xxx
- 通过objdump或者readelf命令,查看预构建so是否还依赖了其他so库
yaml
# objdump -x libxxx.so | grep NEEDED
NEEDED libc.so
# readelf -d libxxx.so
Dynamic section at offset 0x15b48 contains 24 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so]
- 通过源码或者so符号表查看该符号是否是C语言符号,在引入C++工程时,有没有使用extern C声明
分析结论
场景一
- 使用HarmonyOS工具链构建so时,由于参数配置的原因,导致符号对应的文件没有编译或者导出,在so中找不到符号
- 编译缓存没有更新,或者构建文件损坏
场景二
- 依赖预构建库so,只引入了头文件,没有引入so及其依赖的so,或者so依赖路径不对,导致链接时找不到函数的实现
- 预构建so不是使用HarmonyOS工具链编译的,或者不是arm64版本的,在NDK工程不可用,无法解析其中的符号
- 对预构建的C语言so,在C++工程里引用头文件时,没有使用extern C声明,C++函数编译生成的符号和C不同,因此在对应的so中无法找到对应的实现
- 引入的头文件版本和预构建so的源码版本不一致,函数的实现和头文件不一致(如参数个数、类型等),导致从头文件中引用的符号找不到
修改建议
场景一
- 使用NDK构建工程时,参考官方示例和CMake语法手册,添加函数的实现文件到库编译脚本中
scss
file(GLOB CPP_FILES "./.cpp")
add_library(xxx SHARED
${CPP_FILES}
"./XX/xx.cpp"
)
- 通过IDE工具栏Build > Clean Project后,再次编译工程
场景二
- 使用预构建库,库以及其依赖的库,都需要使用HarmonyOS工具链构建arm64版本,编译参数可参考应用在其他平台使用的构建参数,并参考预构建库文档将so引入工程并添加依赖
- 使用系统C API时,也需要将对应的so添加到依赖中
- 需要注意的是,构建的三方so,其.so文件可能只是个软连接,需要将其链接的bin文件一并导入工程
scss
target_link_libraries(libxxx PUBLIC libace_napi.z.so libz.so)
- C++文件引用C语言的头文件时,需要用extern C声明包裹
arduino
extern "C" {
#include "xxx.h"
}
1.2 其他"找不到符号"问题
问题现象
在鸿蒙中遇到Cannot resolve symbol '$string:microphone_permission_reason'等类似错误。
解决方案
- 检查字符串资源定义 在
entry/src/main/resources/base/element/string.json
文件中,确保已定义相关字符串:
json
{
"string": [
{
"name": "microphone_permission_reason",
"value": "需要麦克风权限以实现语音录制功能"
}
]
}
- 确认资源路径和命名 确保资源文件放在正确的目录下,如
entry/src/main/resources/base/element/string.json
。 - 检查权限声明 确认权限的声明是否正确,如在
config.json
中添加麦克风权限:
json
"reqPermissions": [
{
"name": "ohos.permission.MICROPHONE"
}
]
- 清理IDE缓存 通过
File -> Invalidate Caches
清理缓存并重启IDE。 - 更新开发工具版本 确保使用最新稳定版的DevEco Studio和HarmonyOS SDK。
二、依赖冲突问题
2.1 多so相互依赖场景下的解耦
问题现象
A模块包含a.so,B模块包含b.so。a.so中有调用b.so的函数,b.so中也有调用a.so的函数。如果按照正常编译步骤,无论先编译哪个so,均会编译失败。
解决措施
通过dlopen和dlsym接口进行so编译依赖解耦,将隐式依赖转为显式依赖。
示例代码:
a.cpp
arduino
extern "C" {
#include "a.h"
#include <dlfcn.h>
#include "stdio.h"
typedef int (*FUNC_SUB)(int, int);
int add(int a, int b) {
return a + b;
}
int getb(char *path, int a, int b) {
// path:从ArkTS侧传递So文件的沙箱路径
void *handle = dlopen(path, RTLD_LAZY); // 打开一个动态链接库
if (!handle) {
return 0;
}
FUNC_SUB sub_func = (FUNC_SUB)dlsym(handle, "sub"); // 获取函数名为sub的函数
int res = sub_func(a, b); // 调用函数
dlclose(handle); // close动态链接库
return res;
}
}
a.h
arduino
extern "C" {
#ifndef DemoSO_a_H
#define DemoSO_a_H
int add(int a, int b);
int getb(char *path, int a, int b);
#endif // DemoSO_a_H
}
b.cpp
arduino
extern "C" {
#include "b.h"
#include <dlfcn.h>
#include "stdio.h"
typedef int (*FUNC_ADD)(int, int);
int sub(int a, int b) {
return a - b;
}
int geta(char *path, int a, int b) {
// path:从ArkTS侧传递So文件的沙箱路径
void *handle = dlopen(path, RTLD_LAZY); // 打开一个动态链接库
if (!handle) {
return 0;
}
FUNC_ADD add_func = (FUNC_ADD)dlsym(handle, "add"); // 获取函数名为add的函数
int res = add_func(a, b); // 调用函数
dlclose(handle); // close动态链接库
return res;
}
}
b.h
arduino
extern "C" {
#ifndef DemoSO_b_H
#define DemoSO_b_H
int sub(int a, int b);
int geta(char *path, int a, int b);
#endif // DemoSO_b_H
}
CMakeLists.txt
scss
cmake_minimum_required(VERSION 3.4.1)
project(DemoSO)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/include)
add_library(a SHARED a.cpp)
target_link_libraries(a PUBLIC libace_napi.z.so libhilog_ndk.z.so)
add_library(b SHARED b.cpp)
target_link_libraries(b PUBLIC libace_napi.z.so libhilog_ndk.z.so)
add_library(demoso SHARED hello.cpp)
target_link_libraries(demoso PUBLIC a b libace_napi.z.so)
2.2 依赖管理与冲突解决
2.2.1 依赖添加与同步
在HarmonyOS鸿蒙Next中,依赖的添加和重新同步主要通过oh-package.json5
文件进行管理。
添加依赖 :在oh-package.json5
文件的dependencies
属性中添加所需的依赖项
json
{
"dependencies": {
"example-library": "1.0.0"
}
}
重新同步依赖:在项目根目录下执行以下命令以重新同步依赖
ohpm install
更新依赖 :修改oh-package.json5
文件中的版本号,然后再次执行ohpm install
命令
删除依赖 :从oh-package.json5
文件的dependencies
属性中移除不需要的依赖项,然后执行ohpm install
命令以同步更改
2.2.2 多Module依赖关系
在HarmonyOS鸿蒙Next中,多Module的依赖关系通过oh-package.json5
文件管理。每个Module声明自身依赖的其他Module或第三方库。
本地模块依赖:可以通过配置本地文件夹路径或本地模块名来添加本地模块依赖
json
// 配置本地文件夹路径
"dependencies": {
"folder": "file:../folder"
}
// 配置本地模块名(从DevEco Studio 6.0.0 Beta1版本开始支持)
"dependencies": {
"moduleA": "@module:moduleA"
}
HAR/HSP包依赖:
json
// 引用HAR
"dependencies": {
"package": "file:../package.har"
}
// 引用HSP(在release模式下,构建HSP会生成tgz包)
"dependencies": {
"package": "file:../package.tgz"
}
注意事项:
- HarmonyOS不支持传递依赖。如果A依赖B,B依赖C,A需要在
oh-package.json5
中显式写明对C的依赖关系 - 这种设计是为了避免幽灵依赖问题,确保依赖关系的可控性和稳定性
2.2.3 依赖冲突解决策略
当遇到依赖冲突时,可以采用以下策略解决:
- 版本统一 :在项目级
oh-package.json5
中使用overrides
字段统一指定依赖版本
json
{
"overrides": {
"moduleA": "1.2.0"
}
}
- 排除依赖:在依赖声明中排除冲突的子依赖
json
{
"dependencies": {
"moduleA": {
"version": "1.0.0",
"exclude": ["conflict-module"]
}
}
}
- 强制解析:在构建脚本中使用强制解析策略
json
{
"buildProfile": {
"buildOption": {
"resolveStrategy": {
"force": {
"moduleA": "1.2.0"
}
}
}
}
}
三、模拟器运行问题
3.1 模拟器启动失败
问题现象
启动模拟器失败,提示"Unable to start the emulator.",模拟器无法启动。
解决方案
- 清除模拟器数据:在Local Emulator的设备列表窗口,点击"Wipe User Data"清除模拟器数据,然后重新启动模拟器
- 重新下载模拟器镜像:在File > Settings > SDKs > HarmonyOS界面中,卸载并重新下载模拟器镜像文件"System-image"后,尝试重新启动模拟器
- 重新下载模拟器应用:在File > Settings > SDKs > HarmonyOS界面中,卸载并重新下载模拟器应用"EmulatorX86"后,尝试重新启动模拟器
- 检查系统资源:确保电脑有足够的内存和磁盘空间运行模拟器
- 检查虚拟化技术:确保已启用CPU虚拟化技术
- 更新显卡驱动:更新或回退显卡驱动到稳定版本
3.2 模拟器启动黑洞:Hyper-V与资源争夺
问题现象
点击模拟器后持续卡在启动页,或提示"未开启Hyper-V"。
快速定位
- 检查CPU虚拟化支持:任务管理器→性能→CPU→确认"虚拟化"状态为"已启用"
- 验证Hyper-V激活:Windows功能中勾选"Hyper-V"与"Windows虚拟机监控程序平台",重启生效
根治方案
- 强制重置模拟器:删除当前模拟器(Device Manager→Delete),重新创建时勾选"Force Cold Boot"
- 资源释放:关闭其他虚拟机进程(如VMware),避免内存抢占
- 驱动回退:如果最新版本的显卡驱动有问题,尝试将驱动回退到稳定版本
3.3 模拟器常见问题汇总
3.3.1 HAXM安装问题
在Intel CPU的Windows电脑下,如果提示"Unable to install HAXM",可能是因为Hyper-V功能未关闭导致的。解决方法是关闭Hyper-V功能并重启电脑。
3.3.2 用户数据或本地文件问题
可以通过Wipe User Data清理模拟器用户数据,或者Delete删除已创建的Local文件夹来尝试解决问题。
3.3.3 CPU虚拟化未开启
需要确保CPU虚拟化功能已开启。
3.3.4 显卡驱动或配置问题
如果遇到模拟器黑屏无响应或闪退的情况,可以尝试禁用一个显卡后重新打开模拟器。此外,如果是多显卡设备,可能存在兼容性问题。
3.3.5 磁盘空间不足
确保有足够的磁盘空间供模拟器运行。
3.3.6 Vulkan问题
如果模拟器大小显示为特定数值但无法启动,可能是Vulkan问题,替换vulkan-1.dll文件可能恢复正常。
3.3.7 内存完整性开关问题
对于Mac用户,点击内核隔离,将内存完整性的开关打开可能解决问题。
3.3.8 显卡异常或OpenGL版本过低
检查电脑与显卡的连接是否有异常,以及显卡驱动是否正确安装。
3.3.9 虚拟化特性不支持
警告信息表明,并非所有现代X86虚拟化特性都得到支持,这可能会导致在运行HarmonyOS时出现性能下降的问题。
3.3.10 系统资源占用高
关闭系统资源占用高的其他程序以使系统保持更好性能。
3.3.11 模拟器启动方式错误
如果模拟器一直卡在updating indexes,可能是因为工程太大或工程含有java一些文件导致的。
四、真机运行问题
4.1 真机连接调试无法识别
问题现象
调试运行时,安装HAP失败并提示"设备未找到或未连接";或DevEco Studio设备列表显示"No device"(未识别设备)。
可能原因
- 设备未开启"开发者选项"开关
- 设备系统与DevEco Studio版本不匹配
- 使用的USB连接线为充电线而非数据线
- 当前的USB数据口损坏
- hdc工具的进程或设备异常
- 链接的设备不在支持调试的设备列表中
解决措施
- 开启开发者选项和USB调试 在设备上打开"开发者选项"开关,打开"USB调试"开关或"无线调试"开关。
- 确认版本匹配 务必确认版本的配套关系是否与当前所使用的开发套件是一致的,可参考版本概览使用对应的配套版本。
- 检查USB数据线和接口 请更换为符合USB2.0标准的数据线;建议直接连接,不要使用拓展坞。请更换USB数据口后重新尝试,并检查端口驱动是否正常。
- 重启ADB服务 执行如下命令,结束hdc进程,然后重新连接
bash
hdc kill
如果按上一个步骤操作后仍无法连接,请重启设备,尝试重新连接。
- 其他解决方案
- 重启设备,连接USB,开启USB调试
- 确保连接调试的设备在支持列表中
- 检查ADB设备授权
- 更新或重装Deveco Studio
- 使用无线调试(备用方案)
- 验证HarmonyOS兼容性
- 联系华为技术支持
4.2 特定芯片设备调试问题
问题现象
例如:使用高通芯片的设备无法进行真机调试,而使用麒麟芯片的设备可以正常调试。
解决方案
- 确认USB调试设置 确保已正确开启"开发者选项"和"USB调试"开关。
- 安装高通芯片专用驱动 针对高通芯片设备,需要安装专用的驱动程序。
- 重启ADB服务
bash
hdc kill
hdc start-server
- 检查ADB设备授权 确保设备已授权当前计算机进行调试。
- 更新或重装Deveco Studio 确保使用最新版本的开发工具。
- 使用无线调试(备用方案) 通过无线方式连接设备进行调试。
- 验证HarmonyOS兼容性
- 访问华为开发者论坛或文档,查询HarmonyOS版本对骁龙芯片的调试支持情况
- 确认项目配置中
compileSdkVersion
和targetSdkVersion
与设备系统版本匹配
- 联系华为技术支持 通过华为开发者支持提交问题,提供设备型号、系统版本及错误日志。
五、编译构建优化与问题排查
5.1 编译构建常见问题
5.1.1 依赖库下载失败
问题现象:提示"Install js dependencies failed"或"Gradle sync failed"等错误。
解决方案:
- 清理缓存 执行
File -> Invalidate Caches
,删除~/.gradle/caches
目录 - 仓库重置 修改
build.gradle
的maven仓库地址为华为镜像源 - 版本对齐 检查JDK版本(推荐JDK 11+)与HarmonyOS SDK路径配置
5.2 编译配置优化
5.2.1 构建配置优化
在hvigor-config.json5
中properties
下新增ohos.arkCompile.noEmitJs
字段,用于指定ArkTS编译过程中是否生成js中间产物,不生成js中间产物可以降低编译过程的峰值内存,加快编译速度
json
{
"properties": {
"ohos.arkCompile.noEmitJs": true
}
}
5.2.2 多环境依赖处理
使用hvigor的getOverrides
、setOverrides
和setProperty
能力,可以统一在项目根目录下的hvigor.ts
中直接判断不同的环境进行依赖管理
javascript
// hvigor.ts
export default {
overrides: (context) => {
const env = context.getProperty('env');
if (env === 'production') {
return {
'moduleA': '2.0.0'
};
} else {
return {
'moduleA': '1.0.0'
};
}
}
}
六、实用工具与资源
6.1 AI辅助开发工具
CodeGenie是华为推出的AI智能辅助开发助手,深度集成在DevEco Studio中,提供鸿蒙知识智能问答、鸿蒙ArkTS代码补全/生成和万能卡片生成等功能。
主要功能:
- 鸿蒙知识智能问答
- 代码补全与生成
- 代码解释功能
- 万能卡片生成
使用方法: 在DevEco Studio右侧边栏点击"CodeGenie"或输入快捷键"Alt/Option+U"进入CodeGenie。
6.2 调试工具
6.2.1 HDC工具
HDC(HarmonyOS Device Connector)是HarmonyOS应用开发中用于设备连接、调试的命令行工具。
常用命令:
perl
# 查看已连接设备
hdc list targets
# 安装应用
hdc install app.hap
# 卸载应用
hdc uninstall com.example.app
# 启动应用
hdc shell am start -n com.example.app/.MainAbility
# 查看应用日志
hdc shell hilog | grep com.example.app
# 传输文件
hdc file send local_file remote_path
# 进入设备shell
hdc shell
6.2.2 DevEco Profiler
DevEco Profiler是HarmonyOS应用性能分析工具,可用于分析应用的启动时间、内存使用、CPU占用等性能指标。
主要功能:
- 启动时间分析
- 内存使用分析
- CPU占用分析
- 网络请求分析
- 功耗分析
七、案例分析与最佳实践
7.1 "找不到符号"案例
案例背景
在NDK工程中使用CMake编译时,提示找不到某个符号,该符号位于项目中的C++文件中。
问题定位
通过检查发现,该C++文件没有被添加到CMakeLists.txt的编译列表中,导致编译时无法找到对应的符号实现。
解决方案
修改CMakeLists.txt文件,添加该C++文件到编译列表中:
css
add_library(native-lib SHARED
src/main/cpp/native-lib.cpp
src/main/cpp/missing-file.cpp # 添加缺失的文件
)
7.2 依赖冲突案例
案例背景
项目中同时依赖了两个不同版本的相同库,导致编译冲突。
解决方案
在项目级oh-package.json5
文件中使用overrides
字段统一指定依赖版本:
json
{
"overrides": {
"library-name": "2.0.0"
}
}
7.3 模拟器启动失败案例
案例背景
模拟器启动时卡在开机界面,无法正常启动。
问题定位
查看启动日志,发现是由于显卡驱动版本过高导致的兼容性问题。
解决方案
将显卡驱动回退到稳定版本,重启电脑后问题解决。
7.4 真机调试识别问题
案例背景
使用高通芯片的设备无法被DevEco Studio识别。
解决方案
安装高通芯片专用驱动,并重启ADB服务:
bash
hdc kill
hdc start-server
八、总结与注意事项
8.1 编译问题注意事项
-
符号找不到问题
- 确保所有源文件都已添加到编译配置中
- 检查头文件引用和extern "C"声明
- 清理编译缓存并重新编译
-
依赖冲突问题
- 使用ohpm管理依赖
- 明确声明所有依赖,包括间接依赖
- 使用overrides字段统一依赖版本
8.2 运行调试注意事项
-
模拟器问题
- 确保启用CPU虚拟化技术
- 分配足够的内存和磁盘空间
- 保持显卡驱动版本稳定
-
真机调试问题
- 使用原装数据线
- 安装对应芯片的驱动程序
- 检查开发者选项和USB调试设置
8.3 最佳实践
-
项目配置管理
- 使用版本控制工具管理项目配置文件
- 定期更新DevEco Studio和SDK到最新稳定版本
- 保持依赖版本一致,避免版本冲突
-
问题排查流程
- 查看详细日志信息
- 逐步排查可能的原因
- 记录问题解决过程,形成知识库
-
社区资源利用
- 积极参与华为开发者论坛讨论
- 关注官方文档和更新日志
- 参加鸿蒙开发者培训和活动
通过遵循上述解决方案和最佳实践,可以有效解决鸿蒙开发中常见的编译和运行问题,提高开发效率,确保应用的稳定运行。