package com.xw.common.utils import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.location.LocationManager import android.os.Build import android.os.Bundle import android.provider.Settings import androidx.core.net.toUri import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent import com.amap.api.location.AMapLocation import com.amap.api.location.AMapLocationClient import com.amap.api.location.AMapLocationClientOption import com.amap.api.location.AMapLocationListener import com.amap.api.maps.model.LatLng import com.blankj.utilcode.util.ToastUtils import com.hjq.permissions.OnPermissionCallback import com.hjq.permissions.Permission import com.hjq.permissions.XXPermissions import com.xw.common.CommonDialog import com.xw.common.Constants /** * GPS * */ class GpsHelper( var context: Context, var listener: OnMapLocation, var isOnceLocation: Boolean, // 是否定位一次 var isAutomatic: Boolean = true,// isAutomatic 是否自动开启定位 var savedInstanceState: Bundle?, var interval: Long = 2000L,//默认定位间隔 ) : AMapLocationListener, LifecycleObserver { val TAG = "zmc-GpsHelper" // 声明AMapLocationClient类对象 private var mLocationClient: AMapLocationClient? = null // 声明mLocationOption对象 private lateinit var mLocationOption: AMapLocationClientOption // GPS 状态广播接收器 private var gpsStatusReceiver: GpsStatusReceiver? = null // 标记是否正在监听 GPS 状态(用于避免重复注册) private var isWatchingGpsStatus = false // 是否已经发起过位置权限申请(避免每次 onResume 都重复 request 导致权限 Activity 反复拉起、onResume 死循环) private var hasRequestedPermission = false // 是否已经弹过"权限被拒绝"提示框(避免多次弹出) private var hasShownDeniedDialog = false // 定位权限组 private val locationPermissions = arrayOf(Permission.ACCESS_FINE_LOCATION, Permission.ACCESS_COARSE_LOCATION) @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun resume() { LogUtils.i(TAG, "onResume isAutomatic = $isAutomatic") if (savedInstanceState == null && isAutomatic) checkAndStartLocation() } /** * 检查定位状态并决定是否开始定位或监听 GPS 状态 */ private fun checkAndStartLocation() { if (isLocationEnabledModern()) { // 定位已开启,停止监听(如果正在监听),直接开始定位 stopWatchingGpsStatus() startLocation() } else { // 定位未开启,先回调 null 通知外部,然后开始监听 GPS 状态变化 LogUtils.w(TAG, "定位未开启,开始监听 GPS 状态变化") listener.location(null) startWatchingGpsStatus() } } fun startLocation() { // 已授权:直接定位,绝不再调 request()。 // request() 在 XXPermissions 18+ 会拉起一个透明的权限 Activity, // 权限被永久拒绝时该 Activity 会立即返回并 finish,从而再次触发宿主 onResume, // 若每次 onResume 都无条件 request 就会形成 onResume <-> 权限 Activity 的死循环。 if (XXPermissions.isGranted(context, locationPermissions)) { hasShownDeniedDialog = false // 已授权,重置提示状态,便于下次被撤销时能再次提醒 initLocation() return } // 之前已经申请过且现在仍未授权 => 已被拒绝:不再 request,避免死循环,只提示一次 if (hasRequestedPermission) { listener.location(null) showDeniedDialogOnce() return } // 首次申请,最多触发一次 hasRequestedPermission = true XXPermissions.with(context).permission(locationPermissions) .request(object : OnPermissionCallback { override fun onGranted(permissions: MutableList, allGranted: Boolean) { // 权限同意 hasShownDeniedDialog = false initLocation() } override fun onDenied(permissions: MutableList, doNotAskAgain: Boolean) { super.onDenied(permissions, doNotAskAgain) //权限禁止 listener.location(null) showDeniedDialogOnce() } }) } /** * 位置权限被拒绝后,弹框告知用户(同一生命周期内只弹一次,避免重复打扰) */ private fun showDeniedDialogOnce() { if (hasShownDeniedDialog) return hasShownDeniedDialog = true CommonDialog.showLocationNotGrantedTipsDialog(context) } private fun initLocation() { if (mLocationClient == null) { mLocationClient = AMapLocationClient(context) mLocationClient?.let { mLocationClient -> // 设置定位回调监听 mLocationClient.setLocationListener(this) // 初始化定位参数 mLocationOption = AMapLocationClientOption() // 设置定位模式为高精度模式,Battery_Saving为低功耗模式,Device_Sensors是仅设备模式 mLocationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy // 设置是否返回地址信息(默认返回地址信息) mLocationOption.isNeedAddress = true // 设置是否只定位一次,默认为false mLocationOption.isOnceLocation = isOnceLocation // 设置是否强制刷新WIFI,默认为强制刷新 // mLocationOption.isWifiActiveScan = true // 设置是否允许模拟位置,默认为false,不允许模拟位置 mLocationOption.isMockEnable = false // 设置定位间隔,单位毫秒,默认为2000ms mLocationOption.interval = interval //返回方位角 mLocationOption.isSensorEnable = false // 给定位客户端对象设置定位参数 mLocationClient.setLocationOption(mLocationOption) } } // 启动定位 mLocationClient?.startLocation() LogUtils.w(TAG, "mLocationClient?.startLocation") } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun pause() { mLocationClient?.let { mLocationClient -> mLocationClient.stopLocation() LogUtils.d(TAG, "onPause") } } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun destroy() { // 停止监听 GPS 状态 stopWatchingGpsStatus() mLocationClient?.let { mLocationClient -> mLocationClient.onDestroy() LogUtils.e(TAG, "onDestroy") } } /** * 开始监听 GPS 状态变化 */ private fun startWatchingGpsStatus() { if (isWatchingGpsStatus) { LogUtils.d(TAG, "已经在监听 GPS 状态,无需重复注册") return } gpsStatusReceiver = GpsStatusReceiver { isEnabled -> LogUtils.i(TAG, "GPS 状态变化: isEnabled = $isEnabled") if (isEnabled) { // GPS 已开启,停止监听并开始定位 LogUtils.i(TAG, "检测到 GPS 已开启,自动开始定位") stopWatchingGpsStatus() startLocation() } } gpsStatusReceiver?.let { receiver -> val filter = IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION) context.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED) isWatchingGpsStatus = true LogUtils.i(TAG, "已注册 GPS 状态监听") } } /** * 停止监听 GPS 状态变化 */ private fun stopWatchingGpsStatus() { if (!isWatchingGpsStatus) return gpsStatusReceiver?.let { receiver -> try { context.unregisterReceiver(receiver) LogUtils.i(TAG, "已注销 GPS 状态监听") } catch (e: IllegalArgumentException) { // 接收器可能已经被注销 LogUtils.e(TAG, "注销 GPS 监听失败: ${e.message}") } } gpsStatusReceiver = null isWatchingGpsStatus = false } fun isLocationEnabledModern(): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // Android 9.0 (API 28) 及以上 val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager locationManager.isLocationEnabled } else { // 兼容旧版本 val mode = Settings.Secure.getInt( context.contentResolver, Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF ) mode != Settings.Secure.LOCATION_MODE_OFF } } override fun onLocationChanged(location: AMapLocation?) { location?.let { if (it.longitude != 0.0 && it.latitude != 0.0) { Constants.longitude = it.longitude Constants.latitude = it.latitude } LogUtils.e(TAG, "----------------------------------") LogUtils.w(TAG, "onLocationChanged: ${location.errorCode} ${location.errorInfo}") // 定位成功,详细打印地址信息 LogUtils.w(TAG, "经度: ${it.longitude}") LogUtils.w(TAG, "维度: ${it.latitude}") LogUtils.w(TAG, "国家: ${it.country}") LogUtils.w(TAG, "省份: ${it.province}") LogUtils.w(TAG, "城市: ${it.city}") LogUtils.w(TAG, "区县: ${it.district}") LogUtils.w(TAG, "街道: ${it.street}") LogUtils.w(TAG, "门牌号: ${it.streetNum}") LogUtils.w(TAG, "完整地址: ${it.address}") LogUtils.w(TAG, "城市编码: ${it.cityCode}") LogUtils.w(TAG, "区域编码: ${it.adCode}") LogUtils.w(TAG, "POI名称: ${it.poiName}") LogUtils.w(TAG, "AOI名称: ${it.aoiName}") LogUtils.w(TAG, "海拔: ${it.altitude} 米") LogUtils.w(TAG, "定位精度: ${it.accuracy} 米") LogUtils.w(TAG, "卫星数: ${it.satellites}") LogUtils.w(TAG, "定位提供者: ${it.provider}") LogUtils.w(TAG, "定位类型: ${it.locationType}") } listener.location(location) } interface OnMapLocation { fun location(pLocation: AMapLocation?) } companion object { val gaodePackageName = "com.autonavi.minimap" // 高德地图包名 fun navigate(start: LatLng? = null, end: LatLng, context: Context) { if (!isInstallPackage(gaodePackageName)) { ToastUtils.showLong("请安装导航应用") } else { val builder = StringBuilder("amapuri://route/plan?sourceApplication=maxuslife") start?.let { builder.append("&sname=").append("起点") .append("&slat=").append(start.latitude) .append("&slon=").append(start.longitude) } builder.append("&dlat=").append(end.latitude) .append("&dlon=").append(end.longitude) .append("&dname=").append("终点") .append("&dev=0") .append("&t=0") val uriString = builder.toString() val intent = Intent(Intent.ACTION_VIEW) intent.setPackage(gaodePackageName) intent.data = uriString.toUri() context.startActivity(intent) } } private fun isInstallPackage(packageName: String): Boolean { return try { AppGlobals.getApplication().packageManager.getPackageInfo(packageName, 0) true } catch (e: PackageManager.NameNotFoundException) { try { val intent = AppGlobals.getApplication().packageManager.getLaunchIntentForPackage( packageName ) intent != null } catch (ex: Exception) { false } } } } }