带有 PendingIntent 的 FusedLocationApi 只被触发一次并且它的 null

FusedLocationApi with PendingIntent is fired only once and its null

我正在开发一个即使在后台也需要频繁更新位置的应用程序。按照文档 here,我正在使用未决意图而不是位置侦听器。我的代码如下

/**
 * Created by philip on 7/30/16.
 */
public class LocationService extends Service implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener{
    private GoogleApiClient mGoogleApiClient;
    private LocationRequest mLocationRequest;

    public static final long UPDATE_INTERVAL_IN_MILLISECONDS =  1000 * 30; //1 minute;
    /**
     * The fastest rate for active location updates. Exact. Updates will never be more frequent
     * than this value.
     */
    public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
            UPDATE_INTERVAL_IN_MILLISECONDS / 2;

    private String mLastUpdateTime;
    private Location mCurrentLocation;

    private GeoFire mGeoFire = null;
    private String uuid = null;
    private Intent intentService;
    private PendingIntent mPendingIntent;

    @Override
    public void onCreate() {
        super.onCreate();
        Firebase.setAndroidContext(this);
        buildGoogleApiClient();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!mGoogleApiClient.isConnected()) {
            mGoogleApiClient.connect();
        }
        createLocationRequest();
        if(intent != null){
            intentService = new Intent(this, LocationBroadcastReceiver.class);
            intentService.putExtra("UUID", intent.getStringExtra("UUID"));
            intentService.setAction("foo.LOCATION_UPDATE_INTENT");
            mPendingIntent = PendingIntent.getBroadcast(this, 0, intentService, PendingIntent.FLAG_UPDATE_CURRENT);
        }
        return START_REDELIVER_INTENT;
    }

    /********************************************** Google api connection callback below **************************************/

    /***
     * callback fired once connection has been established
     * @param bundle
     */
    @Override
    public void onConnected(Bundle bundle) {
        startLocationUpdates();
        Log.i(this.getClass().getSimpleName(), "Location Service api has been connected");
    }


    /***
     * callback fired when connection fails
     * @param connectionResult
     */
    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.i(this.getClass().getSimpleName(), "Location Service disconnected");
    }


    /**
     * callback fired when connection is temporary suspended
     * @param i
     */
    @Override
    public void onConnectionSuspended(int i) {
        if (mGoogleApiClient != null) {
            mGoogleApiClient.connect();
        }
    }

    /************************************** Ease methods ************************************************/

    /**
     * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the
     * LocationServices API.
     */
    protected synchronized void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(API)
                .build();
    }

    /**
     * Requests location updates from the FusedLocationApi.
     */
    protected void startLocationUpdates() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            return;
        }
        FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, mPendingIntent);
    }

    protected void createLocationRequest() {
        mLocationRequest = new LocationRequest();

        // Sets the desired interval for active location updates. This interval is
        // inexact. You may not receive updates at all if no location sources are available, or
        // you may receive them slower than requested. You may also receive updates faster than
        // requested if other applications are requesting location at a faster interval.
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);

        // Sets the fastest rate for active location updates. This interval is exact, and your
        // application will never receive updates faster than this value.
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);

        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    }


}

和我的广播接收器

@Override
    public void onReceive(Context context, Intent intent) {
        Firebase.setAndroidContext(context);
        Log.i(getClass().getSimpleName(), "broadcast has been called");

        if (intent != null) {
            String uuid = intent.getStringExtra("UUID");
            mGeoFire = new GeoFire(new Firebase("https://foo.firebaseio.com/users/" + uuid));

            LocationResult locationResult = LocationResult.extractResult(intent);

            if(locationResult == null){// check for null pointer
                Log.i(getClass().getSimpleName(), "location result is null <<<<<<<<<<<<<<<<<<");
                return;
            }

            Log.i(getClass().getSimpleName(), "location result found >>>>>>>>>>>>>>>>>>>>====>>>>>>>>>>>>>>>>>>>");

            location = locationResult.getLastLocation();
        }

        if( isBetterLocation(location, currentBestLocation)){
            currentBestLocation = location;
        }else {
            return;
        }

        if (mGeoFire != null) {
            mGeoFire.setLocation("location", new GeoLocation(location.getLatitude(), location.getLongitude()));
        }
    }

我面临的问题是

  1. 我的广播只被调用过一次,
  2. 当我收到意图时,LocationResult locationResult = LocationResult.extractResult(intent); returns null

这是我最后一次收到广播接收者的消息

我基本上将听众移到了 onConnected 并检查了 null。之后一切正常。

package com.github.robophil.location_service;

import android.Manifest;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.widget.Toast;

import com.firebase.client.Firebase;
import com.firebase.geofire.GeoFire;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;

import static com.google.android.gms.location.LocationServices.API;
import static com.google.android.gms.location.LocationServices.FusedLocationApi;

/**
 * Created by philip on 7/30/16.
 */
public class LocationService extends Service implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
    private GoogleApiClient mGoogleApiClient;
    private LocationRequest mLocationRequest;

    public static final String prefName = "com.github.robophil.pref";

    public static final long UPDATE_INTERVAL_IN_MILLISECONDS = 1000 * 60; //1 minute;
    /**
     * The fastest rate for active location updates. Exact. Updates will never be more frequent
     * than this value.
     */
    public static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
            UPDATE_INTERVAL_IN_MILLISECONDS / 2;

    private String mLastUpdateTime;
    private Location mCurrentLocation;

    private GeoFire mGeoFire = null;
    private String uuid = null;
    private Intent intentService;
    private PendingIntent mPendingIntent;

    @Override
    public void onCreate() {
        super.onCreate();
        Firebase.setAndroidContext(this);
        buildGoogleApiClient();
    }

    @Override
    public void onDestroy() {
        if(mGoogleApiClient != null){
            if(mGoogleApiClient.isConnected()){
                mGoogleApiClient.disconnect();
                FusedLocationApi.removeLocationUpdates(mGoogleApiClient, mPendingIntent);
            }
        }
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            if(!intent.hasExtra("UUID") || !intent.hasExtra("URL")){
                Log.i(getClass().getSimpleName(), "Service has stopped itself");
                Toast.makeText(this, "stopself called", Toast.LENGTH_SHORT).show();
                stopSelf();
            }

            SharedPreferences.Editor editor = getSharedPreferences(LocationService.prefName, getApplicationContext().MODE_PRIVATE).edit();
            editor.putString("UUID", intent.getStringExtra("UUID"));
            editor.putString("URL", intent.getStringExtra("URL"));
            editor.apply();

            intentService = new Intent(this, LocationIntentService.class);
            intentService.setExtrasClassLoader(LocationResult.class.getClassLoader());
            mPendingIntent = PendingIntent.getService(getApplicationContext(), 0, intentService, PendingIntent.FLAG_UPDATE_CURRENT);
        }

        if (!mGoogleApiClient.isConnected()) {
            mGoogleApiClient.connect();
        }

        return START_REDELIVER_INTENT;
    }

    /********************************************** Google api connection callback below **************************************/

    /***
     * callback fired once connection has been established
     * @param bundle
     */
    @Override
    public void onConnected(Bundle bundle) {
        createLocationRequest();
        startLocationUpdates();
        Log.i(this.getClass().getSimpleName(), "Location Service api has been connected");
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            return;
        }
        Location location = FusedLocationApi.getLastLocation(mGoogleApiClient);
        if (location == null){
            Log.i(getClass().getSimpleName(), "init location is null");
            return;
        }
        Log.i(getClass().getSimpleName(), "init location ==> "+location.getAccuracy()+" "+location.getProvider());
    }


    /***
     * callback fired when connection fails
     * @param connectionResult
     */
    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.i(this.getClass().getSimpleName(), "Location Service disconnected");
    }


    /**
     * callback fired when connection is temporary suspended
     * @param i
     */
    @Override
    public void onConnectionSuspended(int i) {
        if (mGoogleApiClient != null) {
            mGoogleApiClient.connect();
        }
    }

    /************************************** Ease methods ************************************************/

    /**
     * Builds a GoogleApiClient. Uses the {@code #addApi} method to request the
     * LocationServices API.
     */
    protected synchronized void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(API)
                .build();
    }

    /**
     * Requests location updates from the FusedLocationApi.
     */
    protected void startLocationUpdates() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            return;
        }
//        FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
        FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, mPendingIntent);
    }

    protected void createLocationRequest() {
        mLocationRequest = new LocationRequest();

        // Sets the desired interval for active location updates. This interval is
        // inexact. You may not receive updates at all if no location sources are available, or
        // you may receive them slower than requested. You may also receive updates faster than
        // requested if other applications are requesting location at a faster interval.
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);

        // Sets the fastest rate for active location updates. This interval is exact, and your
        // application will never receive updates faster than this value.
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);

        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    }


}

用于我的未决意图的 LocationsIntentService 保持不变,只是在执行任何操作之前检查是否为 null

package com.github.robophil.location_service;

import android.app.IntentService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;

import com.firebase.client.Firebase;
import com.firebase.geofire.GeoFire;
import com.firebase.geofire.GeoLocation;
import com.google.android.gms.location.LocationResult;

import java.util.Set;

/**
 * Created by philip on 8/7/16.
 */
public class LocationIntentService extends IntentService {
    private GeoFire mGeoFire;
    private Location location, currentBestLocation;

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public LocationIntentService(String name) {
        super("i am a value");
    }

    public LocationIntentService(){
        super("i am a value");
    }



    @Override
    protected void onHandleIntent(Intent intent) {
        Firebase.setAndroidContext(this);
        Log.i(getClass().getSimpleName(), "intent service has been called");

        SharedPreferences pref = getSharedPreferences(LocationService.prefName, getApplicationContext().MODE_PRIVATE);
        String uuid = pref.getString("UUID", null);
        String url = pref.getString("URL", null);

        if(uuid == null || url==null){
            stopSelf();
            return;
        }

        if (intent != null) {

            mGeoFire = new GeoFire(new Firebase(url +"/"+ uuid));
            Log.i(getClass().getSimpleName(), "user id found ==> "+uuid+", "+url);

            Bundle extra = intent.getExtras();
            Set<String> extraKeySet = extra.keySet();
            for(String key: extraKeySet){
                Log.i(getClass().getSimpleName(), "key found ==> "+ key);
            }

            if(LocationResult.hasResult(intent)){
                Log.i(getClass().getSimpleName(), "intent contains location");
            }

            LocationResult locationResult = LocationResult.extractResult(intent);

            if(locationResult == null){// check for null pointer
                Log.i(getClass().getSimpleName(), "location result is null ...");
                return;
            }

            Log.i(getClass().getSimpleName(), "location result found");

            location = locationResult.getLastLocation();
        }

        if( isBetterLocation(location, currentBestLocation)){
            currentBestLocation = location;
        }else {
            return;
        }

        if (mGeoFire != null) {
            mGeoFire.setLocation("location", new GeoLocation(location.getLatitude(), location.getLongitude()));
        }
    }

    private static final int TWO_MINUTES = 1000 * 60 * 2;

    /** Determines whether one Location reading is better than the current Location fix
     * @param location  The new Location that you want to evaluate
     * @param currentBestLocation  The current Location fix, to which you want to compare the new one
     */
    protected boolean isBetterLocation(Location location, Location currentBestLocation) {
        if (currentBestLocation == null) {
            // A new location is always better than no location
            return true;
        }

        // Check whether the new location fix is newer or older
        long timeDelta = location.getTime() - currentBestLocation.getTime();
        boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
        boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
        boolean isNewer = timeDelta > 0;

        // If it's been more than two minutes since the current location, use the new location
        // because the user has likely moved
        if (isSignificantlyNewer) {
            return true;
            // If the new location is more than two minutes older, it must be worse
        } else if (isSignificantlyOlder) {
            return false;
        }

        // Check whether the new location fix is more or less accurate
        int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
        boolean isLessAccurate = accuracyDelta > 0;
        boolean isMoreAccurate = accuracyDelta < 0;
        boolean isSignificantlyLessAccurate = accuracyDelta > 200;

        // Check if the old and new location are from the same provider
        boolean isFromSameProvider = isSameProvider(location.getProvider(),
                currentBestLocation.getProvider());

        // Determine location quality using a combination of timeliness and accuracy
        if (isMoreAccurate) {
            return true;
        } else if (isNewer && !isLessAccurate) {
            return true;
        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
            return true;
        }
        return false;
    }

    /** Checks whether two providers are the same */
    private boolean isSameProvider(String provider1, String provider2) {
        if (provider1 == null) {
            return provider2 == null;
        }
        return provider1.equals(provider2);
    }
}