关闭和关闭GPS后,有时无法获取当前位置

时间:2018-05-30 10:43:18

标签: android react-native kotlin gps android-gps

我有一个需要使用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>

0 个答案:

没有答案