отправка дополнительных сведений в requestLocationUpdates намерениеService прерывает обновления местоположения

У меня возникли проблемы с отправкой дополнительной строки с моим PendingIntent, которую я передаю LocationServices.FusedLocationApi.requestLocationUpdates(GoogleApiClient client, LocationRequest request, PendingIntent callbackIntent).

Похоже, что дополнительное имя пользователя, которое я помещаю в Intent, искажает местоположение, которое requestLocationUpdates пытается передать моему IntentService, поскольку intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED) возвращает null.

ИЗМЕНИТЬ

Я попытался создать класс User, реализующий Parcelable, и добавить его как дополнительный:

mRequestLocationUpdatesIntent.putExtra("username", new User(username));

и я также попытался поместить Parcelable User внутри Bundle, как было предложено в комментарии к этому отчету об ошибке https://code.google.com/p/android/issues/detail?id=81812:

Bundle userBundle = new Bundle();
userBundle.putParcelable("user", new User(username));
mRequestLocationUpdatesIntent.putExtra("user", userBundle);

на моей службе:

Bundle userBundle = intent.getBundleExtra("user");
User user = userBundle.getParcelable("user");
String username = user.getUsername();

Однако ни один из этих подходов не имел никакого значения. Всякий раз, когда я добавляю какие-либо дополнительные данные в свое намерение, местоположение никогда не добавляется к намерению при обновлении.

Я настраиваю этот IntentService для обработки обновлений местоположения:

public class LocationUpdateService extends IntentService {

    private final String TAG = "LocationUpdateService";

    public LocationUpdateService() {
        super("LocationUpdateService");
    }


    @Override
    protected void onHandleIntent(Intent intent) {

        Log.d(TAG, "onHandleIntent");

        Bundle extras = intent.getExtras();
        Log.d(TAG, "keys found inside intent: " + TextUtils.join(", ", extras.keySet()));

        String username = intent.getStringExtra("username");

        if (username != null) {
            Log.d(TAG, "username: " + username);
        } else {
            Log.d(TAG, "username: null");
        }

        if (!intent.hasExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED)) {
            Log.d(TAG, "intent does not have location :(");
        }

        Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);

        if (location == null) {
            Log.d(TAG, "location == null :(");
        }

        Log.d(TAG, "latitude " + String.valueOf(location.getLatitude()));
        Log.d(TAG, "longitude " + String.valueOf(location.getLongitude()));

        ...

    }


}

Когда пользователь нажимает кнопку, в моей основной деятельности вызывается startLocationUpdates:

основной вид деятельности:

...

Boolean mLocationUpdatesEnabled = false;

protected void createLocationRequest() {
    mLocationRequest = new LocationRequest();
    mLocationRequest.setInterval(LOCATION_UPDATE_INTERVAL);
    mLocationRequest.setFastestInterval(LOCATION_UPDATE_FASTEST_INTERVAL);
    mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}

protected void startLocationUpdates() {

    Log.d(TAG, "startng location updates...");

    mLocationUpdatesEnabled = true;

    if (mLocationRequest == null) {
        createLocationRequest();
    }

    // create the Intent to use WebViewActivity to handle results
    Intent mRequestLocationUpdatesIntent = new Intent(this, LocationUpdateService.class);

    // create a PendingIntent
    mRequestLocationUpdatesPendingIntent = PendingIntent.getService(getApplicationContext(), 0,
            mRequestLocationUpdatesIntent,
            PendingIntent.FLAG_CANCEL_CURRENT);

    // request location updates
    LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
            mLocationRequest,
            mRequestLocationUpdatesPendingIntent);

    Log.d(TAG, "location updates started");
}

protected void stopLocationUpdates() {

    Log.d(TAG, "stopping location updates...");

    mLocationUpdatesEnabled = false;

    LocationServices.FusedLocationApi.removeLocationUpdates(
            mGoogleApiClient,
            mRequestLocationUpdatesPendingIntent);

    Log.d(TAG, "location updates stopped");
}

Все это работает хорошо и хорошо; Когда пользователь нажимает кнопку, вызывается toggleLocationUpdates, который вызывает LocationServices.FusedLocationApi.requestLocationUpdates, который вызывает мой LocationUpdateService, где я могу получить местоположение.

Проблема возникает, когда я попытался добавить дополнительную строку в свой Intent, используя Intent.putExtra(String, String):

основной вид деятельности:

...
protected void startLocationUpdates(String username) {
    ....

    // create the Intent to use WebViewActivity to handle results
    Intent mRequestLocationUpdatesIntent = new Intent(this, LocationUpdateService.class);

    //////////////////////////////////////////////////////////////////
    //
    //  When I put this extra, IntentService sees my username extra
    //  but the parcelableExtra `location` == null :(
    // 
    //////////////////////////////////////////////////////////////////

    mRequestLocationUpdatesIntent.putExtra("username", username);
    ...
}
...

ИЗМЕНИТЬ Я начал следующее предложение как утверждение, а не как вопрос: "Я использую..."

Использую ли я правильный подход к отправке дополнительных данных в эту обработку обновления местоположения IntentService, или есть более разумный способ сделать это?

Это ошибка или просто плохая документация?


person techjeffharris    schedule 16.06.2015    source источник
comment
@BladeCoder предоставил мне некоторые рекомендации в ответ на мою публикацию в сообществе разработчиков Android Google+: plus. google.com/117366723702848823459/posts/6QAmns2pQCT Я опубликую свой ответ, как только разберусь с ним   -  person techjeffharris    schedule 23.06.2015
comment
Один уточняющий вопрос: выводит ли он "location == null :(", то есть hasExtra() возвращает false, или intent.getParcelableExtra() просто возвращает ноль?   -  person ianhanniballake    schedule 23.06.2015
comment
Если я удалю оператор return, я получу java.lang.NullPointerException: Attempt to invoke virtual method 'double android.location.Location.getLatitude()' on a null object reference Я отредактировал IntentService, чтобы обеспечить подробное ведение журнала, которое показывает, что Intent.hasExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED возвращает false, location == null и создается исключение ссылки на нулевой объект.   -  person techjeffharris    schedule 23.06.2015
comment
Я также получаю этот вывод журнала keys found inside intent: username; однако, когда я не указываю имя пользователя в намерении, я получаю этот вывод: keys found inside intent: com.google.android.location.LOCATION, com.google.android.gms.location.EXTRA_LOCATION_RESULT   -  person techjeffharris    schedule 23.06.2015
comment
Вы когда-нибудь решали это? У меня такая же проблема.   -  person Catherine    schedule 12.08.2015
comment
Я не. Мои потребности на самом деле не требовали этого, поэтому я разработал обходной путь, используя «общие настройки» приложения.   -  person techjeffharris    schedule 12.08.2015
comment
Вы проверили, истинно ли isSuccess() для PendingResult, возвращаемого методом requestLocationUpdates?   -  person user3334059    schedule 15.08.2015
comment
Может быть, вы можете попробовать перебрать все ключи в комплекте и убедиться, что ни один из ключей, которые вы ожидаете, не существует в комплекте.   -  person user3334059    schedule 15.08.2015
comment
@SumantHanumante, да, я проверил, что isSuccess() является истинным для PendingResult (это было) и я попытался перебрать все ключи в комплекте, но ключа username там не было.   -  person techjeffharris    schedule 15.08.2015
comment
@ Кэтрин, моего ответа достаточно, чтобы помочь вам?   -  person andrewdleach    schedule 16.08.2015
comment
@andrewdleach Я адаптирую его, чтобы он работал, и тестирую его. В нем отсутствуют некоторые реализации функций и используются классы, отличные от API (например, CoordinateStorageDatabaseHelper), поэтому его нельзя использовать сразу.   -  person Catherine    schedule 17.08.2015
comment
@ Кэтрин, вы бы хотели изменить ответ с удалением пользовательских классов?   -  person andrewdleach    schedule 17.08.2015


Ответы (1)


Использование IntentService в сочетании с FusedLocationProviderAPI вызовет проблемы. Из документации разработчика под названием Получение обновлений местоположения:

В зависимости от формы запроса провайдер объединенного местоположения либо вызывает метод обратного вызова LocationListener.onLocationChanged() и передает ему объект Location, либо выдает PendingIntent, который содержит местоположение в своих расширенных данных. На точность и частоту обновлений влияют запрошенные вами разрешения на определение местоположения и параметры, заданные вами в объекте запроса на определение местоположения.

Кроме того, PendingIntent используется для расширения разрешений для другого фрагмента кода (FusedLocationProviderAPI в сервисах Google Play) для выполнения их кода в вашем apk. IntentService используется для запуска службы, определенной в рамках вашего apk.

Таким образом, метод требует реализации LocationListener для обновлений переднего плана или PendingIntent для фоновых обновлений в сочетании с широковещательным приемником.

Это рабочий пример некоторых методов, используемых для запроса обновлений местоположения из PendingIntent в сочетании с дополнительными значениями.

Примечание. LocalStorage.java — это служебный класс для хранения локальных переменных, он не является частью Android API

GPSPlotter

/**
 * Private helper method to initialize the Google Api Client with the
 * LocationServices Api and Build it for use.
 */
private void initializeGoogleApiClient() {
    mGoogleApiClient = new GoogleApiClient.Builder(mContext)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(LocationServices.API)
            .build();

}

/**
 * Private helper method to determine whether or not GooglePlayServices
 * are installed on the local system.
 *
 * @return services are installed.
 */
private boolean googlePlayServicesInstalled() {
    int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mContext);
    return result == ConnectionResult.SUCCESS;
}

/**
 * Private method to build the Api Client for use with the LocationServices API.
 */
private synchronized void buildApiClient() {
    Log.w(TAG, "Building Google Api Client...");
    initializeGoogleApiClient();
}

/**
 * Private method used to connect the ApiClient to the Api hosted by Google for
 * Accessing Locations.
 */
private void connectClient() {
    mGoogleApiClient.connect();
}

 /**
 * User passes in a requested interval polling time in seconds as an
 * integer.
 *
 * @param theAccount is a reference to the parent activity used for updating views.
 */
public void beginManagedLocationRequests(MyAccount theAccount) {
    if (mAccount == null)
        mAccount = theAccount;

    startBackgroundUpdates();

}

/**
 * Public method to end the managed Location Requests.
 */
public void endManagedLocationRequests() {
        endBackgroundUpdates();

}

/**
 * This method handles the switch in polling rates by stopping and then starting once more the
 * background udpates, which in turn sets the interval in another method in the call stack.
 * @param theInterval the desired interval polling rate
 */
public void changeRequestIntervals(int theInterval) {
    mIntentInterval = theInterval;
    if (LocalStorage.getRequestingBackgroundStatus(mContext)) {
        endBackgroundUpdates();
        startBackgroundUpdates();
    }



}

/**
 * Private helper method to build an Intent that will be couple with a pending intent uses
 * for issuing background Location requests.
 *
 * @return theIntent
 */
private Intent buildBackgroundRequestIntent() {
    Intent intent = new Intent(mContext, BackgroundLocationReceiver.class);
    intent.setAction(BACKGROUND_ACTION);
    intent.putExtra(User.USER_ID, mUserID);
    return intent;
}

/**
 * Private helper method used to generate a PendingIntent for use when the User requests background service
 * within the FusedLocationApi until the Interval is changed.
 *
 * @return pendingIntent
 */
private PendingIntent buildRequestPendingIntent(Intent theIntent) {
    Log.w(TAG, "building pending intent");
    return PendingIntent.getBroadcast(mContext, 0, theIntent, 0);
}


/**
 * Private method to start the Location Updates using the FusedLocation API in the background.
 */
private void startBackgroundUpdates() {
    Log.w(TAG, "Starting background updates");
    if (googlePlayServicesInstalled()) {
        LocalStorage.putBackgroundRequestStatus(true, mContext);
        LocalStorage.putLocationRequestStatus(true, mContext);
        registerAlarmManager();
        LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, buildLocationRequest(), buildRequestPendingIntent(buildBackgroundRequestIntent()));
    }
}


/**
 * Private method to end background updates.
 */
private void endBackgroundUpdates() {
    Log.w(TAG, "Ending background updates");
    LocalStorage.putBackgroundRequestStatus(false, mContext);
    LocalStorage.putLocationRequestStatus(false, mContext);
    unregisterAlarmManager();
    LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, buildRequestPendingIntent(buildBackgroundRequestIntent()));
}

BackgroundLocationReceiver

public class BackgroundLocationReceiver extends BroadcastReceiver {
private static final String TAG = "BLocRec: ";
private static final String UPLOAD_ERROR_MESSAGE = "Background Service to Upload Coordinates Failed.";
private static final String UPLOAD_MESSAGE = "Coordinate Batch Pushed to Database.";

public BackgroundLocationReceiver() {
    //Default, no-arg constructor
}

/**
 * This method handles any location updates received when the app is no longer in focus. Coordinates are
 * stored in the local database and uploaded once every hour.
 * @param context the application context
 * @param intent is the pending intent
 */
@Override
public void onReceive(Context context, Intent intent) {

    if (intent.getAction().matches(GPSPlotter.BACKGROUND_ACTION)) {
        Log.w(TAG, "BLR Received-background");
        Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
        storeLocation(location, context, intent.getStringExtra(User.USER_ID));

    }

EDIT Следующий метод создает запрос LocationRequest, необходимый для вызова метода requestLocationUpdates()

/**
 * Private helper method used to generate a LocationRequest which will be used to handle all location updates
 * within the FusedLocationApi until the Interval is changed.
 *
 * @return locationRequest
 */
private LocationRequest buildLocationRequest() {
    int dateConversion = 1000;
    LocationRequest locationRequest = LocationRequest.create();
    locationRequest.setInterval(mIntentInterval * dateConversion);
    locationRequest.setFastestInterval((mIntentInterval / 2) * dateConversion);
    locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    Log.w(TAG, "Building location request");
    return locationRequest;
}

РЕДАКТИРОВАТЬ После долгого обсуждения в чате с Кэтрин мы пришли к выводу, что в библиотеке сервисов Google Play 7.5 есть ошибка, из-за которой не обрабатывается дополнительное местоположение Parcelable, переданное из FusedLocationProviderAPI, при добавлении других дополнений. в Намерение. Однако версия 7.0 предоставляет такую ​​возможность. Она сказала, что отправит сообщение об ошибке, и мы посмотрим, сколько времени потребуется команде Android на ее устранение

person andrewdleach    schedule 15.08.2015
comment
› Использование IntentService в сочетании с FusedLocationProviderAPI вызовет проблемы. Откуда вы взяли эту информацию? Я просматривал, просматривал и просматривал документацию Android для объединенного провайдера определения местоположения и IntentService, и ни один из них не сказал, что они несовместимы или что их комбинация может вызвать проблемы. ( - person techjeffharris; 15.08.2015
comment
› Таким образом, метод требует реализации LocationListener для обновлений переднего плана или PendingIntent для фоновых обновлений в сочетании с Broadcast Receiver. Опять же, я никогда не видел в документах ничего, что говорило бы о необходимости широковещательного приемника. Не могли бы вы объяснить, почему, или указать мне часть документации, объясняющую, почему необходимо использовать широковещательный приемник. - person techjeffharris; 15.08.2015
comment
Посмотрите здесь под заголовком Request Location Updates во втором абзаце. . Это там. - person andrewdleach; 15.08.2015
comment
Кроме того, вам не обязательно нужен BroadcastReceiver, просто некоторый экземпляр класса для обработки обновлений при их выпуске. BroadcastReceiver хорошо работал в моей реализации, потому что он также требовал трансляции WAKE BOOT. - person andrewdleach; 16.08.2015
comment
Насколько я понимаю, как вы сказали, любой экземпляр класса может обрабатывать намерение, но вы не ответили на мой вопрос о том, почему человек столкнется с проблемами при использовании IntentService. - person techjeffharris; 16.08.2015
comment
Проблемы возникнут, потому что API указывает PendingIntent или LocationListener для обработки обновлений местоположения. PendingIntent используется для расширения разрешений для другого фрагмента кода (FusedLocationProviderAPI в сервисах Google Play) для выполнения их кода в вашем apk. IntentService используется для запуска службы, определенной в рамках вашего apk. - person andrewdleach; 16.08.2015
comment
Хорошо. Я не знал об этом различии. Благодарю вас! - person techjeffharris; 16.08.2015
comment
Не проблема. Рад помочь - person andrewdleach; 16.08.2015
comment
Протестировал решение. Кажется, у него та же проблема, что и у оригинала, т.е. когда я получаю намерение в приемнике, дополнения присутствуют, но местоположение равно нулю. - person Catherine; 17.08.2015
comment
@ Кэтрин, вы тестируете на эмуляторе или на устройстве? - person andrewdleach; 17.08.2015
comment
Я тестирую на устройстве. - person Catherine; 17.08.2015
comment
@Catherine, я забыл включить пример создания LocationRequest в первый опубликованный ответ. Это может помочь. Единственное, о чем я могу думать, это, возможно, GPS не «включен» на устройстве? - person andrewdleach; 17.08.2015
comment
@andrewdleach GPS определенно включен. Когда я включаю дополнительные функции в намерение, я не получаю местоположение, если я убираю дополнительные функции, я получаю местоположение. - person Catherine; 18.08.2015
comment
@ Кэтрин, черт возьми ... я в тупике. Я могу подтвердить, что это сработало для меня и моей команды. Если вы хотите клонировать репозиторий и сравнить его, вы можете перейти на github.com/leachad/buuteeq-ponyhax - person andrewdleach; 18.08.2015
comment
Была ли ошибка когда-либо поднята для этого? Это все еще происходит в версии 8.x сервисов Google Play, поэтому необходимо откатиться до 7.0.0. - person cockadoodledo; 05.10.2015
comment
Это все еще происходит в версии 9.x сервисов Google Play. @andrewdleach Нет причин, по которым IntentService не должен работать. На самом деле, это работает. Вполне приемлемо использовать IntentService с PendingIntent, и так было всегда. - person Eliezer; 22.06.2016
comment
Проблема сохраняется даже в версии 10.x - person user3105453; 18.02.2017
comment
Я так рада, что перестала заниматься этим вопросом. Очевидно, это привело бы к году просмотра обновлений без какого-либо облегчения. :-/ - person techjeffharris; 14.03.2017