You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

React Native应用GPS开关后无法获取位置,Promise无响应求助

我之前做Android原生定位模块时碰到过几乎一模一样的问题,结合你的代码和描述来看,核心问题出在GPS定位的超时处理、状态监听缺失以及请求堆积这几个点上,给你梳理具体的修复方案:

1. 给定位请求添加超时机制(最关键的修复)

你的forceSingleGPSLocationUpdate方法调用了requestSingleUpdate,但完全没有超时处理——一旦GPS搜星慢(比如室内、信号差)或者暂时无法获取位置,Promise就会一直处于pending状态,直到GPS终于拿到位置才会批量resolve,这就是你看到“多次切换后突然全部解决”的原因。

修改代码,添加超时逻辑:

@RequiresPermission(
    anyOf = [
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.ACCESS_FINE_LOCATION
    ]
)
fun forceSingleGPSLocationUpdate(promise: Promise) {
    val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
    if (locationManager === null) {
        Log.e(LOG_TAG, "Location Manager is null")
        promise.reject(LOG_TAG, Exception("Location Manager is null"))
        return
    }

    val locationListener = object : LocationListener {
        override fun onLocationChanged(location: Location?) {
            // 获取到位置后,先移除监听,避免重复回调
            locationManager.removeUpdates(this)
            if (location === null) {
                Log.e(LOG_TAG, "Location changed is null")
                promise.reject(LOG_TAG, Exception("Location changed is null"))
                return
            }
            Log.v(LOG_TAG, "Resolving promise with location")
            promise.resolve(convertLocationToJSON(location))
        }

        override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {
            // 当GPS状态变为不可用时,直接reject
            if (status == LocationProvider.OUT_OF_SERVICE || status == LocationProvider.TEMPORARILY_UNAVAILABLE) {
                locationManager.removeUpdates(this)
                promise.reject(LOG_TAG, "GPS provider unavailable")
            }
        }

        override fun onProviderEnabled(provider: String) {}

        override fun onProviderDisabled(provider: String) {
            // GPS被关闭时,终止请求
            locationManager.removeUpdates(this)
            promise.reject(LOG_TAG, "GPS provider disabled")
        }
    }

    try {
        locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, locationListener, null)
        // 设置10秒超时,可根据需求调整
        Handler(Looper.getMainLooper()).postDelayed({
            // 检查Promise是否还未完成,是的话就reject
            if (!promise.isRejected && !promise.isResolved) {
                locationManager.removeUpdates(locationListener)
                promise.reject(LOG_TAG, "GPS location request timed out")
            }
        }, 10000)
    } catch (e: SecurityException) {
        Log.e(LOG_TAG, e.message, e)
        promise.reject(LOG_TAG, e)
    } catch (e: Exception) {
        Log.e(LOG_TAG, e.message, e)
        promise.reject(LOG_TAG, e)
    }
}

2. 完善LastKnownLocation为空时的降级逻辑

getLastGPSLocation获取到null时,你直接reject了Promise,但这时候可以自动触发强制获取新位置的逻辑,而不是让请求直接失败:

@RequiresPermission(
    anyOf = [
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.ACCESS_FINE_LOCATION
    ]
)
fun getLastGPSLocation(promise: Promise) {
    val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
    if (locationManager === null) {
        Log.e(LOG_TAG, "Location Manager is null")
        promise.reject(LOG_TAG, Exception("Location Manager is null"))
        return
    }
    try {
        val lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
        if (lastKnownLocation === null) {
            Log.w(LOG_TAG, "Last known location is null, falling back to fresh GPS request")
            // 降级请求新位置
            forceSingleGPSLocationUpdate(promise)
            return
        }
        Log.v(LOG_TAG, "Resolving promise with last known location")
        promise.resolve(convertLocationToJSON(lastKnownLocation))
    } catch (e: SecurityException) {
        Log.e(LOG_TAG, e.message, e)
        promise.reject(LOG_TAG, e)
    } catch (e: Exception) {
        Log.e(LOG_TAG, e.message, e)
        promise.reject(LOG_TAG, e)
    }
}

3. 优化Provider可用性检查

你的areProvidersAvailable()只检查了provider是否enabled,但enabled不代表provider已经就绪(比如GPS刚打开还在搜星),可以优化为检查可用的provider

fun areProvidersAvailable(): Boolean {
    val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
    return try {
        // 获取当前可用的定位provider(true表示只返回enabled且可用的)
        val availableProviders = lm.getProviders(true)
        availableProviders.contains(LocationManager.GPS_PROVIDER) || availableProviders.contains(LocationManager.NETWORK_PROVIDER)
    } catch (ex: Exception) {
        Log.e(LOG_TAG, ex.toString())
        false
    }
}

4. 处理GPS状态变化的广播监听(可选但推荐)

注册一个广播接收器,监听GPS开关状态变化,当GPS从关闭变为打开时,可以主动清理之前pending的请求,或者触发一次定位:

// 在LocationFetcher类中添加广播接收器逻辑
private val gpsStatusReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action == LocationManager.PROVIDERS_CHANGED_ACTION) {
            // GPS状态变化,这里可以根据需求处理,比如通知上层刷新定位状态
            Log.d(LOG_TAG, "GPS provider status changed")
        }
    }
}

// 在类初始化时注册广播
init {
    val filter = IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION)
    context.registerReceiver(gpsStatusReceiver, filter)
}

// 记得在合适的时机注销广播(比如React Native模块销毁时)
fun destroy() {
    context.unregisterReceiver(gpsStatusReceiver)
}

最后补充:完善Play Services检查

你的checkForPlayServices方法代码不完整,确保完整实现,比如:

internal fun checkForPlayServices(): Boolean {
    val googleApiAvailability = GoogleApiAvailability.getInstance()
    val resultCode = googleApiAvailability.isGooglePlayServicesAvailable(context)
    return resultCode == ConnectionResult.SUCCESS
}

这些修改应该能解决你碰到的Promise pending、批量resolve、状态切换无响应的问题,核心就是给定位请求加超时、避免请求堆积、完善状态监听。

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

火山引擎 最新活动