puzhibing
2026-08-21 5fa94b3718b4a78a4fcea4c97652b0ef20b7de3a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
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<String>, allGranted: Boolean) { // 权限同意
                    hasShownDeniedDialog = false
                    initLocation()
                }
 
                override fun onDenied(permissions: MutableList<String>, 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
                }
            }
        }
    }
}