IntentService 不会与 PendingIntent 一起调用

IntentService doesn't get called in combination with PendingIntent

我正在尝试制作一个关于地理围栏的 Android 应用程序。我已按照 developers page and the sample code 上的教程进行操作,但它没有按预期工作。由于我结合了位置地址和地理围栏,我可能在 Android 应用程序的生命周期方面犯了一些错误。

我的问题具体是为什么我没有在 GeofenceTransitionIntentService.java class 中的 IntentService 中调用 onHandleIntentMainActivity.java中的getGeofencePending函数是否有错误? 这是代码:

package com.example.geofence;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.Builder;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.internal.mr;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingRequest;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.model.LatLng;

import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;

public class MainActivity extends Activity implements OnClickListener,
ConnectionCallbacks, OnConnectionFailedListener, LocationListener, ResultCallback <Status>{

    protected static final String TAG = "creating-and-monitoring-geofences";
    GoogleApiClient mGoogleApiClient;
    LocationRequest mLocationRequest;
    Boolean mRequestingLocationUpdates = true;
    Boolean mAddressRequested = true;
    String mAddressOutput = null;
    Location mCurrentLocation;
    protected Location mLastLocation;
    private AddressResultReceiver mResultReceiver;
    String mLastUpdateTime;
    TextView latitudeText, longitudeText, lastUpdateTimeText, locationAddress;
    private SharedPreferences mSharedPreferences;

    private Button mAddGeofencesButton, mRemoveGeofencesButton;
    protected ArrayList<Geofence> mGeofenceList = null;
    private PendingIntent mGeofencePendingIntent;
    private boolean mGeofencesAdded;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Toast.makeText(this, "OnCreate", Toast.LENGTH_SHORT).show();
        setContentView(R.layout.activity_main);
        mResultReceiver = new AddressResultReceiver(new Handler());
        latitudeText = (TextView) findViewById(R.id.latitudeText);
        longitudeText = (TextView) findViewById(R.id.longitudeText);
        lastUpdateTimeText = (TextView) findViewById(R.id.lastUpdateText);
        locationAddress = (TextView) findViewById(R.id.locationAddressText);

        mAddGeofencesButton = (Button) findViewById(R.id.add_geofence);
        mRemoveGeofencesButton = (Button) findViewById(R.id.remove_geofence);
        mGeofenceList = new ArrayList<Geofence>();
        mGeofencePendingIntent = null;
        mSharedPreferences = getSharedPreferences(Constants.SHARED_PREFERENCES_NAME,
                MODE_PRIVATE);
        mGeofencesAdded = mSharedPreferences.getBoolean(Constants.GEOFENCES_ADDED_KEY, false);

        mAddGeofencesButton.setOnClickListener(this);
        mRemoveGeofencesButton.setOnClickListener(this);

        setButtonsEnabledState();
        buildGoogleApiClient(); 
    }

    protected void startIntentService() {
        Intent intent = new Intent(this, FetchAddressIntentService.class);
        intent.putExtra(Constants.RECEIVER, mResultReceiver);
        intent.putExtra(Constants.LOCATION_DATA_EXTRA, mLastLocation);
        startService(intent);
    }
    /*  
    @Override
    protected void onSaveInstanceState(Bundle savedInstanceState) {
        savedInstanceState.putBoolean(REQUESTING_LOCATION_UPDATES_KEY,
                mRequestingLocationUpdates);
        savedInstanceState.putParcelable(LOCATION_KEY, mCurrentLocation);
        savedInstanceState.putString(LAST_UPDATED_TIME_STRING_KEY, mLastUpdateTime);
        super.onSaveInstanceState(savedInstanceState);
    }
    */

    @Override
    public void onClick(View v) {
        if(v==mAddGeofencesButton){
            addGeofencesButtonHandler(v);
        }
        if(v==mRemoveGeofencesButton){
            removeGeofencesButtonHandler(v);
        }

    }

    @Override
    protected void onPause() {
        super.onPause();
        stopLocationUpdates();
    }

    @Override
    public void onResume() {
        super.onResume();
        Toast.makeText(this, "onResume", Toast.LENGTH_SHORT).show();
        if (mGoogleApiClient.isConnected() && !mRequestingLocationUpdates) {
            startLocationUpdates();
        }
    }

    protected synchronized void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(this)
        .addConnectionCallbacks(this)
        .addOnConnectionFailedListener(this)
        .addApi(LocationServices.API)
        .build();
        mGoogleApiClient.connect();

    }
    @Override
    public void onConnectionFailed(ConnectionResult result) {
        // TODO Auto-generated method stub
        Toast.makeText(this, "onConnectionFailed", Toast.LENGTH_SHORT).show();

    }
    @SuppressLint("NewApi")
    @Override
    public void onConnected(Bundle connectionHint) {
        Toast.makeText(this, "onConnected", Toast.LENGTH_SHORT).show();
        mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
                mGoogleApiClient);
        if (mLastLocation != null) {
            String latitude = String.valueOf(mLastLocation.getLatitude());
            String longitude = String.valueOf(mLastLocation.getLongitude());
            latitudeText.setText("latitude: " + latitude);
            longitudeText.setText("longitude: " + longitude);
        }
        if(mRequestingLocationUpdates){
            createLocationRequest();
            startLocationUpdates();
        }
        if (mLastLocation != null) {
            // Determine whether a Geocoder is available.
            if (!Geocoder.isPresent()) {
                Toast.makeText(this, "No geocoder available",
                        Toast.LENGTH_SHORT).show();
                return;
            }

            if (mAddressRequested) {
                startIntentService();
            }
        }

    }
    @Override
    public void onConnectionSuspended(int cause) {
        // TODO Auto-generated method stub
        Toast.makeText(this, "onConnectionSuspended", Toast.LENGTH_SHORT).show();
    }

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

    protected void startLocationUpdates() {
        LocationServices.FusedLocationApi.requestLocationUpdates(
                mGoogleApiClient, mLocationRequest, this); //this refers to the interface LocationListener
    }

    protected void stopLocationUpdates() {
        LocationServices.FusedLocationApi.removeLocationUpdates(
                mGoogleApiClient, this);
    }   

    @Override
    public void onLocationChanged(Location location) {
        mCurrentLocation = location;
        mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
        updateUI();
    }   

    public void updateUI() {
        latitudeText.setText(String.valueOf(mCurrentLocation.getLatitude()));
        longitudeText.setText(String.valueOf(mCurrentLocation.getLongitude()));
        lastUpdateTimeText.setText(mLastUpdateTime);
        locationAddress.setText(mAddressOutput);
    }

    public void showToast(String text){
        Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
    }


    class AddressResultReceiver extends ResultReceiver {
        public AddressResultReceiver(Handler handler) {
            super(handler);
        }

        @Override
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            showToast("onReceiveResult");
            // Display the address string
            // or an error message sent from the intent service.
            mAddressOutput = resultData.getString(Constants.RESULT_DATA_KEY);
            //displayAddressOutput();
            System.out.println(mAddressOutput);
            Log.e("RESULT!!!", mAddressOutput);
            locationAddress.setText(mAddressOutput);

            // Show a toast message if an address was found.
            if (resultCode == Constants.SUCCESS_RESULT) {
                ;
            }
        }
    }

    /**
    * Adds geofences, which sets alerts to be notified when the device enters or exits one of the
    * specified geofences. Handles the success or failure results returned by addGeofences().
    */
   public void addGeofencesButtonHandler(View view) {
       if (!mGoogleApiClient.isConnected()) {
           Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show();
           return;
       }

       try {
           LocationServices.GeofencingApi.addGeofences(
                   mGoogleApiClient,
                   // The GeofenceRequest object.
                   getGeofencingRequest(),
                   // A pending intent that that is reused when calling removeGeofences(). This
                   // pending intent is used to generate an intent when a matched geofence
                   // transition is observed.
                   getGeofencePendingIntent()
           ).setResultCallback(this); // Result processed in onResult().
       } catch (SecurityException securityException) {
           // Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
           logSecurityException(securityException);
       }
   }

   /**
    * Removes geofences, which stops further notifications when the device enters or exits
    * previously registered geofences.
    */
   public void removeGeofencesButtonHandler(View view) {
       if (!mGoogleApiClient.isConnected()) {
           Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show();
           return;
       }
       try {
           // Remove geofences.
           LocationServices.GeofencingApi.removeGeofences(
                   mGoogleApiClient,
                   // This is the same pending intent that was used in addGeofences().
                   getGeofencePendingIntent()
           ).setResultCallback(this); // Result processed in onResult().
       } catch (SecurityException securityException) {
           // Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
           logSecurityException(securityException);
       }
   }

   private void logSecurityException(SecurityException securityException) {
       Log.e(TAG, "Invalid location permission. " +
               "You need to use ACCESS_FINE_LOCATION with geofences", securityException);
   }

    /**
     * Gets a PendingIntent to send with the request to add or remove Geofences. Location Services
     * issues the Intent inside this PendingIntent whenever a geofence transition occurs for the
     * current list of geofences.
     *
     * @return A PendingIntent for the IntentService that handles geofence transitions.
     */
    private PendingIntent getGeofencePendingIntent() {
        // Reuse the PendingIntent if we already have it.
        if (mGeofencePendingIntent != null) {
            Toast.makeText(this, "mGeofencePendingIntent != null", Toast.LENGTH_SHORT).show();
            return mGeofencePendingIntent;
        }
        Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
        // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
        // addGeofences() and removeGeofences().
        Toast.makeText(this, "mGeofencePendingIntent == null", Toast.LENGTH_SHORT).show();
        mGeofencePendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        return mGeofencePendingIntent;
    }

    /**
     * Builds and returns a GeofencingRequest. Specifies the list of geofences to be monitored.
     * Also specifies how the geofence notifications are initially triggered.
     */
    private GeofencingRequest getGeofencingRequest() {     

        GeofencingRequest.Builder builder = new GeofencingRequest.Builder();

        // The INITIAL_TRIGGER_ENTER flag indicates that geofencing service should trigger a
        // GEOFENCE_TRANSITION_ENTER notification when the geofence is added and if the device
        // is already inside that geofence.
            builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);

        // Add the geofences to be monitored by geofencing service.
        builder.addGeofences(mGeofenceList);
        builder.addGeofence(new Geofence.Builder()
        .setRequestId("CurrentLocation")
        .setCircularRegion(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude(), 50)
        .setExpirationDuration(Geofence.NEVER_EXPIRE)
        .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT)
        .build());

        Toast.makeText(this, "Geofence request added",  Toast.LENGTH_SHORT).show();

        // Return a GeofencingRequest.
        return builder.build();
    }


    @Override
    public void onResult(Status status) {
        if (status.isSuccess()) {
            // Update state and save in shared preferences.
            mGeofencesAdded = !mGeofencesAdded;
            SharedPreferences.Editor editor = mSharedPreferences.edit();
            editor.putBoolean(Constants.GEOFENCES_ADDED_KEY, mGeofencesAdded);
            editor.commit();

            // Update the UI. Adding geofences enables the Remove Geofences button, and removing
            // geofences enables the Add Geofences button.
            setButtonsEnabledState();

            Toast.makeText(
                    this,
                    getString(mGeofencesAdded ? R.string.geofences_added :
                            R.string.geofences_removed),
                    Toast.LENGTH_SHORT
            ).show();
        } else {
            // Get the status code for the error and log it using a user-friendly message.
            String errorMessage = GeoFenceErrorMessages.getErrorString(this,
                    status.getStatusCode());
            Log.e(TAG, errorMessage);
        }
    }

     /**
     * Ensures that only one button is enabled at any time. The Add Geofences button is enabled
     * if the user hasn't yet added geofences. The Remove Geofences button is enabled if the
     * user has added geofences.
     */
    private void setButtonsEnabledState() {
        if (mGeofencesAdded) {
            mAddGeofencesButton.setEnabled(false);
            mRemoveGeofencesButton.setEnabled(true);
        } else {
            mAddGeofencesButton.setEnabled(true);
            mRemoveGeofencesButton.setEnabled(false);
        }
    }    
}

和 GeofenceTransitionIntentService class,应该处理触发事件

package com.example.geofence;

import java.util.ArrayList;
import java.util.List;

import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingEvent;

import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

public class GeofenceTransitionsIntentService extends IntentService{

    protected static final String TAG = "geofence-transitions-service";

    public GeofenceTransitionsIntentService(String name) {
        super("name");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Toast.makeText(this, "OnHandleIntent", Toast.LENGTH_SHORT).show();
        Log.e(TAG, "OnHandleIntent");
        GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);       
        if (geofencingEvent.hasError()) {
            String errorMessage = GeoFenceErrorMessages.getErrorString(this,
                    geofencingEvent.getErrorCode());
            Log.e(TAG, errorMessage);
            return;
        }
        Log.e("DEBUG", "No geofencingEvent error");

         // Get the transition type.
        int geofenceTransition = geofencingEvent.getGeofenceTransition();
        Log.e("DEBUG", "getGeofenceTransition has been called");
        // Test that the reported transition was of interest.
        if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
                geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
            Log.e("DEBUG", "Geofence Transition enter or exit");
            // Get the geofences that were triggered. A single event can trigger multiple geofences.
            List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();

            // Get the transition details as a String.
            String geofenceTransitionDetails = getGeofenceTransitionDetails(
                    this,
                    geofenceTransition,
                    triggeringGeofences
            );

            // Send notification and log the transition details.
            sendNotification(geofenceTransitionDetails);
            Log.i(TAG, geofenceTransitionDetails);
        } else {
            // Log the error.
            Log.e(TAG, getString(R.string.geofence_transition_invalid_type, geofenceTransition));
        }   
    }

    /**
     * Gets transition details and returns them as a formatted string.
     *
     * @param context               The app context.
     * @param geofenceTransition    The ID of the geofence transition.
     * @param triggeringGeofences   The geofence(s) triggered.
     * @return                      The transition details formatted as String.
     */
    private String getGeofenceTransitionDetails(
            Context context,
            int geofenceTransition,
            List<Geofence> triggeringGeofences) {
        Log.e("DEBUG", "getGeofenceTransitionDetails");
        String geofenceTransitionString = getTransitionString(geofenceTransition);

        // Get the Ids of each geofence that was triggered.
        ArrayList triggeringGeofencesIdsList = new ArrayList();
        for (Geofence geofence : triggeringGeofences) {
            triggeringGeofencesIdsList.add(geofence.getRequestId());
        }
        String triggeringGeofencesIdsString = TextUtils.join(", ",  triggeringGeofencesIdsList);

        return geofenceTransitionString + ": " + triggeringGeofencesIdsString;
    }

    /**
     * Posts a notification in the notification bar when a transition is detected.
     * If the user clicks the notification, control goes to the MainActivity.
     */
    private void sendNotification(String notificationDetails) {
        // Create an explicit content Intent that starts the main Activity.
        Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
        Log.e("DEBUG", "sendNotification");
        // Construct a task stack.
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);

        // Add the main Activity to the task stack as the parent.
        stackBuilder.addParentStack(MainActivity.class);

        // Push the content Intent onto the stack.
        stackBuilder.addNextIntent(notificationIntent);

        // Get a PendingIntent containing the entire back stack.
        PendingIntent notificationPendingIntent =
                stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

        // Get a notification builder that's compatible with platform versions >= 4
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);

        // Define the notification settings.
        builder.setSmallIcon(R.drawable.ic_launcher)
                // In a real app, you may want to use a library like Volley
                // to decode the Bitmap.
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),
                        R.drawable.ic_launcher))
                .setColor(Color.RED)
                .setContentTitle(notificationDetails)
                .setContentText(getString(R.string.geofence_transition_notification_text))
                .setContentIntent(notificationPendingIntent);

        // Dismiss notification once the user touches it.
        builder.setAutoCancel(true);

        // Get an instance of the Notification manager
        NotificationManager mNotificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // Issue the notification
        mNotificationManager.notify(0, builder.build());
    }

    /**
     * Maps geofence transition types to their human-readable equivalents.
     *
     * @param transitionType    A transition type constant defined in Geofence
     * @return                  A String indicating the type of transition
     */
    private String getTransitionString(int transitionType) {
        Log.e("DEBUG", "getTransitionString");
        switch (transitionType) {
            case Geofence.GEOFENCE_TRANSITION_ENTER:
                return getString(R.string.geofence_transition_entered);
            case Geofence.GEOFENCE_TRANSITION_EXIT:
                return getString(R.string.geofence_transition_exited);
            default:
                return getString(R.string.unknown_geofence_transition);
        }
    }

}

清单:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.geofence"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.INTERNET" />

    <uses-sdk
        android:minSdkVersion="9"
        android:targetSdkVersion="21" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <meta-data android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name=".FetchAddressIntentService"
            android:exported="false"/>
        <service android:name=".GeofenceTransitionsIntentService"/>
    </application>

</manifest>

我刚刚通过删除GeofenceTransitionIntentService.java

的构造函数的参数解决了问题