Administrator
发布于 2026-09-01 / 43 阅读
0
0

鸿蒙开发系列(第15篇):地图与位置服务——Map Kit集成与轨迹绘制

鸿蒙开发系列 · 高级篇

前言

前几篇我们分别搞定了元服务、安全权限、推送后台、音视频播放。但如果你的产品是一个外卖配送App、一个运动健康应用,或者一个旅游攻略平台,地图能力就是绕不开的核心诉求。

地图开发看似简单——“调个SDK,传个经纬度,不就显示出来了?”——实际踩过坑的人都知道,定位精度漂移、地图卡顿、POI搜索不准确、轨迹绘制锯齿化、离线场景无图,每个问题都能消耗数天的调试时间。

HarmonyOS提供了Map Kit作为地图能力的官方解决方案。它基于华为自研地图数据,深度整合鸿蒙系统能力,支持手机、车机、手表等多设备形态。本文将从定位获取、地图显示、POI搜索、轨迹绘制四个维度,帮你建立完整的地图开发技术栈。


一、位置服务:定位获取与精度控制

1.1 定位架构与权限

在调用任何定位API之前,需要完成两件事:

权限声明(module.json5):

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:perm_location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:perm_approx_location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

HarmonyOS NEXT引入了模糊定位权限(APPROXIMATELY_LOCATION),这是隐私合规的重要设计:

权限

精度

适用场景

APPROXIMATELY_LOCATION

约100米精度

天气、新闻本地化、粗略推荐

LOCATION

最高精度(GPS可达米级)

导航、外卖配送、运动轨迹

最佳实践:先申请模糊定位,只有在用户明确需要高精度场景时再申请精确定位。这样既能保护用户隐私,又能提升授权通过率。

1.2 单次定位与持续定位

// entry/src/main/ets/utils/LocationManager.ets
import { geoLocationManager } from '@kit.LocationKit';
import { BusinessError } from '@kit.BasicServicesKit';

export interface LocationInfo {
  latitude: number;
  longitude: number;
  altitude: number;
  accuracy: number;
  speed: number;
  time: number;
}

export class LocationManager {
  private locationChangeCallback: ((location: LocationInfo) => void) | null = null;

  // 单次定位
  async getCurrentLocation(): Promise<LocationInfo> {
    const requestInfo: geoLocationManager.CurrentLocationRequest = {
      priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
      scenario: geoLocationManager.LocationRequestScenario.UNSET
    };

    try {
      const location = await geoLocationManager.getCurrentLocation(requestInfo);
      return this.convertLocation(location);
    } catch (err) {
      const error = err as BusinessError;
      console.error(`定位失败: ${error.code}, ${error.message}`);
      throw error;
    }
  }

  // 持续定位
  startLocationUpdates(
    callback: (location: LocationInfo) => void,
    interval: number = 1000
  ): void {
    this.locationChangeCallback = callback;

    const requestInfo: geoLocationManager.LocationRequest = {
      priority: geoLocationManager.LocationRequestPriority.PRIORITY_LOCATING,
      scenario: geoLocationManager.LocationRequestScenario.NAVIGATION,
      timeInterval: interval,
      distanceInterval: 0
    };

    geoLocationManager.on('locationChange', requestInfo, (location) => {
      const info = this.convertLocation(location);
      this.locationChangeCallback?.(info);
    });
  }

  // 停止持续定位
  stopLocationUpdates(): void {
    geoLocationManager.off('locationChange');
    this.locationChangeCallback = null;
  }

  // 地理编码:地址 → 经纬度
  async geoCode(address: string): Promise<LocationInfo[]> {
    const result = await geoLocationManager.getAddressesFromLocationName({
      description: address,
      maxItems: 5
    });

    return result.map(item => ({
      latitude: item.latitude,
      longitude: item.longitude,
      altitude: 0,
      accuracy: 0,
      speed: 0,
      time: Date.now()
    }));
  }

  // 逆地理编码:经纬度 → 地址
  async reverseGeoCode(lat: number, lng: number): Promise<string> {
    const result = await geoLocationManager.getAddressesFromLocation({
      latitude: lat,
      longitude: lng,
      maxItems: 1
    });

    if (result.length > 0) {
      const addr = result[0];
      return `${addr.administrativeArea || ''}${addr.locality || ''}${addr.subLocality || ''}${addr.roadName || ''}${addr.subRoadName || ''}`;
    }
    return '';
  }

  private convertLocation(loc: geoLocationManager.Location): LocationInfo {
    return {
      latitude: loc.latitude,
      longitude: loc.longitude,
      altitude: loc.altitude ?? 0,
      accuracy: loc.accuracy ?? 0,
      speed: loc.speed ?? 0,
      time: loc.timeStamp ?? Date.now()
    };
  }
}

1.3 定位精度与功耗权衡

优先级(Priority)

精度

功耗

适用场景

PRIORITY_ACCURACY

最高(GPS)

高

导航、运动轨迹

PRIORITY_LOCATING

高(混合定位)

中高

实时位置共享

FIRST_FIX

首速优先

中

快速获取大致位置

PRIORITY_LOW_POWER

低(网络定位)

低

后台位置上报

关键建议:持续定位场景中,根据应用状态动态调整精度。前台用高精度,退后台后切换为低功耗模式。

// 前台高精度
startLocationUpdates(callback, 1000); // 每秒更新

// 退后台后降低频率
onBackgroundEnter() {
  this.stopLocationUpdates();
  this.startLocationUpdates(callback, 30000); // 30秒更新一次
}

二、Map Kit 地图集成

2.1 引入地图组件

Map Kit以组件形式提供,在页面中直接引用:

// entry/src/main/ets/pages/MapPage.ets
import { MapComponent, map } from '@kit.MapKit';

@Entry
@Component
struct MapPage {
  private mapController: map.MapComponentController | null = null;
  @State mapOptions: map.MapOptions = {
    position: {
      target: {
        latitude: 39.9042,  // 北京天安门
        longitude: 116.4074
      },
      zoom: 12
    }
  };

  onMapReady(controller: map.MapComponentController) {
    this.mapController = controller;
    // 地图就绪后可以进行操作
    this.addMarkers();
  }

  private addMarkers() {
    if (!this.mapController) return;

    const markerOptions: map.MarkerOptions = {
      position: {
        latitude: 39.9042,
        longitude: 116.4074
      },
      title: '当前位置',
      snippet: '北京市东城区',
      icon: $r('app.media.ic_location_marker')
    };

    this.mapController.addMarker(markerOptions);
  }

  // 移动到指定位置
  moveToLocation(lat: number, lng: number) {
    this.mapController?.moveCamera(
      map.CameraUpdateFactory.newLatLngZoom({ latitude: lat, longitude: lng }, 15)
    );
  }

  build() {
    Stack() {
      MapComponent({
        mapOptions: this.mapOptions,
        mapCallback: (err, controller) => {
          if (!err && controller) {
            this.onMapReady(controller);
          }
        }
      })
        .width('100%')
        .height('100%')

      // 叠加UI控件
      Column({ space: 12 }) {
        Button('定位到我')
          .onClick(() => this.locateMe())

        Button('放大')
          .onClick(() => this.mapController?.moveCamera(
            map.CameraUpdateFactory.zoomIn()
          ))

        Button('缩小')
          .onClick(() => this.mapController?.moveCamera(
            map.CameraUpdateFactory.zoomOut()
          ))
      }
      .padding(16)
      .alignSelf(ItemAlign.End)
      .margin({ bottom: 32, right: 16 })
    }
    .width('100%')
    .height('100%')
  }

  private async locateMe() {
    const locManager = new LocationManager();
    const location = await locManager.getCurrentLocation();
    this.moveToLocation(location.latitude, location.longitude);

    // 添加当前位置标记
    this.mapController?.addMarker({
      position: { latitude: location.latitude, longitude: location.longitude },
      title: '我的位置',
      icon: $r('app.media.ic_my_location')
    });
  }
}

2.2 地图核心配置

// 地图选项详解
const mapOptions: map.MapOptions = {
  position: {
    target: { latitude: 39.9, longitude: 116.4 },
    zoom: 10,        // 缩放级别 3-20
    tilt: 0,         // 倾斜角度 0-75
    bearing: 0       // 旋转角度
  },
  mapType: map.MapType.STANDARD,  // STANDARD / SATELLITE / TERRAIN
  // 控件显示
  compassControlsEnabled: true,    // 指南针
  scaleControlsEnabled: true,      // 比例尺
  zoomControlsEnabled: false,      // 缩放按钮(通常自定义UI)
  // 手势
  scrollGesturesEnabled: true,     // 拖拽
  zoomGesturesEnabled: true,       // 双指缩放
  tiltGesturesEnabled: true,       // 双指倾斜
  rotationGesturesEnabled: true    // 旋转
};

三、POI搜索与周边服务

3.1 文本搜索与周边搜索

// entry/src/main/ets/utils/PoiSearchManager.ets
import { site } from '@kit.MapKit';

export interface PoiResult {
  name: string;
  address: string;
  latitude: number;
  longitude: number;
  distance?: number;
  phone?: string;
  poiTypes?: string[];
}

export class PoiSearchManager {
  private searchService: site.SiteClient | null = null;

  async initialize(): Promise<void> {
    this.searchService = await site.createSiteClient();
  }

  // 文本搜索(如"附近的咖啡厅")
  async textSearch(query: string, location?: { lat: number; lng: number }): Promise<PoiResult[]> {
    if (!this.searchService) throw new Error('搜索服务未初始化');

    const request: site.TextSearchRequest = {
      query: query,
      location: location ? {
        lat: location.lat,
        lng: location.lng
      } : undefined,
      radius: 5000, // 搜索半径5公里
      pageSize: 20,
      pageIndex: 1
    };

    const response = await this.searchService.textSearch(request);
    return this.convertResults(response.sites);
  }

  // 周边搜索(搜索某位置附近的某类POI)
  async nearbySearch(
    lat: number,
    lng: number,
    keyword: string,
    radius: number = 3000
  ): Promise<PoiResult[]> {
    if (!this.searchService) throw new Error('搜索服务未初始化');

    const request: site.NearbySearchRequest = {
      location: { lat, lng },
      query: keyword,
      radius,
      pageSize: 20,
      pageIndex: 1
    };

    const response = await this.searchService.nearbySearch(request);
    return this.convertResults(response.sites);
  }

  // 获取POI详情
  async getPoiDetail(siteId: string): Promise<PoiResult | null> {
    if (!this.searchService) throw new Error('搜索服务未初始化');

    const request: site.DetailSearchRequest = { siteId };
    const response = await this.searchService.detailSearch(request);
    return response.site ? this.convertResult(response.site) : null;
  }

  // 搜索建议(自动补全)
  async searchSuggestion(query: string): Promise<string[]> {
    if (!this.searchService) throw new Error('搜索服务未初始化');

    const request: site.QuerySuggestionRequest = {
      query: query
    };

    const response = await this.searchService.querySuggestion(request);
    return response.suggestions.map(s => s.name);
  }

  private convertResults(sites: site.Site[]): PoiResult[] {
    return sites.map(s => this.convertResult(s));
  }

  private convertResult(s: site.Site): PoiResult {
    return {
      name: s.name,
      address: s.formatAddress || '',
      latitude: s.location?.lat ?? 0,
      longitude: s.location?.lng ?? 0,
      distance: s.distance,
      phone: s.phone,
      poiTypes: s.poiTypes
    };
  }
}

3.2 搜索页面实战

// entry/src/main/ets/pages/SearchPage.ets
import { PoiSearchManager, PoiResult } from '../utils/PoiSearchManager';
import { MapComponent, map } from '@kit.MapKit';

@Entry
@Component
struct SearchPage {
  private searchManager: PoiSearchManager = new PoiSearchManager();
  private mapController: map.MapComponentController | null = null;
  @State searchQuery: string = '';
  @State suggestions: string[] = [];
  @State results: PoiResult[] = [];
  @State selectedPoi: PoiResult | null = null;
  @State showResults: boolean = false;

  aboutToAppear() {
    this.searchManager.initialize();
  }

  private async onSearch() {
    if (!this.searchQuery.trim()) return;
    this.showResults = true;
    this.results = await this.searchManager.textSearch(this.searchQuery);

    // 在地图上标记结果
    this.mapController?.clearMarkers();
    this.results.forEach((poi, index) => {
      this.mapController?.addMarker({
        position: { latitude: poi.latitude, longitude: poi.longitude },
        title: poi.name,
        snippet: poi.address,
        icon: index === 0
          ? $r('app.media.ic_marker_primary')
          : $r('app.media.ic_marker_secondary')
      });
    });

    // 移动视角到第一个结果
    if (this.results.length > 0) {
      this.mapController?.moveCamera(
        map.CameraUpdateFactory.newLatLng({
          latitude: this.results[0].latitude,
          longitude: this.results[0].longitude
        })
      );
    }
  }

  private async onQueryChange(value: string) {
    this.searchQuery = value;
    if (value.length >= 2) {
      this.suggestions = await this.searchManager.searchSuggestion(value);
    } else {
      this.suggestions = [];
    }
  }

  build() {
    Stack() {
      MapComponent({
        mapOptions: {
          position: { target: { latitude: 39.9, longitude: 116.4 }, zoom: 12 }
        },
        mapCallback: (err, controller) => {
          if (!err && controller) this.mapController = controller;
        }
      })
        .width('100%')
        .height('100%')

      // 搜索栏
      Column({ space: 0 }) {
        Row({ space: 12 }) {
          TextInput({ placeholder: '搜索地点、商家...', text: $$this.searchQuery })
            .layoutWeight(1)
            .height(44)
            .backgroundColor('#F5F5F5')
            .borderRadius(22)
            .padding({ left: 16, right: 16 })
            .onChange((value) => this.onQueryChange(value))

          Button('搜索')
            .height(44)
            .onClick(() => this.onSearch())
        }
        .width('100%')
        .padding(16)
        .backgroundColor('#FFFFFF')
        .shadow({ radius: 8, color: 'rgba(0,0,0,0.08)' })

        // 搜索建议
        if (this.suggestions.length > 0 && !this.showResults) {
          List() {
            ForEach(this.suggestions, (suggestion: string) => {
              ListItem() {
                Text(suggestion)
                  .fontSize(16)
                  .padding(16)
                  .width('100%')
              }
              .onClick(() => {
                this.searchQuery = suggestion;
                this.onSearch();
              })
            })
          }
          .width('100%')
          .maxHeight(200)
          .backgroundColor('#FFFFFF')
        }

        // 搜索结果
        if (this.showResults) {
          List() {
            ForEach(this.results, (poi: PoiResult) => {
              ListItem() {
                Column({ space: 4 }) {
                  Text(poi.name)
                    .fontSize(16)
                    .fontWeight(FontWeight.Medium)
                  Text(poi.address)
                    .fontSize(13)
                    .fontColor('#888')
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                }
                .padding(16)
                .width('100%')
                .backgroundColor(this.selectedPoi?.name === poi.name ? '#E3F2FD' : '#FFFFFF')
              }
              .onClick(() => {
                this.selectedPoi = poi;
                this.mapController?.moveCamera(
                  map.CameraUpdateFactory.newLatLngZoom(
                    { latitude: poi.latitude, longitude: poi.longitude },
                    16
                  )
                );
              })
            })
          }
          .width('100%')
          .layoutWeight(1)
          .backgroundColor('#FFFFFF')
        }
      }
      .width('100%')
      .height('100%')
      .alignItems(HorizontalAlign.Start)
    }
  }
}

四、轨迹绘制与运动场景

4.1 轨迹线绘制

运动类应用(跑步、骑行、外卖配送)需要绘制用户移动的轨迹线:

// entry/src/main/ets/utils/TrackDrawer.ets
import { map } from '@kit.MapKit';

export interface TrackPoint {
  latitude: number;
  longitude: number;
  timestamp: number;
  speed?: number;
}

export class TrackDrawer {
  private mapController: map.MapComponentController;
  private polyline: map.Polyline | null = null;
  private trackPoints: TrackPoint[] = [];

  constructor(controller: map.MapComponentController) {
    this.mapController = controller;
  }

  // 添加轨迹点
  addPoint(point: TrackPoint): void {
    this.trackPoints.push(point);
    this.redrawTrack();
  }

  // 批量设置轨迹
  setTrack(points: TrackPoint[]): void {
    this.trackPoints = points;
    this.redrawTrack();
  }

  // 重绘轨迹线
  private redrawTrack(): void {
    if (this.trackPoints.length < 2) return;

    // 移除旧轨迹
    if (this.polyline) {
      this.mapController.removeOverlay(this.polyline);
    }

    // 创建新轨迹线
    const options: map.PolylineOptions = {
      points: this.trackPoints.map(p => ({
        latitude: p.latitude,
        longitude: p.longitude
      })),
      width: 6,
      color: 0xFF2196F3, // 蓝色
      // 可分段设置颜色表示速度
      gradient: this.generateGradient()
    };

    this.polyline = this.mapController.addPolyline(options);

    // 自动调整视角以完整显示轨迹
    this.fitBounds();
  }

  // 根据速度生成渐变色
  private generateGradient(): map.Gradient | undefined {
    if (this.trackPoints.length < 2) return undefined;

    const colors: map.GradientColor[] = [];
    const speeds = this.trackPoints
      .filter(p => p.speed !== undefined)
      .map(p => p.speed!);

    if (speeds.length === 0) return undefined;

    const maxSpeed = Math.max(...speeds);
    const minSpeed = Math.min(...speeds);

    for (let i = 0; i < this.trackPoints.length; i++) {
      const speed = this.trackPoints[i].speed ?? 0;
      const ratio = maxSpeed > minSpeed
        ? (speed - minSpeed) / (maxSpeed - minSpeed)
        : 0;

      // 低速绿色 → 高速红色
      const color = this.interpolateColor(0xFF4CAF50, 0xFFF44336, ratio);
      colors.push({ color, ratio: i / (this.trackPoints.length - 1) });
    }

    return { colors };
  }

  private interpolateColor(color1: number, color2: number, ratio: number): number {
    const r1 = (color1 >> 16) & 0xFF;
    const g1 = (color1 >> 8) & 0xFF;
    const b1 = color1 & 0xFF;

    const r2 = (color2 >> 16) & 0xFF;
    const g2 = (color2 >> 8) & 0xFF;
    const b2 = color2 & 0xFF;

    const r = Math.round(r1 + (r2 - r1) * ratio);
    const g = Math.round(g1 + (g2 - g1) * ratio);
    const b = Math.round(b1 + (b2 - b1) * ratio);

    return (0xFF << 24) | (r << 16) | (g << 8) | b;
  }

  // 调整地图视角以显示完整轨迹
  private fitBounds(): void {
    if (this.trackPoints.length === 0) return;

    const lats = this.trackPoints.map(p => p.latitude);
    const lngs = this.trackPoints.map(p => p.longitude);

    const bounds: map.LatLngBounds = {
      southwest: {
        latitude: Math.min(...lats),
        longitude: Math.min(...lngs)
      },
      northeast: {
        latitude: Math.max(...lats),
        longitude: Math.max(...lngs)
      }
    };

    this.mapController?.moveCamera(
      map.CameraUpdateFactory.newLatLngBounds(bounds, 50) // 50px padding
    );
  }

  // 清除轨迹
  clear(): void {
    if (this.polyline) {
      this.mapController.removeOverlay(this.polyline);
      this.polyline = null;
    }
    this.trackPoints = [];
  }
}

4.2 运动记录页面

// entry/src/main/ets/pages/TrackPage.ets
import { MapComponent, map } from '@kit.MapKit';
import { LocationManager, LocationInfo } from '../utils/LocationManager';
import { TrackDrawer, TrackPoint } from '../utils/TrackDrawer';

@Entry
@Component
struct TrackPage {
  private mapController: map.MapComponentController | null = null;
  private trackDrawer: TrackDrawer | null = null;
  private locationManager: LocationManager = new LocationManager();
  @State isRecording: boolean = false;
  @State duration: number = 0;
  @State distance: number = 0;
  @State speed: number = 0;
  private timer: number = -1;
  private lastLocation: LocationInfo | null = null;

  onMapReady(controller: map.MapComponentController) {
    this.mapController = controller;
    this.trackDrawer = new TrackDrawer(controller);
  }

  startTracking() {
    this.isRecording = true;
    this.duration = 0;
    this.distance = 0;

    // 开始计时
    this.timer = setInterval(() => {
      this.duration++;
    }, 1000);

    // 开始定位
    this.locationManager.startLocationUpdates((location) => {
      const point: TrackPoint = {
        latitude: location.latitude,
        longitude: location.longitude,
        timestamp: location.time,
        speed: location.speed
      };

      this.trackDrawer?.addPoint(point);

      // 计算距离
      if (this.lastLocation) {
        const dist = this.calculateDistance(this.lastLocation, location);
        this.distance += dist;
      }
      this.lastLocation = location;

      // 计算配速
      if (this.duration > 0) {
        this.speed = (this.distance / this.duration) * 3.6; // m/s → km/h
      }

      // 跟随当前位置
      this.mapController?.moveCamera(
        map.CameraUpdateFactory.newLatLng({
          latitude: location.latitude,
          longitude: location.longitude
        })
      );
    }, 2000); // 每2秒记录一个点
  }

  stopTracking() {
    this.isRecording = false;
    this.locationManager.stopLocationUpdates();
    if (this.timer !== -1) {
      clearInterval(this.timer);
      this.timer = -1;
    }
  }

  // 简化的距离计算(Haversine公式)
  private calculateDistance(loc1: LocationInfo, loc2: LocationInfo): number {
    const R = 6371000; // 地球半径(米)
    const lat1 = this.toRad(loc1.latitude);
    const lat2 = this.toRad(loc2.latitude);
    const deltaLat = this.toRad(loc2.latitude - loc1.latitude);
    const deltaLng = this.toRad(loc2.longitude - loc1.longitude);

    const a = Math.sin(deltaLat / 2) ** 2 +
              Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLng / 2) ** 2;
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

    return R * c;
  }

  private toRad(deg: number): number {
    return deg * Math.PI / 180;
  }

  private formatDuration(seconds: number): string {
    const hrs = Math.floor(seconds / 3600);
    const mins = Math.floor((seconds % 3600) / 60);
    const secs = seconds % 60;
    return `${hrs.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  }

  build() {
    Stack() {
      MapComponent({
        mapOptions: {
          position: { target: { latitude: 39.9, longitude: 116.4 }, zoom: 15 }
        },
        mapCallback: (err, controller) => {
          if (!err && controller) this.onMapReady(controller);
        }
      })
        .width('100%')
        .height('100%')

      // 数据面板
      Column({ space: 16 }) {
        Row({ space: 24 }) {
          Column({ space: 4 }) {
            Text(this.formatDuration(this.duration))
              .fontSize(28)
              .fontWeight(FontWeight.Bold)
            Text('时长')
              .fontSize(12)
              .fontColor('#888')
          }

          Column({ space: 4 }) {
            Text(`${(this.distance / 1000).toFixed(2)}`)
              .fontSize(28)
              .fontWeight(FontWeight.Bold)
            Text('公里')
              .fontSize(12)
              .fontColor('#888')
          }

          Column({ space: 4 }) {
            Text(`${this.speed.toFixed(1)}`)
              .fontSize(28)
              .fontWeight(FontWeight.Bold)
            Text('km/h')
              .fontSize(12)
              .fontColor('#888')
          }
        }
        .width('100%')
        .padding(20)
        .backgroundColor('rgba(255,255,255,0.95)')
        .borderRadius(16)
        .justifyContent(FlexAlign.SpaceAround)

        Button(this.isRecording ? '停止记录' : '开始记录')
          .width(200)
          .height(56)
          .fontSize(18)
          .backgroundColor(this.isRecording ? '#F44336' : '#4CAF50')
          .onClick(() => {
            if (this.isRecording) {
              this.stopTracking();
            } else {
              this.startTracking();
            }
          })
      }
      .padding(16)
      .alignSelf(ItemAlign.End)
      .margin({ bottom: 32 })
    }
    .width('100%')
    .height('100%')
  }
}

五、小结与下篇预告

本文从四个维度建立了鸿蒙地图开发能力:

  1. 位置服务:单次/持续定位、模糊/精确定位权限、地理编码与逆编码、功耗优化

  2. Map Kit集成:地图组件、标记、相机控制、手势交互

  3. POI搜索:文本搜索、周边搜索、搜索建议、详情查询

  4. 轨迹绘制:实时轨迹线、速度渐变色、视角自动适配

地图开发的核心挑战在于精度与功耗的平衡、大量标记的性能优化、离线场景的降级策略。在实际项目中,建议结合Work Scheduler做后台轨迹记录,结合Push Kit做地理围栏触发。

地图解决了"在哪里"的问题,而现代应用还需要解决"怎么付钱"和"怎么登录"的问题。华为账号体系提供了统一的登录能力,应用内支付(IAP)提供了标准化的收银台。第16篇将深入鸿蒙支付与账号集成,讲解华为账号登录、OAuth授权、应用内商品配置和支付流程的完整实现。


参考阅读


评论