OpenHarmony 回调函数使用心得:从接口声明到异步结果处理
在 OpenHarmony 的应用开发和系统适配过程中,回调函数是一种非常常见的编程方式。它可以将“事件发生之后要执行什么操作”交给调用方定义,从而降低模块之间的耦合度。
通过对 OpenHarmony 地图模块中 POI 搜索、网络适配和渲染流程的分析,我对回调函数的理解更加具体。回调函数并不只是“把一个函数传给另一个函数”,它实际上包含了接口设计、函数实现、回调注册、事件触发和结果处理等完整流程。
一、什么是回调函数
普通函数调用通常是由调用方主动执行:
result = Calculate();
调用方明确知道什么时候执行 Calculate(),并等待函数返回结果。
回调函数则不同。调用方先把一个函数传递给另一个模块,由另一个模块在特定事件发生时调用它:
RegisterCallback(OnResult);
后续当结果到达时,底层模块执行:
OnResult(result);
也就是说:
调用方提供函数
被调用方保存函数
事件发生时被调用方执行函数
这种方式的核心是控制反转:函数的实现由上层提供,但执行时机由底层模块决定。
二、回调函数涉及的几个角色
一个完整的回调流程通常包含以下角色。
1. 接口声明方
接口声明方负责定义回调函数的类型和参数,例如:
typedef void (*OnResultCallback)(const char *data, int32_t length);
在 OpenHarmony 的系统组件或 SDK 中,通常由底层框架或 SDK 提供接口声明。
声明方需要明确:
- 回调函数的参数类型
- 返回值类型
- 回调触发时机
- 回调是否允许为空
- 回调运行在哪个线程
- 回调参数的生命周期
- 回调结束后由谁释放资源
因此,声明方通常也是接口规范的设计方,但“声明方”和“实现方”并不一定是同一方。
2. 回调实现方
上层应用或适配层根据接口声明实现具体函数:
void OnResult(const char *data, int32_t length)
{
if (data == nullptr || length <= 0) {
return;
}
ProcessResult(data, length);
}
实现方只需要关注业务逻辑,不需要关心底层网络、线程或硬件如何产生结果。
3. 回调注册方
实现函数之后,需要将函数地址传给底层模块:
RegisterCallback(OnResult);
如果回调是结构体形式,可能需要这样注册:
HttpCallback callback = {
.onSuccess = OnSuccess,
.onFail = OnFail,
};
然后将 callback 传给 SDK。
4. 回调触发方
底层模块在事件发生时执行回调:
callback->onSuccess(response);
触发方通常是网络模块、文件模块、图形模块、设备驱动或系统服务。
三、OpenHarmony 中常见的回调形式
OpenHarmony 代码中常见的回调形式主要有三种。
1. 函数指针回调
typedef void (*Callback)(int32_t result);
优点是简单直接,适合底层 C 接口和轻量级系统组件。
缺点是表达能力有限,需要额外传递上下文数据。
2. 回调结构体
typedef struct {
void (*onSuccess)(const Response *response);
void (*onFail)(int32_t errorCode);
void (*onCancel)(void);
} Callback;
这种方式可以将多个生命周期事件组织在一起,适合网络请求、任务执行和资源加载等场景。
3. C++ 虚函数接口
class IListener {
public:
virtual void OnResult(int32_t result) = 0;
};
实现类继承接口并重写虚函数:
class ResultListener : public IListener {
public:
void OnResult(int32_t result) override
{
ProcessResult(result);
}
};
这种方式更适合 C++ 模块和复杂业务,但会增加对象生命周期管理的要求。
四、地图模块中的回调设计
在当前 OpenHarmony 地图模块中,SDK 定义了网络响应回调结构:
struct _awk_http_response_callback_t {
void (*on_receive_header)(...);
void (*on_receive_body)(...);
void (*on_fail)(...);
void (*on_success)(...);
void (*on_canceled)(...);
};
定义位置:
ohos_launcher_map/map_engine/sdk/include/awk_adapter.h
从接口设计上看,SDK 支持以下事件:
收到响应头
收到响应体
请求失败
请求成功
请求取消
如果使用这套回调,调用方需要实现相应函数,并将回调结构传递给网络模块:
awk_http_response_callback_t callback = {
.on_receive_body = OnReceiveBody,
.on_success = OnSuccess,
.on_fail = OnFail,
};
然后调用:
awk_aos_network_adapter_send_adapter(&request, &callback);
网络请求完成后,网络适配层负责触发对应回调。
五、当前同步 POI 搜索并没有使用结果回调
虽然 SDK 定义了 HTTP 回调接口,但当前 POI 搜索流程传入的是空指针:
awk_http_response_callback_t *awk_cb = NULL;
awk_aos_network_adapter_send_adapter(&request, awk_cb);
因此,当前 POI 搜索不会执行:
on_receive_body
on_success
on_fail
on_canceled
当前流程采用的是另一种方式:
发起请求
-> 网络接收回调保存 JSON
-> 发送请求完成事件
-> 当前线程阻塞等待事件
-> 读取 JSON
-> 解析 POI
-> 同步返回结果
对应的主要函数包括:
awk_poi_search.c:send_poi_around_url()
awk_system_adapter.c:http_data_recv_cb()
awk_system_adapter.c:get_aos_json()
awk_map_controller.cpp:AwkMapController::PoiSearchRequest()
这说明一个重要问题:
代码中存在回调接口,不代表当前业务流程一定使用了该回调接口。
阅读代码时,不能只看回调类型的声明,还要继续确认回调对象是否创建、是否注册以及传入的指针是否为空。
六、网络接收回调和事件通知的区别
当前 POI 流程中同时存在“回调”和“事件”。
网络模块收到数据时,会触发数据接收回调:
http_data_recv_cb()
这个回调负责:
接收数据
写入共享 JSON 缓冲区
更新数据长度
网络请求结束后,代码发送事件:
rt_event_send(http_evt, HTTP_RESPONSE_DONE);
同步调用线程则等待事件:
rt_event_recv(
http_evt,
HTTP_RESPONSE_DONE,
...);
二者职责不同:
机制 作用
网络接收回调 处理网络到达的数据
事件通知 告知等待线程请求已经完成
同步返回值 将最终解析结果返回给调用方
可以理解为:
回调负责“发生事情时处理事情”
事件负责“通知另一个线程事情已经发生”
返回值负责“把处理结果交给调用方”
七、同步调用中也可能存在回调
回调并不一定意味着整个接口是异步的。
当前 POI 搜索就是一个例子:
网络接收过程:异步回调
POI 搜索接口:同步阻塞
最终结果:同步返回
调用线程在 get_aos_json() 中等待网络完成事件:
调用线程
-> 发起请求
-> 阻塞等待
-> 被事件唤醒
-> 解析结果
-> 返回 mal_map_poi_t*
网络线程则负责:
网络线程
-> 接收数据
-> 保存 JSON
-> 发送完成事件
因此,“使用了回调”和“接口是异步接口”是两个不同概念。
八、回调函数使用中最重要的注意事项
1. 明确回调执行线程
回调可能在以下线程执行:
- 调用线程
- 网络线程
- 工作线程
- UI 线程
- SDK 内部任务线程
OpenHarmony 地图 SDK 的接口注释明确要求网络代理回调在主流程线程中执行。因此,设计回调时必须确认线程约束。
如果回调在非 UI 线程中执行,不能直接操作 UI 控件,应通过任务投递切换到 UI 线程。
2. 注意参数生命周期
回调参数可能只在回调函数执行期间有效:
void OnReceive(const char *data)
{
SaveData(data);
}
如果要在回调结束后继续使用,通常需要复制数据:
char *copy = Duplicate(data);
当前 POI 流程中,网络线程将数据复制到共享 JSON 缓冲区,就是为了避免直接依赖网络回调参数的生命周期。
3. 正确处理空回调
底层触发回调前,应判断回调指针:
if (callback != nullptr && callback->onSuccess != nullptr) {
callback->onSuccess(response);
}
调用方也要明确:
传入空指针是否表示不关心回调
传入空指针是否允许
空回调时结果通过什么方式获取
当前 POI 请求传入 NULL,表示不使用 HTTP 响应回调,而是采用事件和共享缓冲区获取结果。
4. 避免回调中执行耗时操作
回调通常运行在网络线程或系统工作线程中。如果在回调中执行大量解析、文件操作或同步等待,可能阻塞底层线程。
比较合理的方式是:
回调接收数据
-> 快速保存数据
-> 投递任务
-> 由业务线程执行复杂处理
5. 处理回调对象生命周期
如果底层会保存回调指针,回调对象必须在请求结束前保持有效:
Callback callback;
SendRequest(&callback);
如果 SendRequest() 是异步接口,而 callback 是栈对象,就可能在函数返回后失效。
因此需要确认 SDK 是:
- 立即复制回调结构体
- 保存回调指针
- 仅在当前函数调用期间使用
6. 避免重复释放
回调参数的内存所有权必须明确:
谁分配
谁使用
谁释放
什么时候释放
当前 POI 流程中:
网络层生成 JSON
get_aos_json() 返回复制后的 JSON
AwkMapController 解析 JSON
调用 awk_mem_free_adapter() 释放 JSON
应用层调用 PoiMemFree() 释放 POI 结果
不同层次的内存由对应层释放,不能混用释放函数。
九、如何阅读一个回调流程
阅读 OpenHarmony 代码中的回调时,可以按以下顺序查找:
第一步:查找回调类型声明
搜索:
typedef
(*callback)
on_success
on_fail
on_receive
先确认回调参数和返回值。
第二步:查找回调实现
搜索函数指针实际指向了哪些函数:
callback.on_success = xxx;
或者:
xxx(request, &callback);
第三步:查找回调注册位置
重点确认:
回调对象是否为空
回调结构体是否完整初始化
底层是否保存回调指针
第四步:查找触发位置
搜索:
callback->on_success(...)
callback->on_fail(...)
或者查找网络、事件、任务完成处理函数。
第五步:确认线程关系
需要明确:
谁发起调用
谁触发回调
回调在哪个线程执行
结果如何返回
第六步:确认生命周期
重点查看:
回调参数是否需要复制
回调对象何时释放
返回数据由谁释放
十、回调流程图
典型异步回调流程:
应用层
|
| 1. 实现回调函数
v
适配层
|
| 2. 注册回调
v
SDK
|
| 3. 发起请求
v
网络模块
|
| 4. 请求完成
v
SDK触发回调
|
| 5. 调用 on_success/on_fail
v
应用层处理结果
当前同步 POI 流程:
应用线程
|
| PoiSearchRequest()
v
MapControllerProxy
|
v
AwkMapController
|
| send_poi_around_url(..., NULL)
v
网络模块
|
| http_data_recv_cb()
v
共享 JSON 缓冲区
|
| HTTP_RESPONSE_DONE
v
get_aos_json()
|
v
JSON解析
|
v
返回 mal_map_poi_t*
十一、我的几点实践体会
1. 看到回调声明后,要继续追踪是否真正注册
仅仅看到:
on_success
on_fail
还不能说明业务使用了回调。必须确认调用时是否传入了有效回调对象。
2. 回调和同步返回可以同时存在
一个流程可以同时具有:
底层异步回调
上层同步等待
最终同步返回
不能简单地认为“使用回调就一定是异步接口”。
3. 事件和回调是不同层次的机制
回调通常用于执行处理逻辑,事件通常用于线程间通知。复杂系统中二者经常配合使用。
4. 接口设计时要明确错误处理方式
回调函数中的错误可以通过参数传递:
on_fail(errorCode);
同步接口则通常通过:
返回值
错误码
空指针
当前 POI 搜索失败时返回 nullptr,应用层通过判断返回值并进行重试。
5. 线程安全和内存生命周期比语法更重要
回调函数本身语法很简单,真正容易出问题的是:
- 回调在哪个线程执行
- 回调参数是否仍然有效
- 是否存在竞态
- 是否重复释放
- 回调对象是否提前销毁
这些问题决定了回调设计是否可靠。
十二、总结
回调函数的完整使用流程可以概括为:
接口声明方定义回调类型
-> 上层实现回调函数
-> 调用方注册函数地址
-> 底层模块保存回调
-> 特定事件发生
-> 底层触发回调
-> 上层处理结果
在 OpenHarmony 地图模块中,还可以看到另一种组合模式:
网络回调
+ 共享数据缓冲区
+ 事件通知
+ 同步返回值
学习回调函数时,不能只关注函数指针的写法,更应该关注完整的调用链、线程模型和资源生命周期。只有弄清楚“谁定义、谁实现、谁注册、谁触发、谁释放”,才能真正理解 OpenHarmony 系统中的回调机制。
更多推荐

所有评论(0)