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

相关推荐
aqi0037 分钟前
FFmpeg开发笔记(一百零二)国产的音视频移动开源工具FFmpegAndroid
android·ffmpeg·kotlin·音视频·直播·流媒体
星间都市山脉1 小时前
Android 谷歌 CTS 完整测试
android
nianniannnn1 小时前
快应用day2项目架构
android·快应用
用户83352502537852 小时前
ViewModel详细解析
android
问心无愧05132 小时前
ctf show web入门91
android·前端·笔记
YF02112 小时前
Android App 高效升级指南:OkDownload 多线程断点续传与全版本安装适配
android·okhttp·app
huangliang07032 小时前
MySQL 中的 distinct 和 group by 哪个效率更高?
android·数据库·mysql
程思扬2 小时前
Android 悬浮窗状态错乱终极解决方案:告别 onResume
android·网络
逸Y 仙X3 小时前
文章二十九:ElasticSearch分桶聚合
android·大数据·elasticsearch·搜索引擎·全文检索
陆业聪3 小时前
网络监控与容灾:让网络问题无处遁形
android·性能优化·启动优化