我有一个需要使用GPS位置的React-Native应用程序。下面有一个LocationFetcher
类,负责通过强制新位置或获取最后一个位置来获取当前位置。
假设以固定间隔调用getLocation()
方法。当我关闭GPS时,我会收到消息No location provider found.
,这很好。但当我转 GPS 时,我会得到没有消息。更有希望的是既不解决也不拒绝。我可以多次切换GPS,让我们说大约第5次所有的承诺将通过当前位置解决,然后我重复这个过程并再次没有位置。
有时启动另一个使用GPS的应用程序,例如GPS Viewer
会立即解决所有承诺,有时则不会。经常打开和关闭GPS会导致没有问题,有时会很多。有时关闭网络会导致这个问题持续15分钟,之后就会产生影响。
有没有人有GPS这样的问题?
class LocationFetcher(
val context: Context
) {
companion object {
val LOG_TAG = "LOC_FETCHER"
}
/**
* Gets a location "synchronously" as a Promise
*/
fun getLocation(forceNewLocation: Boolean, promise: Promise) {
try {
if (!areProvidersAvailable()) {
promise.reject(NATIVE_ERROR, "No location provider found.")
return
}
if (!checkForPlayServices()) {
promise.reject(NATIVE_ERROR, "Install Google Play Services First and Try Again.")
return
}
if (!hasPermissions()) {
promise.reject(NATIVE_ERROR, "Appropriate permissions not given.")
return
}
/* --------- */
if (forceNewLocation) {
forceSingleGPSLocationUpdate(promise)
return
}
getLastGPSLocation(promise)
} catch (ex: Exception) {
Log.e(TAG, "Native Location Module ERR - " + ex.toString())
promise.reject(NATIVE_ERROR, ex.toString())
}
}
@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.e(LOG_TAG, "Last known location is null")
promise.reject(LOG_TAG, "Last known location is null");
return
}
Log.v(LOG_TAG, "Resolving promise with location")
promise.resolve(convertLocationToJSON(lastKnownLocation))
} catch (e: SecurityException) {
Log.e(LOG_TAG, e.message, e)
promise.reject(LOG_TAG, e)
return
} catch (e: Exception) {
Log.e(LOG_TAG, e.message, e)
promise.reject(LOG_TAG, e)
return
}
}
@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
}
try {
val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location?) {
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) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, locationListener, null)
} catch (e: SecurityException) {
Log.e(LOG_TAG, e.message, e)
promise.reject(LOG_TAG, e)
return
} catch (e: Exception) {
Log.e(LOG_TAG, e.message, e)
promise.reject(LOG_TAG, e)
return
}
}
fun areProvidersAvailable(): Boolean {
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
return try {
lm.isProviderEnabled(LocationManager.GPS_PROVIDER) ||
lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
} catch (ex: Exception) {
Log.e(LOG_TAG, ex.toString())
false
}
}
fun hasPermissions(): Boolean {
return ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
// ~ https://stackoverflow.com/questions/
// 22493465/check-if-correct-google-play-service-available-unfortunately-application-has-s
internal fun checkForPlayServices(): Boolean {
val googleApiAvailability = GoogleApiAvailability.getInstance()
val resultCode = googleApiAvailability.isGooglePlayServicesAvailable(context)
if (resultCode != ConnectionResult.SUCCESS) {
if (googleApiAvailability.isUserResolvableError(resultCode)) {
val map = WritableNativeMap().also {
it.putInt("resultCode", resultCode)
it.putInt("resolutionRequest", PLAY_SERVICES_RESOLUTION_REQUEST)
}
sendLocalEventToModule(LOCAL_PLAY_SERVICES_ERROR, map)
}
return false
}
return true
}
internal fun convertLocationToJSON(l: Location?): WritableMap {
if (l === null) {
return WritableNativeMap().also {
it.putString("error", "Received location was null")
}
}
return WritableNativeMap().also {
it.putDouble("latitude", l.latitude)
it.putDouble("longitude", l.longitude)
it.putDouble("accuracy", l.accuracy.toDouble())
it.putDouble("altitude", l.altitude)
it.putDouble("bearing", l.bearing.toDouble())
it.putString("provider", l.provider)
it.putDouble("speed", l.speed.toDouble())
it.putString("timestamp", l.time.toString())
}
}
internal fun sendLocalEventToModule(eventName: String, data: WritableMap) {
val intent = Intent(eventName).also {
it.putExtra("data", WritableMapWrapper(data))
}
Log.v(TAG, "Sending local event ${eventName}")
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
}
}
我不知道这是否重要,但我们通过前台服务获取位置,如下所示。
class ForegroundLocationService : Service() {
lateinit var locationFetcher : LocationFetcher
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_NOT_STICKY
}
override fun onTaskRemoved(rootIntent: Intent?) {
stopSelf()
}
override fun onCreate() {
super.onCreate()
locationFetcher = LocationFetcher(applicationContext)
/**
* Saving this [ForegroundLocationService] reference to the static variable,
* because when binding a Service using [bindService] it would not stop the
* this service, even though the app would close
*/
LocationModule.foregroundLocationService = this
showNotification()
Log.v(TAG, "Creating Foreground Location Service")
}
override fun onDestroy() {
super.onDestroy()
LocationModule.foregroundLocationService = null
locationFetcher.stopLocationUpdates()
Log.v(TAG, "Destroying Foreground Location Service")
}
fun showNotification() {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
} else {
// In Android versions before Oreo channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
}
val notification = NotificationCompat.Builder(this, channelId)
.setOngoing(true)
.setContentTitle(NOTIFICATION_TITLE)
.setSmallIcon(R.mipmap.ic_notification)
.setTicker(NOTIFICATION_TITLE)
.build()
Log.v(TAG, "Showing a notification")
startForeground(NOTIFICATION_ID, notification)
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String {
val channelId = "app_gps_service"
val channelName = NOTIFICATION_TITLE
val chan = NotificationChannel(
channelId,
channelName,
NotificationManager.IMPORTANCE_LOW
)
chan.lightColor = Color.BLUE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
Log.v(TAG, "Created notification channel, because SDK version is ${Build.VERSION.SDK_INT}")
return channelId
}
companion object {
val TAG = "ForegroundLocationSvc"
val NOTIFICATION_ID = 101
val NOTIFICATION_TITLE = "GPS Service"
@JvmStatic
fun start(context: Context) {
Log.v(TAG, "Starting Foreground Location Service")
val intent = Intent(context, ForegroundLocationService::class.java)
context.startService(intent)
}
}
}
下面有我们的清单
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myapp">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-feature android:name="android.hardware.location.gps" />
<!-- push notifications permissions -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<permission
android:name="com.ticketing.permission.C2D_MESSAGE"
android:protectionLevel="signature"/>
<uses-permission android:name="com.ticketing.permission.C2D_MESSAGE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<permission
android:name="android.permission.ACCESS_COARSE_LOCATION"
android:protectionLevel="signature"/>
<permission
android:name="android.permission.ACCESS_FINE_LOCATION"
android:protectionLevel="signature"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:name=".MainApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
<!-- GPS Receiver -->
<receiver android:name=".gps.GpsLocationReceiver">
<intent-filter>
<action android:name="android.location.PROVIDERS_CHANGED"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
<service
android:name=".location.ForegroundLocationService"
android:description="@string/foreground_location_service_desc"
android:exported="false"
android:stopWithTask="false">
</service>
</application>
</manifest>