Geofence Notification not triggered when app in background

只愿长相守 提交于 2019-12-10 15:27:11

问题


I have already gone through many SO posts , but nothing has worked yet for me. I am trying to trigger notification when device enters a geofence . But it does not trigger until app is opened. How would it be possible to trigger notification when app is in backgroud ?

Geofencing:

public class Geofencing implements ResultCallback {

    // Constants
    public static final String TAG = Geofencing.class.getSimpleName();
    private static final float GEOFENCE_RADIUS = 50; // 50 meters
    private static final long GEOFENCE_TIMEOUT = 24 * 60 * 60 * 1000; // 24 hours

    private List<Geofence> mGeofenceList;
    private PendingIntent mGeofencePendingIntent;
    private GoogleApiClient mGoogleApiClient;
    private Context mContext;

    public Geofencing(Context context, GoogleApiClient client) {
        mContext = context;
        mGoogleApiClient = client;
        mGeofencePendingIntent = null;
        mGeofenceList = new ArrayList<>();
    }

    /***
     * Registers the list of Geofences specified in mGeofenceList with Google Place Services
     * Uses {@code #mGoogleApiClient} to connect to Google Place Services
     * Uses {@link #getGeofencingRequest} to get the list of Geofences to be registered
     * Uses {@link #getGeofencePendingIntent} to get the pending intent to launch the IntentService
     * when the Geofence is triggered
     * Triggers {@link #onResult} when the geofences have been registered successfully
     */
    public void registerAllGeofences() {
        // Check that the API client is connected and that the list has Geofences in it
        if (mGoogleApiClient == null || !mGoogleApiClient.isConnected() ||
                mGeofenceList == null || mGeofenceList.size() == 0) {
            return;
        }
        try {
            LocationServices.GeofencingApi.addGeofences(
                    mGoogleApiClient,
                    getGeofencingRequest(),
                    getGeofencePendingIntent()
            ).setResultCallback(this);
        } catch (SecurityException securityException) {
            // Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
            Log.e(TAG, securityException.getMessage());
        }
    }

    /***
     * Unregisters all the Geofences created by this app from Google Place Services
     * Uses {@code #mGoogleApiClient} to connect to Google Place Services
     * Uses {@link #getGeofencePendingIntent} to get the pending intent passed when
     * registering the Geofences in the first place
     * Triggers {@link #onResult} when the geofences have been unregistered successfully
     */
    public void unRegisterAllGeofences() {
        if (mGoogleApiClient == null || !mGoogleApiClient.isConnected()) {
            return;
        }
        try {
            LocationServices.GeofencingApi.removeGeofences(
                    mGoogleApiClient,
                    // This is the same pending intent that was used in registerGeofences
                    getGeofencePendingIntent()
            ).setResultCallback(this);
        } catch (SecurityException securityException) {
            // Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
            Log.e(TAG, securityException.getMessage());
        }
    }


    /***
     * Updates the local ArrayList of Geofences using data from the passed in list
     * Uses the Place ID defined by the API as the Geofence object Id
     *
     * @param places the PlaceBuffer result of the getPlaceById call
     */
    public void updateGeofencesList(PlaceBuffer places) {
        mGeofenceList = new ArrayList<>();
        if (places == null || places.getCount() == 0) return;
        for (Place place : places) {
            // Read the place information from the DB cursor
            String placeUID = place.getId();
            double placeLat = place.getLatLng().latitude;
            double placeLng = place.getLatLng().longitude;
            // Build a Geofence object
            Geofence geofence = new Geofence.Builder()
                    .setRequestId(placeUID)
                    .setExpirationDuration(GEOFENCE_TIMEOUT)
                    .setCircularRegion(placeLat, placeLng, GEOFENCE_RADIUS)
                    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT)
                    .build();
            // Add it to the list
            mGeofenceList.add(geofence);
        }
    }

    /***
     * Creates a GeofencingRequest object using the mGeofenceList ArrayList of Geofences
     * Used by {@code #registerGeofences}
     *
     * @return the GeofencingRequest object
     */
    private GeofencingRequest getGeofencingRequest() {
        GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
        builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
        builder.addGeofences(mGeofenceList);
        return builder.build();
    }

    /***
     * Creates a PendingIntent object using the GeofenceTransitionsIntentService class
     * Used by {@code #registerGeofences}
     *
     * @return the PendingIntent object
     */
    private PendingIntent getGeofencePendingIntent() {
        // Reuse the PendingIntent if we already have it.
        if (mGeofencePendingIntent != null) {
            return mGeofencePendingIntent;
        }
        //Intent intent = new Intent(mContext, GeofenceBroadcastReceiver.class);
        Intent intent = new Intent("com.aol.android.geofence.ACTION_RECEIVE_GEOFENCE");
        mGeofencePendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.
                FLAG_UPDATE_CURRENT);
        return mGeofencePendingIntent;
    }

    @Override
    public void onResult(@NonNull Result result) {
        Log.e(TAG, String.format("Error adding/removing geofence : %s",
                result.getStatus().toString()));
    }

}

GeofenceBroadcastReceiver:

public class GeofenceBroadcastReceiver extends BroadcastReceiver {

    public static final String TAG = GeofenceBroadcastReceiver.class.getSimpleName();

    /***
     * Handles the Broadcast message sent when the Geofence Transition is triggered
     * Careful here though, this is running on the main thread so make sure you start an AsyncTask for
     * anything that takes longer than say 10 second to run
     *
     * @param context
     * @param intent
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        // Get the Geofence Event from the Intent sent through

        Log.d("onRecccc","trt");

        GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
        if (geofencingEvent.hasError()) {
            Log.e(TAG, String.format("Error code : %d", geofencingEvent.getErrorCode()));
            return;
        }

        // Get the transition type.
        int geofenceTransition = geofencingEvent.getGeofenceTransition();
        // Check which transition type has triggered this event

        // Send the notification
        sendNotification(context, geofenceTransition);
    }


    /**
     * Posts a notification in the notification bar when a transition is detected
     * Uses different icon drawables for different transition types
     * If the user clicks the notification, control goes to the MainActivity
     *
     * @param context        The calling context for building a task stack
     * @param transitionType The geofence transition type, can be Geofence.GEOFENCE_TRANSITION_ENTER
     *                       or Geofence.GEOFENCE_TRANSITION_EXIT
     */
    private void sendNotification(Context context, int transitionType) {
        // Create an explicit content Intent that starts the main Activity.
        Intent notificationIntent = new Intent(context, MainActivity.class);

        // Construct a task stack.
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);

        // 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
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);

        // Check the transition type to display the relevant icon image
        if (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER) {
            builder.setSmallIcon(R.drawable.ic_near_me_black_24dp)
                    .setLargeIcon(BitmapFactory.decodeResource(context.getResources(),
                            R.drawable.ic_near_me_black_24dp))
                    .setContentTitle("You have a task nearby")
                    .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
                    //Vibration
                    .setVibrate(new long[]{300,300})
                    .setLights(Color.RED, 3000, 3000);
                    //LED


        } else if (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT) {
            builder.setSmallIcon(R.drawable.ic_near_me_black_24dp)
                    .setLargeIcon(BitmapFactory.decodeResource(context.getResources(),
                            R.drawable.ic_near_me_black_24dp))
                    .setContentTitle(context.getString(R.string.back_to_normal));
        }

        // Continue building the notification
        builder.setContentText(context.getString(R.string.touch_to_relaunch));
        builder.setContentIntent(notificationPendingIntent);

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

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

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

}

EDIT :

 @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        //Create geofences from SharedPreferences/network responses
        //Connect to location services


        mClient = new GoogleApiClient.Builder(this)

                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .addApi(Places.GEO_DATA_API)
                .addApi(Places.PLACE_DETECTION_API)
                .build();

        mGeofencing = new Geofencing(this, mClient);
        GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
        if (geofencingEvent.hasError()) {
            Log.e("dsadsa", String.format("Error code : %d", geofencingEvent.getErrorCode()));
            return;
        }
    }


    public void onConnected(Bundle bundle) {
        //Add geofences
        mGeofencing.registerAllGeofences();

    }

I have done this so far , and still no luck ..


回答1:


I have the same exact issue, until the app is in foreground geofences work perfectly and are triggered every time device enter/exit but when the app goes in background I didn't get any notifications.

I suspected it was Android O background restriction but it isn't working on Android 7 too.

I've tried using a Broadcaster Receiver or Service, both works in foreground but not in background. There must something we missed (or a bug) because it's not possible that they are not working in background.




回答2:


I have the same problem when the application is on the background (user clicked on Home button, or multiple back until he see the Home Screen).

I tried to solve it with registering a BroadcastReceiver in the Manifest instead of IntentService.. It doesn't help so much as I've got the same results..

Then, I tried this: Open the application, added a geofence and went to the Home Screen. As you probably understand the geofence didn't trigger.. But when I clicked on Google Maps instead my application.. It was triggered!!

So it seems that is working on background if there are any apps that request for location updates (like google maps).

So I tried this approach: I created a sticky service for requesting location updates using the LocationServices.FusedLocationApi.. this service contains GoogleApiClient and implements GoogleApiClient.ConnectionCallbacks and GoogleApiClient.OnConnectionFailedListener

But guess what? It still doesn't work on background :(

Update: After I tried so many times to make it work.. it finally worked! I have an Android emulator with Google Play services (version 11.3.02) and Android 7.0 If you want a good explanation of how to work with Geofence and how to check it with the emulator take a look at this link

Now, I've tried geofencing with this emulator when the application is on the foreground and then in the background and it worked!

When I said that it didn't work for me on the background, the Android version on that emulator was Android 8. So I guess I need to find a solution for Android 8 -> a good start is this this documentation link where they explain how they handle now with background and foreground applications.




回答3:


The code that you posted is about registering geofences when app is running + handling geofences events. Additionally, according to the documentation, there are five events where you should re-register your geofences:

  1. The device is rebooted. The app should listen for the device's boot complete action, and then re- register the geofences required.
  2. The app is uninstalled and re-installed.
  3. The app's data is cleared.
  4. Google Play services data is cleared.
  5. The app has received a GEOFENCE_NOT_AVAILABLE alert. This typically happens after NLP (Android's Network Location Provider) is disabled.

Lets figure them out one by one:

Regarding 2 & 3 there's nothing to do, and if the geofences are assigned to some sort of authenticated activity in your app then you don't really want them at all.

Regarding 4, it's pretty much like 2 & 3, I haven't tried to dive deep into this but I don't think there's a way to listen to this event.

1 can be solved pretty easily by registering a BroadcastReceiver:

public class BootBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent startServiceIntent = new Intent(context, AddingGeofencesService.class);
        context.startService(startServiceIntent);
    }
}

Note AddingGeofencesService, a service you should create to add geofences once an intent is received in the BootBroadcastReceiver. Something like this:

public class AddingGeofencesService extends IntentService implements GoogleApiClient.ConnectionCallbacks {

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

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
            //Create geofences from SharedPreferences/network responses
            //Connect to location services
        }
    }

    public void onConnected(Bundle bundle) {
        //Add geofences
    }
    ...
}

And let's not forget the manifest code:

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

<service android:name=".AddingGeofencesService"/>

<receiver android:name=".BootBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

5 is referring mostly to changes in location providers. The solution for this case is a BroadcastReceiver as well.

public class LocationProviderChangedBroadcastReceiver extends BroadcastReceiver {
    boolean isGpsEnabled;
    boolean isNetworkEnabled;

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().matches("android.location.PROVIDERS_CHANGED"))
        {
            LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
            isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

            if (isGpsEnabled || isNetworkEnabled) {
                Intent startServiceIntent = new Intent(context, AddingGeofencesService.class);
                context.startService(startServiceIntent);
            }
        }
    }
}

Manifest:

<receiver
    android:name=".LocationProviderChangedBroadcastReceiver"
    android:exported="false" >
    <intent-filter>
        <action android:name="android.location.PROVIDERS_CHANGED" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</receiver>

EDIT:

I'm supplying here the code I use to manage geofences. It comes in addition to the answer above.

I excluded subclasses of LocationServicesManager that are not relevant to the answer.

/*
 * This class does not handle permission checks/missing permissions. The context that's containing
 * this class is responsible of that.
 */
public class LocationServicesManager implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

    private static final String TAG = "YOURTAG";

    private GoogleApiClient mGoogleApiClient;
    private Context context;

    public GeofencesManager geofencesManager;

    private OnGoogleServicesConnectedListener onGoogleServicesConnectedListener;

    public LocationServicesManager(Context context,
                                   OnGoogleServicesConnectedListener onGoogleServicesConnectedListener) {
        this.context = context;
        this.onGoogleServicesConnectedListener = onGoogleServicesConnectedListener;
        buildGoogleApiClient(context);
    }

    public void GeofencesManager() {
        geofencesManager = new GeofencesManager();
    }

    //region Definition, handling connection
    private synchronized void buildGoogleApiClient(Context context) {
        mGoogleApiClient = new GoogleApiClient.Builder(context)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
    }

    public void connect() {
        mGoogleApiClient.connect();
    }

    public void disconnect() {
        if (mGoogleApiClient.isConnected()) {
            mGoogleApiClient.disconnect();
        }
    }

    public boolean isConnected() {
        return mGoogleApiClient.isConnected();
    }

    @SuppressWarnings({"MissingPermission"})
    @Override
    public void onConnected(Bundle connectionHint) {
        onGoogleServicesConnectedListener.onGoogleServicesConnected();
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult result) {
        Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + result.getErrorCode());
    }


    @Override
    public void onConnectionSuspended(int cause) {
        // Trying to re-establish the connection.
        Log.i(TAG, "Connection suspended");
        mGoogleApiClient.connect();
    }
    //endregion

    public class GeofencesManager implements ResultCallback<Status> {

        private ArrayList<Geofence> mGeofenceList = new ArrayList<>();

        private PendingIntent mGeofencePendingIntent = null;

        private GeofencesManager() {

        }

        public void addGeofenceToList(String key, long expirationDuration, Location location, int radius) {
            addGeofenceToList(key, expirationDuration, new LatLng(location.getLatitude(), location.getLongitude()), radius);
        }

        public void addGeofenceToList(String key, long expirationDuration, LatLng location, int radius) {
            if (location != null) {
                mGeofenceList.add(new Geofence.Builder()
                        .setRequestId(key)
                        .setCircularRegion(location.latitude, location.longitude, radius)
                        .setExpirationDuration(expirationDuration)
                        .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_DWELL
                                | Geofence.GEOFENCE_TRANSITION_ENTER
                                | Geofence.GEOFENCE_TRANSITION_EXIT)
                        .setLoiteringDelay(1000 * 30)
                        .build());
            }
        }

        /**
         * Runs when the result of calling addGeofences() and removeGeofences() becomes available.
         * Either method can complete successfully or with an error.
         */
        public void onResult(@NonNull Status status) {
            if (status.isSuccess()) {
                Log.i(TAG, "onResult: " + status.toString());
            } else {
                Log.e(TAG, getGeofenceErrorString(status.getStatusCode()));
            }
        }

        /**
         * 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() {
            if (mGeofencePendingIntent != null) {
                return mGeofencePendingIntent;
            }

            Intent intent = new Intent(context, GeofenceTransitionsIntentService.class);
            // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
            return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        }

        /**
         * Builds and returns a GeofencingRequest. Specifies the list of geofences to be monitored.
         * Also specifies how the geofence notifications are initially triggered.
         */
        @NonNull
        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.
            // Empty mGeofenceList leads to crash
            builder.addGeofences(mGeofenceList);

            return builder.build();
        }

        public void addGeofences() {
            if (mGeofenceList.size() > 0) {
                try {
                    LocationServices.GeofencingApi.addGeofences(
                            mGoogleApiClient,
                            getGeofencingRequest(),
                            getGeofencePendingIntent()
                    ).setResultCallback(this);
                } catch (SecurityException securityException) {
                    Crashlytics.logException(securityException);
                    Log.e(TAG, "Missing permission ACCESS_FINE_LOCATION", securityException);
                }
            }
        }

        public void removeGeofences() {
            if (mGeofenceList.size() > 0) {
                LocationServices.GeofencingApi.removeGeofences(
                        mGoogleApiClient,
                        getGeofencePendingIntent()
                ).setResultCallback(this); // Result processed in onResult().
            }
        }
    }

    public static String getGeofenceErrorString(int errorCode) {
        switch (errorCode) {
            case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE:
                return "Geofence service is not available now";
            case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
                return "Your app has registered too many geofences";
            case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
                return "You have provided too many PendingIntents to the addGeofences() call";
            default:
                return "Unknown error: the Geofence service is not available now";
        }
    }
}

The aforementioned interface:

public interface OnGoogleServicesConnectedListener {
    void onGoogleServicesConnected();
}

Class GeofenceTransitionsIntentService:

/**
 * Listener for geofence transition changes.
 *
 * Receives geofence transition events from Location Services in the form of an Intent containing
 * the transition type and geofence id(s) that triggered the transition. 
 */
public class GeofenceTransitionsIntentService extends IntentService {

    private static final String TAG = "YOURTAG";

    public GeofenceTransitionsIntentService() {
        super(TAG);
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    /**
     * Handles incoming intents.
     * @param intent sent by Location Services. This Intent is provided to Location
     *               Services (inside a PendingIntent) when addGeofences() is called.
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
        //Do stuff with the geofencing events
    }
}

In manifest:

<service android:name=".GeofenceTransitionsIntentService"/>

And finally, summed-up AddingGeofencesService:

public class AddingGeofencesService extends IntentService implements OnGoogleServicesConnectedListener {

    private static final String TAG = "YOURTAG";

    LocationServicesManager locationServicesManager;

    public AddingGeofencesService() {
        super(TAG);
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
            locationServicesManager = new LocationServicesManager(this, this);
            locationServicesManager.GeofencesManager();

            //Fetch your geofences from somewhere
            List<YourGeofenceObject> yourGeofences = ...

            for (YourGeofenceObject geo : yourGeofences) {
                locationServicesManager.geofencesManager.addGeofenceToList(geo.getKey(),
                        geo.getExpirationDuration(), geo.getLocation(), geo.getRadius());
            }

            locationServicesManager.connect();
        }
    }

    @Override
    public void onGoogleServicesConnected() {
        locationServicesManager.geofencesManager.addGeofences();
    }
}

Note that you should add geofences when app is running in the same way that you add them in the AddingGeofencesService.



来源:https://stackoverflow.com/questions/45422591/geofence-notification-not-triggered-when-app-in-background

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!