给 OpenHarmony 轻量地图组件补充离线能力:一次公共接口演进实践
在参与 OpenHarmony 轻量地图组件共建时,我尝试为地图模块补充离线资源管理能力。最初看起来,这项工作只是增加查询、下载、暂停和删除接口,但真正开始梳理后,我发现难点并不在函数数量,而在于如何让新能力自然地进入原有框架,同时不破坏已有实现。
这次修改让我对轻量地图组件的分层、代理线程和公共接口兼容有了更具体的认识。
从现有调用链开始
轻量地图组件并不是应用直接调用某个地图 SDK,而是通过一条分层调用链隔离上层和具体实现:
应用
→ MapManager
→ MapControllerProxy
→ IMapController
→ MAL 提供方
→ 具体地图能力
MapManager 是应用侧入口;MapControllerProxy 负责把调用转到地图任务线程;IMapController 描述公共能力;MAL 提供方再把这些能力适配到不同实现。
这条调用链带来的直接好处,是上层不需要了解具体地图 SDK 的类型、线程要求和资源管理方式。相应地,新增接口也不能只照着某个 SDK 的函数抄一遍,而要先提炼公共组件真正需要的能力。
我做了哪些修改
这次离线能力主要涉及三个部分。
- 补充公共数据模型
离线资源不只是一个“下载文件”。它至少涉及区域标识、资源状态、版本、大小、进度和错误信息。
我先将这些信息整理为厂商中立的数据模型,并重点考虑了几个问题:
使用稳定标识区分资源,而不是依赖可能重复或变化的显示名称;
将“未安装、等待、下载中、暂停、已安装、失败”等状态明确区分;
用能力位表达不同提供方支持范围的差异;
明确回调数据的有效期和内存所有权;
对暂不支持的操作返回明确结果。
能力位很重要,因为不同地图实现对离线资源的支持程度可能完全不同。有的实现支持下载和删除,但不支持暂停;有的实现只能使用预置资源。公共层不应为了接口统一而伪造成功。
- 扩展控制器接口
公共控制器已经包含大量既有方法。新增离线接口时,我没有把它们插入原有虚函数中间,而是统一追加到末尾,并为可选能力提供默认实现。
默认实现返回“不支持”,这样旧的控制器实现即使暂时没有适配离线功能,也可以继续参与统一编译。这里需要特别注意:默认实现改善的是源码兼容性,虚表扩展后,公共头文件、框架库和提供方仍应使用同一版本重新编译。
这也是我在修改前容易忽略的一点:代码“能够编译”不等于已有二进制可以随意混用。
- 在代理层完成线程转发
地图提供方通常要求接口在固定地图线程中执行。如果让应用自行切线程,每个调用点都要重复处理任务投递、等待和错误返回。
因此,新增接口仍沿用 MapControllerProxy 的现有模式:代理层接收调用,将任务投递到地图任务线程,再调用真实控制器。这样既保持了线程模型一致,也让后续实现方不必处理来自任意线程的并发调用。
为什么没有直接实现所有下层能力
公共接口设计和具体 SDK 适配是两个阶段。
第一阶段先把能力边界、状态、错误和线程契约定义清楚;第二阶段再由各地图提供方声明实际能力并完成映射。如果当前提供方没有相应能力,就返回“不支持”。
这种做法更适合开源组件:
公共接口不会被某个 SDK 的私有概念绑住;
不同提供方可以逐步接入;
上层能够根据能力查询决定是否展示入口;
尚未实现的功能不会以假成功掩盖问题。
这次修改中最需要注意的兼容问题
虚接口只能以兼容方式追加
已发布的虚函数不能随意重排、改成纯虚或改变参数语义。新增可选接口应追加在末尾并提供安全默认行为。
枚举和结构体也是公共契约
枚举值一旦对外使用,就不应重新排序。结构体也不能随意在中间插入字段,否则可能改变布局。需要长期扩展的数据结构,可以增加版本或结构体大小字段。
回调必须说明线程与生命周期
下载进度可能来自工作线程,但应用往往只能在 UI 线程更新界面。公共接口至少要说明回调在哪个线程发生、反初始化后是否还会回调,以及回调参数能否保存。
“请求已提交”不等于“任务已完成”
异步下载接口的同步返回值通常只能表示请求是否被接受。下载成功、失败、暂停或取消属于后续终态,不能混成一个布尔结果。
我对轻量地图框架的新理解
完成这次修改后,我更倾向于把公共地图组件看成一组稳定契约,而不是一套对具体 SDK 的简单封装。
MapManager 负责提供统一入口,代理层负责线程约束,公共控制器负责描述能力,MAL 负责消化实现差异。只有每一层守住自己的职责,新能力才能在不打乱原有结构的前提下逐步增加。
对开源共建来说,接口设计最重要的并不是覆盖尽可能多的函数,而是让支持范围、失败方式、线程和所有权都能够被其他开发者准确理解。
总结
这次离线地图接口演进主要完成了三件事:建立厂商中立的数据模型、以兼容方式扩展公共控制器、沿用代理层完成地图线程转发。
它也让我认识到,轻量组件的接口改动虽然代码量不一定大,但需要同时考虑分层、ABI、线程和生命周期。把这些边界先定义清楚,后续接入不同地图实现时才不会反复修改公共层。
更多推荐
所有评论(0)