Иногда не удается получить текущее местоположение после выключения и включения GPS - PullRequest
0 голосов
/ 30 мая 2018

У меня есть приложение React-Native, которое должно использовать местоположение GPS.Ниже представлен класс 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)
  }
}

Не знаюНе знаю, важно ли это, но мы получаем местоположение с помощью Foreground Service, который представлен ниже.

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>
...