鸿蒙地图上手指南——用MapKit在你的App里放一张地图
鸿蒙地图上手指南——用 Map Kit 在你的 App 里放一张地图
地图这个东西,在手机 App 里太常见了。你打开外卖 App 看到附近商家、打开出行 App 看到路线导航、打开社交 App 看到附近的人……这些背后都离不开地图服务。华为给鸿蒙生态提供了一个叫 Map Kit 的地图服务,功能还挺全的,支持地图显示、位置搜索、路径规划,基本上你能想到的地图能力它都覆盖了。
这篇文章我们就从最基础的开始——怎么在鸿蒙 App 里显示一张地图。别小看这个"显示",里面的坑还挺多的,特别是 AGC 配置和签名那一套,第一次搞很容易翻车。我们一步一步来。
先搞清楚 Map Kit 能干什么
在正式动手之前,先花一分钟了解一下 Map Kit 提供了哪些能力,这样你心里有个底:
- 创建地图:在页面上显示一张完整的地图,内容包括建筑、道路、水系等等
- 地图交互:控制地图的交互手势(缩放、滑动、旋转)和各种交互按钮
- 地图绘制:在地图上添加标记点(Marker)、覆盖物、折线、多边形、圆形等
- 位置搜索:根据关键词搜索 POI(兴趣点),比如搜附近的餐厅、酒店
- 路径规划:支持驾车、步行、骑行三种路径规划
- 静态地图:获取一张地图的静态图片,可以用来做分享卡片什么的
- 地图选点控件:提供地点详情展示、地点选取、区划选择等高级控件
- 坐标系转换:华为地图涉及 WGS84 和 GCJ02 两种坐标系,提供转换工具
这些能力里面,今天这篇文章我们只关注第一个——创建地图、把地图显示出来。后面的那些能力,以后有机会再展开讲。
你需要准备什么
在开始写代码之前,有一些前置工作必须先做好,否则你代码写得再正确,地图也不会显示出来。这一步是最容易踩坑的地方,我尽量讲清楚。
1. 开通地图服务
你需要去 AppGallery Connect(简称 AGC)平台开通地图服务。AGC 是华为的开发者服务平台,类似于苹果的开发者中心或者 Google 的 Firebase Console。
登录 AGC 控制台,找到你的项目,在"我的服务"或者"API 管理"里找到 Map Kit(地图服务),把它打开。这个开关不开的话,后面什么都白搭。
2. 配置应用签名
鸿蒙应用需要一个数字证书(.cer 文件)和 Profile 文件(.p7b 文件)来保证应用的完整性和合法性。如果你之前没搞过鸿蒙的签名,这里简单说一下流程:
- 在 DevEco Studio 里,点击菜单 Build → Generate Key and CSR,生成一个 .p12 密钥文件和一个 .csr 证书请求文件
- 去 AGC 控制台的"证书、APP ID 和 Profile"页面,用 .csr 文件申请一个数字证书(.cer 文件)
- 在同一个页面创建一个 Profile 文件(.p7b 文件),把你的设备 UDID 添加进去
- 回到 DevEco Studio,打开 File → Project Structure → Signing Config,把密钥、证书、Profile 都填进去
获取设备 UDID 的方法是:找到 DevEco Studio 安装目录下的 sdk/default/openharmony/toolchains,用命令行运行 hdc shell bm get --udid。
3. 记录 Client ID
在 AGC 控制台的"我的应用"页面,找到你的应用,能看到一个 Client ID。把这个 ID 复制下来,后面配置项目的时候要用到。
你需要在项目的 module.json5 文件里配置这个 Client ID。打开 entry/src/main/module.json5,找到 module 节点,在里面添加 metadata 配置:
{
"module": {
"name": "entry",
"type": "entry",
"metadata": [
{
"name": "client_id",
"value": "你的ClientID写在这里"
}
]
}
}
补充一句:从 HarmonyOS 5.0.2(14) 版本开始,华为简化了配置流程,开发者不需要再手动配置公钥指纹和 Client ID 了,系统会自动处理身份校验。如果你用的是比较新的 SDK 版本,这一步可能不需要了。但为了保险起见,建议还是配置上。
4. 配置网络权限
地图需要联网才能加载瓦片数据,所以你还需要在 module.json5 里声明网络权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
好了,前置准备工作到此结束。如果你这些配置都搞对了,后面的代码才能正常运行。如果地图不显示,90% 的概率是这几步出了问题。
正式写代码:导入模块
准备工作搞定了,终于可以开始写代码了。
打开 DevEco Studio,创建一个新项目(Empty Ability 就行),然后找到 pages/Index.ets 文件。
首先,在文件顶部导入 Map Kit 相关的模块:
import { MapComponent, mapCommon, map } from '@kit.MapKit';
import { AsyncCallback } from '@kit.BasicServicesKit';
来解释一下这三行 import 到底导入了什么:
MapComponent:这是地图组件本身,它是一个 ArkUI 原生组件,就跟你用 Text、Column、Row 一样,把它放到页面里就能显示地图mapCommon:这里面定义了地图的通用类型,比如MapOptions(地图初始化参数)、MarkerOptions(标记点参数)、MapType(地图类型)等等map:这里面是地图的控制器类和事件管理器,比如MapComponentController(地图控制器)、MapEventManager(事件管理)、MapPolyline(折线)等AsyncCallback:这是异步回调的类型定义,地图初始化的时候会用到
这里补充一个小知识点。你可能注意到导入路径是 @kit.MapKit 而不是 @ohos.xxx。在鸿蒙里,@kit 和 @ohos 是两种不同的命名空间:
@ohos代表鸿蒙核心系统级的 API,更底层,比如系统服务、进程管理之类的@kit代表鸿蒙提供的特定领域的开发工具包(Kit),是针对某个功能领域的封装,比如 Map Kit、Location Kit 之类的
对于应用开发者来说,绝大多数情况下你用的都是 @kit 下的 API,因为它们更高级、更好用。
搭建地图页面的基本框架
接下来我们定义主组件的结构。先写出完整的骨架,然后一行一行解释:
@Entry
@Component
struct HuaweiMapDemo {
private TAG = "HuaweiMapDemo";
private mapOptions?: mapCommon.MapOptions;
private callback?: AsyncCallback<map.MapComponentController>;
private mapController?: map.MapComponentController;
private mapEventManager?: map.MapEventManager;
aboutToAppear(): void {
// ... 初始化代码
}
build() {
Stack() {
MapComponent({ mapOptions: this.mapOptions, mapCallback: this.callback })
.width('100%')
.height('100%')
}
.height('100%')
}
}
先看变量声明部分:
TAG:一个字符串常量,用于 log 输出的时候标识是哪个模块打的日志。这个不是必须的,但养成习惯以后调试的时候会很方便。mapOptions:地图初始化参数,类型是mapCommon.MapOptions。这里面可以设置地图的中心点坐标、缩放层级、手势开关、地图类型等等。callback:地图初始化完成后的回调函数。当地图组件创建好之后,会通过这个回调把一个MapComponentController对象传给你,你拿到这个控制器之后就能操作地图了。mapController:地图控制器,通过回调获取。有了它你可以控制地图的相机位置、添加标记点、画折线等等。mapEventManager:事件管理器,用来监听地图的各种事件,比如地图加载完成(mapLoad)、点击事件(mapClick)等。
再看 build() 方法:
- 最外层用了一个
Stack()布局。Stack 是层叠布局,子组件会堆叠在一起。用 Stack 而不是 Column 的原因是,地图通常是全屏铺满的,你可能会在地图上面叠加一些按钮或者信息面板,Stack 就很适合这种场景。 MapComponent({ mapOptions: this.mapOptions, mapCallback: this.callback }):这就是地图组件了。它接收两个必需的参数:mapOptions(初始化参数)和mapCallback(初始化完成的回调)。然后设置宽高都是 100%,让地图铺满整个页面。
设置地图初始化参数
接下来写 aboutToAppear() 里的初始化逻辑:
aboutToAppear(): void {
// 地图初始化参数,设置地图中心点坐标及层级
this.mapOptions = {
position: {
target: {
latitude: 39.9,
longitude: 116.4
},
zoom: 10
}
};
}
aboutToAppear() 是 ArkTS 组件的生命周期方法,在组件即将显示之前调用。我们在这里初始化地图参数是最合适的时机。
mapOptions 是一个对象,里面最核心的就是 position 属性,它决定了地图一开始显示哪个位置:
target:地图的中心点坐标,包含latitude(纬度)和longitude(经度)。这里我们设置了纬度 39.9、经度 116.4,这大概在北京的位置。zoom:缩放层级。数值越大,地图放大得越多,显示的细节越细;数值越小,显示的范围越大。10 是一个比较适中的层级,能看到整个城市级别的范围。
当然,mapOptions 能设置的属性远不止这些。这里是 Codelab 里用的最简配置,但如果你需要更精细的控制,还可以设置以下属性:
this.mapOptions = {
position: {
target: {
latitude: 39.9,
longitude: 116.4
},
zoom: 10
},
rotateGesturesEnabled: true, // 是否支持旋转手势,默认 true
scrollGesturesEnabled: true, // 是否支持滑动手势,默认 true
zoomGesturesEnabled: true, // 是否支持缩放手势,默认 true
tiltGesturesEnabled: true, // 是否支持倾斜手势,默认 true
mapType: mapCommon.MapType.STANDARD, // 地图类型:标准地图
myLocationControlsEnabled: false, // 是否显示"我的位置"按钮
compassControlsEnabled: true, // 是否显示指南针
scaleControlsEnabled: false // 是否显示比例尺
};
| 属性 | 说明 | 默认值 |
|---|---|---|
mapType |
地图类型(标准/卫星/混合/地形) | MapType.STANDARD |
position |
地图相机位置(中心点 + 缩放级别) | 无默认值,必须设置 |
rotateGesturesEnabled |
是否允许双指旋转 | true |
scrollGesturesEnabled |
是否允许单指滑动 | true |
zoomGesturesEnabled |
是否允许双指缩放 | true |
tiltGesturesEnabled |
是否允许双指倾斜 | true |
myLocationControlsEnabled |
是否显示定位按钮 | false |
compassControlsEnabled |
是否显示指南针 | true |
scaleControlsEnabled |
是否显示比例尺 | false |
dayNightMode |
日夜模式(0=自动, 1=日间, 2=夜间) | 0(自动) |
这些属性看着多,但其实都有合理的默认值,你不需要每个都设置。根据你的业务需求,挑需要的配就行了。
编写地图初始化回调
设置完 mapOptions 之后,接下来写 callback:
this.callback = async (err, mapController) => {
if (!err) {
// 获取地图的控制器类,用来操作地图
this.mapController = mapController;
// 返回地图组件的监听事件管理接口
this.mapEventManager = this.mapController.getEventManager();
let callback = () => {
console.info(this.TAG, `on-mapLoad`);
}
this.mapEventManager.on("mapLoad", callback);
}
};
这个回调函数非常关键,我来说说它的执行时机和参数:
-
执行时机:当地图组件完成了初始化、地图瓦片加载完毕、可以交互的时候,这个回调会被调用。注意它是一个异步回调(
async),因为地图初始化涉及到网络请求和数据渲染,可能需要一点时间。 -
err参数:错误对象。如果初始化失败了,err不为空;如果成功了,err为空(null或undefined)。所以我们先判断!err确认没问题了再继续操作。 -
mapController参数:这就是地图控制器MapComponentController的实例。拿到它之后,你就可以用它来操作地图了。比如移动地图相机、添加标记点、绘制形状等等。 -
getEventManager():通过控制器获取事件管理器。事件管理器可以监听地图上的各种事件,比如地图加载完成(mapLoad)、地图被点击(mapClick)、相机位置变化(cameraChange)等。 -
this.mapEventManager.on("mapLoad", callback):注册一个mapLoad事件监听器。当这个事件触发的时候,执行我们的回调函数——这里只是打印了一条日志,但实际开发中你可能会在这里做初始化标记点、获取地图当前状态等操作。
整个回调的逻辑就是:地图初始化好了 → 拿到控制器 → 拿到事件管理器 → 注册事件监听。这几步是使用 Map Kit 的标准流程,以后你要做更复杂的地图功能,也是在这个回调里展开的。
处理地图的页面生命周期
地图组件有一个很重要的特性:它在页面不可见的时候应该切换到后台,在页面重新显示的时候再切换回前台。这样做可以节省系统资源,避免地图在后台继续渲染和请求网络。
ArkTS 提供了 onPageShow 和 onPageHide 两个页面级生命周期方法(只有加了 @Entry 装饰器的组件才有),刚好可以用来处理这个事情:
// 页面每次显示时触发一次,包括路由过程、应用进入前台等场景
onPageShow(): void {
if (this.mapController) {
this.mapController.show();
}
}
// 页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景
onPageHide(): void {
if (this.mapController) {
this.mapController.hide();
}
}
onPageShow():当页面从后台切回前台、或者新打开这个页面的时候,调用this.mapController.show()让地图切换到前台显示。onPageHide():当页面被其他页面覆盖、或者应用切到后台的时候,调用this.mapController.hide()让地图切换到后台暂停渲染。
这两个方法里都有个 if (this.mapController) 判断,这是因为 onPageShow / onPageHide 可能在地图还没初始化完成的时候就被触发了,这时候 mapController 可能还是 undefined,直接调用方法会报错。所以加个判空保护是必须的。
这两个生命周期方法不是 Codelab 的核心内容,但它们是地图开发中的最佳实践,强烈建议加上。
完整代码
把上面的所有代码拼在一起,完整的 Index.ets 长这样:
import { MapComponent, mapCommon, map } from '@kit.MapKit';
import { AsyncCallback } from '@kit.BasicServicesKit';
@Entry
@Component
struct HuaweiMapDemo {
private TAG = "HuaweiMapDemo";
private mapOptions?: mapCommon.MapOptions;
private callback?: AsyncCallback<map.MapComponentController>;
private mapController?: map.MapComponentController;
private mapEventManager?: map.MapEventManager;
aboutToAppear(): void {
// 地图初始化参数,设置地图中心点坐标及层级
this.mapOptions = {
position: {
target: {
latitude: 39.9,
longitude: 116.4
},
zoom: 10
}
};
// 地图初始化的回调
this.callback = async (err, mapController) => {
if (!err) {
// 获取地图的控制器类,用来操作地图
this.mapController = mapController;
// 返回地图组件的监听事件管理接口
this.mapEventManager = this.mapController.getEventManager();
let callback = () => {
console.info(this.TAG, `on-mapLoad`);
}
this.mapEventManager.on("mapLoad", callback);
}
};
}
// 页面每次显示时触发一次
onPageShow(): void {
if (this.mapController) {
this.mapController.show();
}
}
// 页面每次隐藏时触发一次
onPageHide(): void {
if (this.mapController) {
this.mapController.hide();
}
}
build() {
Stack() {
// 调用MapComponent组件初始化地图
MapComponent({ mapOptions: this.mapOptions, mapCallback: this.callback })
.width('100%')
.height('100%')
}
.height('100%')
}
}
代码量其实不大,去掉注释和空行也就三十多行。但就是这么三十多行代码,就能在你的鸿蒙 App 里显示一张可以交互的地图了——支持滑动、缩放、旋转,跟你在华为地图 App 里看到的效果是一样的。
跑起来看看效果
这段代码写完之后,你需要用真机来运行测试。对,你没看错,模拟器不支持地图组件。
这是为什么呢?因为 Map Kit 地图组件依赖设备的硬件能力和华为的地图服务,模拟器是虚拟环境,无法完全模拟这些。官方文档也明确说了,Map Kit 在模拟器上暂不支持。所以如果你在模拟器上运行,要么看到的是空白页面,要么只显示底色没有街道和楼房。
用真机调试的步骤:
- 用 USB 连上你的鸿蒙手机(或者华为手机升级到鸿蒙系统的)
- 在手机的开发者选项里打开 USB 调试
- 在 DevEco Studio 里选择你的设备,点击运行
如果你的 AGC 配置、签名、Client ID 都没问题的话,打开 App 之后你应该能看到一张以北京为中心的地图。
你可能踩的坑
写地图代码其实不难,真正让人头疼的是那些配置问题。我把常见的坑列一下,遇到问题的时候可以对照着排查:
1. 地图是空白的,只有底色没有内容
这是最常见的问题。大概率是以下原因之一:
- AGC 平台上没有开通地图服务
module.json5里的client_id跟 AGC 控制台上的不一致- 签名没有配置正确(特别是手动签名后忘了在 AGC 上添加公钥指纹)
- 先配置了签名、后来才开通的地图服务(顺序反了,需要重新操作)
2. 模拟器上地图不显示
这个不是 bug,是正常现象。Map Kit 目前只支持真机调试,模拟器上跑不起来。去借一台华为手机吧。
3. App 直接崩溃
检查一下 module.json5 里的网络权限有没有声明。没有 ohos.permission.INTERNET 权限的话,地图组件在初始化的时候就会因为网络请求失败而崩溃。
4. 编译报错 “Cannot find module ‘@kit.MapKit’”
检查一下你的 SDK 版本。Map Kit 是 HarmonyOS 4.1.0(11) 才引入的,如果你的 SDK 版本太低,会找不到这个模块。在 DevEco Studio 里打开 File → Settings → SDK Manager 看一下 SDK 版本,至少要是 4.1 或以上。
5. 回调函数里的 err 不为空
err 不为空说明地图初始化失败了。这时候先看一下 err 对象里有什么信息。如果是网络问题,检查手机能不能正常上网;如果是鉴权问题,重新检查签名和 Client ID 配置。
整体流程回顾一下
最后把整个开发流程串一遍,帮你理清思路:
| 步骤 | 做什么 | 关键点 |
|---|---|---|
| 1 | AGC 开通地图服务 | 控制台里打开 Map Kit 开关 |
| 2 | 配置签名 | 生成密钥 → 申请证书 → 创建 Profile → DevEco 里配置 |
| 3 | 获取 Client ID | 从 AGC 控制台复制,填到 module.json5 |
| 4 | 声明网络权限 | module.json5 里加 ohos.permission.INTERNET |
| 5 | 导入 Map Kit 模块 | import { MapComponent, mapCommon, map } from '@kit.MapKit' |
| 6 | 创建 mapOptions | 设置中心点坐标和缩放层级 |
| 7 | 创建 callback | 初始化完成后获取控制器和事件管理器 |
| 8 | 放置 MapComponent | 传入 mapOptions 和 callback |
| 9 | 处理页面生命周期 | onPageShow 里 show,onPageHide 里 hide |
| 10 | 真机运行测试 | 模拟器不支持,必须用真机 |
这十步走完,你的鸿蒙 App 里就有一张可以交互的地图了。这个是 Map Kit 最基础的用法,但也是后面所有高级功能的基础——添加标记点、画路线、搜索位置、路径规划……全都建立在这个基础之上。把这个基础搞扎实了,后面学那些高级功能会轻松很多。
更多推荐




所有评论(0)