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

相关推荐
AirDroid_cn1 小时前
Android 15 :如何让特定应用通知仅在锁屏显示横幅?
android·智能手机
zh_xuan2 小时前
android ARouter配置降级服务
android·arouter
常利兵2 小时前
Android开发秘籍:接口加解密全解析
android
xuboyok22 小时前
MySQL中ON DUPLICATE KEY UPDATE的介绍与使用、批量更新、存在即更新不存在则插入
android·数据库·mysql
羑悻的小杀马特3 小时前
LangChain实战:工具调用+结构化输出,让AI从“聊天“变“干活“
android·人工智能·langchain
秋饼4 小时前
[EXPLAIN:SQL 执行计划分析与性能优化实战]
android·sql·性能优化
robotx4 小时前
如何从framework层面跳过app开屏广告(简单模拟)
android
毕设源码-朱学姐5 小时前
【开题答辩全过程】以 基于Android的大学生兼职APP设计为例,包含答辩的问题和答案
android
tongxh4235 小时前
MySQL Workbench菜单汉化为中文
android·数据库·mysql