Android 调起第三方导航

Android 调起第三方导航

一、功能说明

实现 Android 应用内调起高德、百度、腾讯地图导航,包含:

  • 自动坐标转换(WGS84/GCJ02/BD09)
  • Android 11+ 包可见性适配
  • 安装检测 + 自动降级
  • 驾车/步行/骑行导航

二、权限配置(必须)

AndroidManifest.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Android 11+ 包可见性声明 -->
    <queries>
        <package android:name="com.autonavi.minimap" />
        <package android:name="com.baidu.BaiduMap" />
        <package android:name="com.tencent.map" />
    </queries>

    <application>
        <activity android:name=".MainActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

三、坐标转换工具类

CoordinateConverter.kt

kotlin 复制代码
import kotlin.math.*

object CoordinateConverter {
    private const val PI = 3.1415926535897932384626
    private const val A = 6378245.0
    private const val EE = 0.00669342162296594323

    fun wgs84ToGcj02(lng: Double, lat: Double): DoubleArray {
        if (outOfChina(lng, lat)) return doubleArrayOf(lng, lat)
        var dLat = transformLat(lng - 105.0, lat - 35.0)
        var dLng = transformLng(lng - 105.0, lat - 35.0)
        val radLat = lat / 180.0 * PI
        var magic = sin(radLat)
        magic = 1 - EE * magic * magic
        val sqrtMagic = sqrt(magic)
        dLat = (dLat * 180.0) / ((A * (1 - EE)) / (magic * sqrtMagic) * PI)
        dLng = (dLng * 180.0) / (A / sqrtMagic * cos(radLat) * PI)
        return doubleArrayOf(lng + dLng, lat + dLat)
    }

    fun gcj02ToBd09(lng: Double, lat: Double): DoubleArray {
        val z = sqrt(lng * lng + lat * lat) + 0.00002 * sin(lat * PI * 3000.0 / 180.0)
        val theta = atan2(lat, lng) + 0.000003 * cos(lng * PI * 3000.0 / 180.0)
        return doubleArrayOf(z * cos(theta) + 0.0065, z * sin(theta) + 0.006)
    }

    fun bd09ToGcj02(lng: Double, lat: Double): DoubleArray {
        val x = lng - 0.0065
        val y = lat - 0.006
        val z = sqrt(x * x + y * y) - 0.00002 * sin(y * PI * 3000.0 / 180.0)
        val theta = atan2(y, x) - 0.000003 * cos(x * PI * 3000.0 / 180.0)
        return doubleArrayOf(z * cos(theta), z * sin(theta))
    }

    private fun outOfChina(lng: Double, lat: Double): Boolean {
        return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271
    }

    private fun transformLat(x: Double, y: Double): Double {
        var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(abs(x))
        ret += (20.0 * sin(6.0 * x * PI) + 20.0 * sin(2.0 * x * PI)) * 2.0 / 3.0
        ret += (20.0 * sin(y * PI) + 40.0 * sin(y / 3.0 * PI)) * 2.0 / 3.0
        ret += (160.0 * sin(y / 12.0 * PI) + 320 * sin(y * PI / 30.0)) * 2.0 / 3.0
        return ret
    }

    private fun transformLng(x: Double, y: Double): Double {
        var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(abs(x))
        ret += (20.0 * sin(6.0 * x * PI) + 20.0 * sin(2.0 * x * PI)) * 2.0 / 3.0
        ret += (20.0 * sin(x * PI) + 40.0 * sin(x / 3.0 * PI)) * 2.0 / 3.0
        ret += (150.0 * sin(x / 12.0 * PI) + 300.0 * sin(x / 30.0 * PI)) * 2.0 / 3.0
        return ret
    }
}

四、导航工具类(核心)

NavigationUtils.kt

kotlin 复制代码
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.widget.Toast

object NavigationUtils {
    private const val AMAP = "com.autonavi.minimap"
    private const val BAIDU = "com.baidu.BaiduMap"
    private const val TENCENT = "com.tencent.map"

    fun startNavigation(
        context: Context,
        wgs84Lat: Double,
        wgs84Lng: Double,
        name: String,
        navType: Int = 0,
        prefer: Int = 0
    ) {
        val (lat, lng) = when (prefer) {
            0, 2 -> {
                val gcj = CoordinateConverter.wgs84ToGcj02(wgs84Lng, wgs84Lat)
                gcj[1] to gcj[0]
            }
            1 -> {
                val gcj = CoordinateConverter.wgs84ToGcj02(wgs84Lng, wgs84Lat)
                val bd = CoordinateConverter.gcj02ToBd09(gcj[0], gcj[1])
                bd[1] to bd[0]
            }
            else -> wgs84Lat to wgs84Lng
        }

        val intent = when (prefer) {
            0 -> amapIntent(lat, lng, name, navType)
            1 -> baiduIntent(lat, lng, name, navType)
            2 -> tencentIntent(lat, lng, name, navType)
            else -> amapIntent(lat, lng, name, navType)
        }

        val pkg = when (prefer) {
            0 -> AMAP
            1 -> BAIDU
            2 -> TENCENT
            else -> AMAP
        }

        if (isInstalled(context, pkg)) {
            context.startActivity(intent)
        } else {
            tryFallback(context, lat, lng, name, navType, prefer)
        }
    }

    private fun amapIntent(lat: Double, lng: Double, name: String, type: Int): Intent {
        val t = when (type) { 0 -> "0"; 1 -> "1"; 2 -> "2"; else -> "0" }
        val uri = Uri.parse("amapuri://route/plan/?dlat=$lat&dlng=$lng&dname=$name&dev=0&t=$t")
        return Intent(Intent.ACTION_VIEW, uri)
    }

    private fun baiduIntent(lat: Double, lng: Double, name: String, type: Int): Intent {
        val mode = when (type) { 0 -> "driving"; 1 -> "walking"; 2 -> "riding"; else -> "driving" }
        val uri = Uri.parse("baidumap://map/direction/?destination=latlng:$lat,$lng|name:$name&mode=$mode")
        return Intent(Intent.ACTION_VIEW, uri)
    }

    private fun tencentIntent(lat: Double, lng: Double, name: String, type: Int): Intent {
        val t = when (type) { 0 -> "drive"; 1 -> "walk"; 2 -> "bike"; else -> "drive" }
        val uri = Uri.parse("qqmap://map/routeplan?type=$t&to=$name&tocoord=$lat,$lng")
        return Intent(Intent.ACTION_VIEW, uri)
    }

    private fun isInstalled(context: Context, pkg: String): Boolean {
        return try {
            context.packageManager.getApplicationInfo(pkg, 0)
            true
        } catch (e: Exception) {
            false
        }
    }

    private fun tryFallback(ctx: Context, lat: Double, lng: Double, name: String, type: Int, prefer: Int) {
        when (prefer) {
            0 -> when {
                isInstalled(ctx, BAIDU) -> ctx.startActivity(baiduIntent(lat, lng, name, type))
                isInstalled(ctx, TENCENT) -> ctx.startActivity(tencentIntent(lat, lng, name, type))
                else -> toast(ctx)
            }
            1 -> when {
                isInstalled(ctx, AMAP) -> ctx.startActivity(amapIntent(lat, lng, name, type))
                isInstalled(ctx, TENCENT) -> ctx.startActivity(tencentIntent(lat, lng, name, type))
                else -> toast(ctx)
            }
            2 -> when {
                isInstalled(ctx, AMAP) -> ctx.startActivity(amapIntent(lat, lng, name, type))
                isInstalled(ctx, BAIDU) -> ctx.startActivity(baiduIntent(lat, lng, name, type))
                else -> toast(ctx)
            }
            else -> toast(ctx)
        }
    }

    private fun toast(context: Context) {
        Toast.makeText(context, "请安装高德/百度/腾讯地图", Toast.LENGTH_SHORT).show()
    }
}

五、调用示例

kotlin 复制代码
// 天安门 WGS84 坐标
NavigationUtils.startNavigation(
    context = this,
    wgs84Lat = 39.908823,
    wgs84Lng = 116.397470,
    name = "天安门",
    navType = 0,        // 0驾车 1步行 2骑行
    prefer = 0          // 0高德 1百度 2腾讯
)

六、常见问题

1. 已安装高德却提示未安装

原因 :Android 11+ 包可见性限制
解决 :必须在 AndroidManifest.xml 添加 <queries>

2. 导航位置偏移

原因:坐标系不统一

  • 高德 / 腾讯:GCJ02
  • 百度:BD09
  • GPS 原始:WGS84
    解决 :使用 CoordinateConverter 自动转换

3. 调起失败

检查包名:

  • 高德:com.autonavi.minimap
  • 百度:com.baidu.BaiduMap
  • 腾讯:com.tencent.map

七、测试命令

bash 复制代码
adb shell pm list packages | grep com.autonavi.minimap

相关推荐
数智工坊7 小时前
机器人运动控制:采样、优化与学习三大流派深度对比与实战
android·学习·机器人
故渊at8 小时前
第二板块:Android 四大组件标准化学理 | 第八篇:Service 后台执行实体与优先级
android·gitee·service·前台服务·后台服务
会Tk矩阵群控的小木9 小时前
安卓群控系统对于游戏工作室实战教程
android·运维·游戏·adb·开源软件·个人开发
qeen879 小时前
【C++】类与对象之类的默认成员函数(二)
android·c语言·开发语言·c++·笔记·学习
故渊at10 小时前
第二板块:Android 四大组件标准化学理 | 第九篇:BroadcastReceiver 事件分发与有序广播
android·gitee·broadcast·广播·动态注册·静态注册
JohnnyDeng9410 小时前
【Android】Room 数据库高级用法与性能调优:从查询瓶颈到毫秒级响应
android·性能优化·kotlin·room
zeqinjie10 小时前
Flutter 折叠屏 iPad / 宽屏适配实践
android·前端·flutter
ab_dg_dp10 小时前
Android 17+ 提取 AIDL 生成 Java 文件的实用脚本
android·java·python
Arrom11 小时前
DLNA 渲染端排障实战:从 20s 卡顿到 stale subscriber 的两周追凶之旅
android·java