You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Kotlin实现Android ACCESS_FINE_LOCATION精确定位功能?

Android ACCESS_FINE_LOCATION 精确定位 Kotlin 实现方案

两类实现方案适用场景

1. 改进版生产环境实现

该方案逻辑完善,核心优势如下:

  • 绑定Activity生命周期,应用暂停或停止时自动终止GPS更新,降低不必要功耗,符合隐私合规要求
  • 内置10秒GPS看门狗计时器,可检测定位异常,适配Android Studio模拟器定位不稳定的问题
  • 权限校验逻辑完整,异常处理覆盖全面

适用场景:正式上线的生产环境应用,尤其是导航、运动记录、实时位置上报类需要长期依赖定位的APP。

const val PERMISSIONS_REQUEST_ACCESS_LOCATION = 99 // 任意正整数即可

class MainActivity : AppCompatActivity() {

    private lateinit var tvCurrentTime : TextView
    private lateinit var tvSpeed : TextView
    private lateinit var tvBearing : TextView

    private var myTimer: Timer = Timer()

    private var sHourMinuteSecond : String = "00:00:00"

    private lateinit var fusedLocationClient : FusedLocationProviderClient
    private lateinit var locationCallback: LocationCallback
    private var gpsWatchDog : Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        tvCurrentTime = findViewById<View>(R.id.show_time_textview) as TextView
        tvSpeed = findViewById<View>(R.id.speed_textview) as TextView
        tvBearing = findViewById<View>(R.id.bearing_textview) as TextView

        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this@MainActivity) as FusedLocationProviderClient
        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                super.onLocationResult(locationResult)
                processNewLocation(locationResult.lastLocation) // 仅处理最新的定位结果
            }
        }
    }

    private fun checkPermissionsForGPSUpdates() {
        if ((ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) ||
            (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)) {
            // Android系统要求同时申请模糊和精确定位权限
            writeToLog("checkSelfPermission  权限未授予,发起权限申请")
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), PERMISSIONS_REQUEST_ACCESS_LOCATION)
            // 申请权限时会触发OnPause()
            // 用户授予权限后会回调OnResume(),会再次调用checkPermissionsForGPSUpdates()启动定位
            return
        }
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            // 仅在获得精确定位权限时启动定位
            writeToLog("ACCESS_FINE_LOCATION 权限已授予,启动GPS更新")
            val locationRequest: LocationRequest = LocationRequest.create().apply {
                interval = 1000
                fastestInterval = 500
                priority = LocationRequest.PRIORITY_HIGH_ACCURACY
            }
            try {
                if (!fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()).isSuccessful) {
                    writeToLog("requestLocationUpdates() 调用失败") // 相关问题反馈多为谷歌服务错误
                }
            } catch (unlikely: SecurityException) {
                writeToLog("定位权限丢失,无法启动定位更新: $unlikely")
            }
        } else {
            writeToLog("权限未授予,退出")
            Toast.makeText(this@MainActivity, "应用需要GPS权限才能运行" , Toast.LENGTH_LONG).show()
            finish()
        }
    }

    private fun updateTimeString() {
        // 由计时器调用,请勿在此处直接修改UI
        val systemTime = GregorianCalendar()
        sHourMinuteSecond = SimpleDateFormat("HH:mm:ss").format(systemTime.timeInMillis)

        // 使用runOnUiThread方法更新界面
        this.runOnUiThread(updateDisplay)
    }

    private val updateDisplay = Runnable {
        // 该方法运行在UI线程
        // 为避免模拟器崩溃,逻辑尽量精简
        tvCurrentTime.text = sHourMinuteSecond

        if (++gpsWatchDog > 10) {
            tvBearing.text = "无GPS信号"
        }

    }

    private fun processNewLocation(latestLocation : Location) {
        // 收到定位更新,重置看门狗计时器
        gpsWatchDog = 0
        // 处理最新的定位结果
        // 示例为展示GPS时间
        val sSpeed = SimpleDateFormat("HH:mm:ss").format(latestLocation.time)
        tvSpeed.text = sSpeed
        val sBearing = latestLocation.bearing.roundToInt().toString() // 不保留小数
        tvBearing.text = sBearing
    }

    override fun onResume() {
        super.onResume()
        writeToLog("调用 onResume()")
        // 校验权限后启动GPS更新
        checkPermissionsForGPSUpdates()
        val currentSystemTime = System.currentTimeMillis()
        // 启动计时器更新时间
        myTimer = Timer()
        val delayToNextWholeSecond = ((currentSystemTime / 1000) + 1) * 1000 - currentSystemTime // 对齐整秒更新
        myTimer.scheduleAtFixedRate(object : TimerTask() {
            override fun run() {
                updateTimeString()
            }
        }, delayToNextWholeSecond, 1000)
    }

    override fun onPause() {
        super.onPause()
        writeToLog("调用 onPause()")
        myTimer.cancel()  // 停止计时器
        myTimer.purge()
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }

    override fun onStop() {
        super.onStop()
        writeToLog("调用 onStop()")
        myTimer.cancel()
        myTimer.purge()
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }

    private fun writeToLog(message : String) {
        Log.d("SmallGPSDemo", message)
    }

}

2. 极简版演示实现

该方案代码精简,核心优势如下:

  • 采用新版Activity Result API申请权限,逻辑直观易读
  • 无冗余代码,可快速验证定位功能可用性

适用场景:Demo开发、功能验证、学习测试场景,不建议直接用于生产环境,未绑定生命周期,退后台后依然会持续请求定位,耗电且不符合合规要求。

class MainActivity : AppCompatActivity() {

    private var debugTextView : TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        debugTextView = findViewById<View>(R.id.debug_textview) as TextView

        // 校验权限并启动定位更新
        requestPermission.launch(Manifest.permission.ACCESS_FINE_LOCATION)

    }

    private fun processNewLocation(latestLocation : Location) {
        // 处理定位结果
        debugTextView!!.text = SimpleDateFormat("hh:mm:ss.SSS").format(System.currentTimeMillis())
    }


    @SuppressLint("MissingPermission")
    private val requestPermission =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            if (isGranted) {
                // 权限已授予,启动GPS更新
                val locationRequest: LocationRequest = LocationRequest.create().apply {
                    interval = 1000
                    fastestInterval = 500
                    priority = LocationRequest.PRIORITY_HIGH_ACCURACY
                }
                val locationCallback  = object : LocationCallback() {
                    override fun onLocationResult(locationResult: LocationResult) {
                        super.onLocationResult(locationResult)
                        for (location in locationResult.locations) {
                            processNewLocation(location) // 配置仅返回单个定位结果,遍历兼容异常情况
                        }
                    }
                }
                val fusedLocationClient = LocationServices.getFusedLocationProviderClient(this@MainActivity)
                fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
            } else {
                Log.d("DEBUG", "权限被拒绝 ")
                Toast.makeText(this@MainActivity, "应用需要GPS权限才能运行", Toast.LENGTH_LONG).show()
                finish()
            }
        }
}

必备配置:AndroidManifest 权限声明

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.reallysmallgpscode">

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.ReallySmallGPSCode">
        <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>

不同Android系统版本适配优化建议

  • Android 6.0 (API 23)及以上:必须动态申请定位权限,不可仅在Manifest中声明,用户拒绝权限后需给出友好引导,避免直接闪退。
  • Android 10 (API 29)及以上:如果需要后台定位,需额外申请ACCESS_BACKGROUND_LOCATION权限,且必须先获得前台定位权限后再申请后台权限,同时要向用户明确说明后台定位的用途,否则会被Google Play审核驳回。
  • Android 12 (API 31)及以上:如果精确定位不是应用核心功能,可同时提供模糊定位选项供用户选择,申请权限时可同时请求ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION,允许用户自主选择授权精度。
  • Android 13 (API 33)及以上:如果应用需要后台持续定位,需提交谷歌位置权限使用说明审核,建议搭配前台服务+通知栏提示,明确告知用户正在使用定位功能。

内容的提问来源于stack exchange,提问作者RatherBeSailing

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 22:12:03