96、地图与导航:高德地图SDK、POI搜索、路线规划、导航组件

地图与导航,几乎是现代App的标配功能。不管是外卖、打车,还是社交、旅游,你总得让用户知道「我在哪」和「怎么去」。

高德地图SDK,是国内用得最广的地图方案之一。我这些年做过好几个地图相关的项目,从简单的定位展示,到复杂的实时导航、轨迹回放,踩过的坑真不少。今天这一讲,我把核心知识点串起来,帮你快速上手。

本章核心脉络:

高德SDK接入 → 地图展示 → POI搜索 → 路线规划 → 导航组件集成

高德地图SDK 知识体系 高德地图SDK 地图展示 POI搜索 路线规划 导航组件 地图初始化 定位与标记 关键词搜索 周边搜索 驾车/步行/公交 导航启动 语音播报

1. 高德SDK接入:别小看这一步

接入SDK本身不复杂,但细节决定成败。我见过太多人卡在「地图一片灰」的问题上。

首先,去高德开放平台注册应用,拿到API Key。这一步要注意:Android的SHA1指纹必须和签名文件一致。调试用debug.keystore,发布用release,别搞混了。

⚠️ 我曾经遇到过: 开发时地图正常显示,打包上线后地图全灰。查了半天,发现是release签名没配置SHA1。嗯,这种低级错误,犯过一次就记住了。

在build.gradle里添加依赖:

dependencies {
    implementation 'com.amap.api:3dmap:latest.integration'
    implementation 'com.amap.api:search:latest.integration'
    implementation 'com.amap.api:navi-3dmap:latest.integration'
}

然后在AndroidManifest.xml里配置Key:

<meta-data
    android:name="com.amap.api.v2.apikey"
    android:value="你的API Key" />

别忘了申请定位权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

2. 地图展示:让地图「动」起来

地图初始化很简单,在布局里放一个MapView:

<com.amap.api.maps.MapView
    android:id="@+id/map"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

在Activity里:

public class MapActivity extends AppCompatActivity {
    private MapView mapView;
    private AMap aMap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_map);
        mapView = findViewById(R.id.map);
        mapView.onCreate(savedInstanceState);
        aMap = mapView.getMap();
        // 设置地图类型
        aMap.setMapType(AMap.MAP_TYPE_NORMAL);
        // 开启定位层
        aMap.setMyLocationEnabled(true);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mapView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mapView.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mapView.onDestroy();
    }
}

💡 我个人习惯: 把MapView的生命周期方法全部写在基类里,避免子类遗漏。你想想看,如果忘了调onDestroy,内存泄漏是小事,地图卡顿才是真头疼。

3. POI搜索:让用户找到想去的地方

POI搜索,说白了就是「搜地点」。高德提供了关键词搜索和周边搜索两种方式。

先初始化搜索对象:

PoiSearch.Query query = new PoiSearch.Query("加油站", "", "北京");
query.setPageSize(20);
query.setPageNum(0);

PoiSearch poiSearch = new PoiSearch(this, query);
poiSearch.setOnPoiSearchListener(new PoiSearch.OnPoiSearchListener() {
    @Override
    public void onPoiSearched(PoiResult result, int rCode) {
        if (rCode == 1000 && result != null) {
            List<PoiItem> items = result.getPois();
            // 把结果展示在地图上
            for (PoiItem item : items) {
                LatLonPoint point = item.getLatLonPoint();
                // 添加Marker
            }
        }
    }

    @Override
    public void onPoiItemSearched(PoiItem item, int rCode) {
        // 单个POI详情
    }
});
poiSearch.searchPOIAsyn();

周边搜索更实用,比如「我附近有什么好吃的」:

PoiSearch.Query query = new PoiSearch.Query("美食", "", "");
query.setPageSize(20);
PoiSearch poiSearch = new PoiSearch(this, query);
poiSearch.setBound(new PoiSearch.SearchBound(
    new LatLonPoint(lat, lng), 1000)); // 半径1公里
poiSearch.searchPOIAsyn();

注意: POI搜索是异步的,回调在子线程。更新UI记得切回主线程。我习惯用Handler或者LiveData来处理。

4. 路线规划:从A到B的多种走法

路线规划支持驾车、步行、公交、骑行。我重点讲驾车路线,其他大同小异。

RouteSearch routeSearch = new RouteSearch(this);
routeSearch.setRouteSearchListener(new RouteSearch.OnRouteSearchListener() {
    @Override
    public void onDriveRouteSearched(DriveRouteResult result, int rCode) {
        if (rCode == 1000 && result != null) {
            List<DrivePath> paths = result.getPaths();
            // 取第一条路线
            DrivePath path = paths.get(0);
            List<DriveStep> steps = path.getSteps();
            // 绘制路线
            for (DriveStep step : steps) {
                List<LatLonPoint> points = step.getPolyline();
                // 用Polyline画到地图上
            }
        }
    }

    @Override
    public void onWalkRouteSearched(WalkRouteResult result, int rCode) { }
    @Override
    public void onBusRouteSearched(BusRouteResult result, int rCode) { }
    @Override
    public void onRideRouteSearched(RideRouteResult result, int rCode) { }
});

// 构建起终点
RouteSearch.FromAndTo fromAndTo = new RouteSearch.FromAndTo(
    new LatLonPoint(startLat, startLng),
    new LatLonPoint(endLat, endLng)
);
// 驾车策略:避免拥堵
RouteSearch.DriveRouteQuery query = new RouteSearch.DriveRouteQuery(
    fromAndTo, RouteSearch.DrivingDefault, null, null, "");
routeSearch.calculateDriveRouteAsyn(query);

💡 避坑指南: 路线规划的结果里,DrivePath 包含多个 DriveStep,每个Step又包含一段折线。绘制时要把所有Step的折线连起来,不然路线会断断续续。我曾经因为这个被测试小姐姐追着改bug……

5. 导航组件:一键开启导航

高德提供了现成的导航组件,说白了就是帮你把地图、路线、语音播报、转向提示都封装好了。你只需要调用一个Intent。

// 构建导航参数
AMapNaviParams naviParams = new AMapNaviParams(
    new LatLonPoint(startLat, startLng), // 起点
    new LatLonPoint(endLat, endLng),     // 终点
    AMapNaviPathType.DRIVING_DEFAULT,    // 驾车模式
    AMapCarType.DEFAULT                  // 车辆类型
);

// 启动导航界面
AMapNavi.getInstance(this).startNavi(naviParams);

如果你想自定义导航界面,高德也提供了 AMapNaviView 组件:

<com.amap.api.navi.AMapNaviView
    android:id="@+id/navi_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

然后在Activity里:

AMapNaviView naviView = findViewById(R.id.navi_view);
naviView.onCreate(savedInstanceState);
naviView.setAMapNaviViewListener(new AMapNaviViewListener() {
    @Override
    public void onNaviSetting() {
        // 用户点击设置按钮
    }

    @Override
    public void onNaviCancel() {
        // 用户取消导航
        finish();
    }
});

// 开始导航
AMapNavi.getInstance(this).calculateDriveRoute(
    new LatLonPoint(startLat, startLng),
    new LatLonPoint(endLat, endLng),
    AMapNaviPathType.DRIVING_DEFAULT);

⚠️ 注意: 导航组件需要后台定位权限,否则导航过程中定位会断。另外,语音播报需要下载离线语音包,或者使用在线合成。我建议提前下载好,避免用户在路上等语音包下载。

6. 性能优化与常见问题

地图类App最怕什么?卡顿和内存泄漏。我总结几个要点:

  • 地图复用: 如果多个页面需要地图,考虑用Fragment共享一个MapView,而不是每个页面都创建新的。
  • Marker管理: 大量Marker时,用MarkerOptions批量添加,不要一个个add。超过200个Marker建议用聚合。
  • 定位频率: 导航时定位频率高,但普通展示场景下,每5秒定位一次就够了。省电也省流量。
  • 内存泄漏: 地图SDK内部持有Activity引用,一定要在onDestroy里调用mapView.onDestroy()AMapNavi.destroy()

最后说一句: 地图开发,调试起来比较麻烦。模拟器不支持定位,真机调试是必须的。我建议准备一台旧手机专门跑地图测试,省得天天插拔数据线。

好了,这一讲的内容就到这里。地图与导航的功能,说白了就是「定位 + 搜索 + 路线 + 导航」四个模块的组合。你只要把每个模块的API摸透,剩下的就是业务逻辑的拼装了。


公众号:蓝海资料掘金营,微信deep3321