向 requestLocationUpdates intentService 发送额外信息会中断位置更新

Posted

技术标签:

【中文标题】向 requestLocationUpdates intentService 发送额外信息会中断位置更新【英文标题】:sending extra to requestLocationUpdates intentService breaks location updates 【发布时间】:2015-08-31 15:24:09 【问题描述】:

我无法使用我的PendingIntent 发送额外的字符串,我将其传递给LocationServices.FusedLocationApi.requestLocationUpdates(GoogleApiClient client, LocationRequest request, PendingIntent callbackIntent)

我在Intent 上添加的用户名似乎正在破坏requestLocationUpdates 试图移交给我的IntentService 的位置,因为intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED) 返回null

编辑

我尝试创建一个实现ParcelableUser 类并将其作为额外的:

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.putExtra(String, String) 将额外的字符串添加到我的 Intent 时,问题就来了:

主要活动类:

...
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 发送一些额外的数据,还是有更理智的方法来解决这个问题?

这是一个错误还是只是糟糕的文档?

【问题讨论】:

@BladeCoder 为我提供了一些指导,以回应我在 Android 开发 Google+ 社区中的帖子:plus.google.com/117366723702848823459/posts/6QAmns2pQCT 一旦我弄清楚了我会发布我的答案 一个澄清的问题,它打印出"location == null :(" - 即hasExtra() 返回false,还是intent.getParcelableExtra() 只是返回null? 如果我删除 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,并引发空对象引用异常。 我也得到了这个日志输出keys found inside intent: username;但是,当我没有将“用户名”放在意图上时,我会得到以下输出:keys found inside intent: com.google.android.location.LOCATION, com.google.android.gms.location.EXTRA_LOCATION_RESULT Location Client request location updates with parcelable extras in PendingIntent的可能重复 【参考方案1】:

将 IntentService 与 FusedLocationProviderAPI 结合使用会出现问题。来自标题为接收位置更新的开发者文档:

根据请求的形式,融合的位置提供者 要么调用LocationListener.onLocationChanged() 回调 方法并传递给它一个 Location 对象,或者发出一个PendingIntent 在其扩展数据中包含该位置。精度和频率 的更新受您拥有的位置权限的影响 请求和您在位置请求对象中设置的选项

此外,PendingIntent 用于扩展另一段代码(Google Play 服务中的FusedLocationProviderAPI)的权限,以在您的 apk 中执行其代码。 IntentService 用于启动在您的 apk 范围内定义的服务。

因此,该方法需要实现LocationListener 用于前台更新,或PendingIntent 用于后台更新以及广播接收器。

这是用于从PendingIntent 请求位置更新以及额外值的一些方法的工作示例。

注意:LocalStorage.java 是一个用于存储局部变量的实用程序类,它不是 Android API 的一部分

GPS绘图仪

/**
 * 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));

    

编辑 下面的方法构建了调用 requestLocationUpdates() 方法所必需的 LocationRequest

/**
 * 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;

编辑 经过与 Catherine 的长时间讨论后,我们得出的结论是 google play services library 7.5 存在一个 bug,即当其他 extras 放入 Intent 时,不会处理从 FusedLocationProviderAPI 传递的 Parcelable Extra Location。但是,7.0 确实提供了这种能力。她说她会提交一个bug,我们看看Android团队需要多长时间才能解决

【讨论】:

> 将 IntentService 与 FusedLocationProviderAPI 结合使用会出现问题。你从哪里得到这些信息的?我查看并查看了融合位置提供程序和 IntentService 的 Android 文档,但都没有说它们不兼容或它们的组合可能会造成麻烦。也许 Google 的文档 do 很糟糕......:' ( > 因此,该方法需要实现 LocationListener 用于前台更新,或 PendingIntent 用于后台更新与广播接收器耦合。同样,我从未在文档中看到任何说需要广播接收器的内容。请您解释一下原因,或者给我一个解释为什么必须使用广播接收器的文档。 @Catherine,我忘记在发布的第一个答案中包含创建 LocationRequest 的示例。这可能会有所帮助。我唯一能想到的另一件事是设备上的 GPS 可能没有“开启”? @andrewdleach GPS 绝对开启。当我在意图中包含额外内容时,我不会得到位置,如果我取出额外内容,我就会得到位置。 即使在 10.x 版本中问题仍然存在

以上是关于向 requestLocationUpdates intentService 发送额外信息会中断位置更新的主要内容,如果未能解决你的问题,请参考以下文章

requestLocationUpdates() 花费太多时间或根本不起作用

为啥 requestLocationUpdates 会在接收时立即触发?

如何等待 requestLocationUpdates() 回调意图完成?

当两个 locationManager 对象调用 requestLocationUpdates(...) 时?

requestLocationUpdates() 最初是不是检索缓存位置,然后随后获取新位置?

当我调用位置管理器时无法解析方法“requestLocationUpdates”