Android中Geofencing搭配JobIntentService无法正常工作的问题
I’ve run into this exact issue before—Android 8.0+ background restrictions make direct Service launches from Geofence triggers unreliable. Here’s a step-by-step solution to get your Geofence logic working with JobIntentService:
Core Problem
GeofencingClient uses a PendingIntent to trigger your service, but Android 8+ blocks implicit service starts in the background. JobIntentService relies on enqueueWork() instead of direct startService() calls, so we need to add a BroadcastReceiver as a middle layer to bridge the gap.
Step 1: Update Geofence PendingIntent to Use a BroadcastReceiver
Instead of pointing the PendingIntent directly to your JobIntentService, create one that triggers a BroadcastReceiver:
private PendingIntent getGeofencePendingIntent(Context context) { Intent intent = new Intent(context, GeofenceBroadcastReceiver.class); // Use FLAG_IMMUTABLE for Android 12+ compliance, FLAG_UPDATE_CURRENT to reuse the intent return PendingIntent.getBroadcast( context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); }
Step 2: Create the BroadcastReceiver to Enqueue Work
This receiver will pass the Geofence intent to your JobIntentService using the required enqueueWork() method:
public class GeofenceBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // Forward the Geofence event to the JobIntentService GeofenceJobIntentService.enqueueWork(context, intent); } }
Step 3: Rewrite Your Service as JobIntentService
Replace your IntentService with JobIntentService, moving your onHandleIntent() logic to onHandleWork() (the JobIntentService equivalent):
public class GeofenceJobIntentService extends JobIntentService { public static final int GEOFENCE_JOB_ID = 1001; private GeofencingClient mGeofencingClient; private boolean isSettingGeofence = false; private String geofenceRequestID = "YOUR_GEOFENCE_ID"; // Use your existing ID // Static method to enqueue work (required for JobIntentService) public static void enqueueWork(Context context, Intent intent) { enqueueWork( context, GeofenceJobIntentService.class, GEOFENCE_JOB_ID, intent ); } @Override protected void onHandleWork(@NonNull Intent intent) { // Copy your original onHandleIntent() logic here GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); if (geofencingEvent != null) { int transitionType = geofencingEvent.getGeofenceTransition(); if (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT) { Location triggering = geofencingEvent.getTriggeringLocation(); if (triggering != null) { Utils.appendLog( "GEOFENCE onHandleWork got an EXIT transition: " + triggering.getLatitude() + ", " + triggering.getLongitude() + " / accuracy: " + triggering.getAccuracy(), "D", "#updategeo " + Constants.GEOFENCE ); } else { Utils.appendLog( "GEOFENCE onHandleWork got an EXIT transition null", "D", "#updategeo " + Constants.GEOFENCE ); } Utils.appendLog("removing geofence after exit", "I", Constants.TRACKER + "#updategeo"); removeGeofencesAndStartTracking(); } } } // Keep your existing addGeofence logic, with small adjustments public void addGeofence(final Context context, final Location location, float radius) { if (!isSettingGeofence) { isSettingGeofence = true; ArrayList<Geofence> geofences = new ArrayList<>(); geofences.add(new Geofence.Builder() .setRequestId(geofenceRequestID) .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_EXIT) .setCircularRegion(location.getLatitude(), location.getLongitude(), radius) .setExpirationDuration(Geofence.NEVER_EXPIRE) .build()); if (geofences.size() > 0) { if (mGeofencingClient == null) mGeofencingClient = LocationServices.getGeofencingClient(context); if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { Utils.appendLog("Geofence Transitions Service addGeofence checkSelfPermission ERROR", "E", Constants.GEOFENCE); isSettingGeofence = false; // Reset state to allow future attempts return; } Utils.appendLog("addGeofences is being called, wait for success or failure callbacks", "I", Constants.TRACKER + "#updategeo"); mGeofencingClient.addGeofences(getGeofencingRequest(geofences), getGeofencePendingIntent(context)) .addOnSuccessListener(aVoid -> { PSLocationService.getInstance(context).lastTimeDidExitRegion = null; isSettingGeofence = false; // Realm logic (updated to use try-with-resources for safety) final RealmLocation realmLocation = new RealmLocation(location.getLatitude(), location.getLongitude(), location.getTime(), null, true); realmLocation.setAccuracy(location.getAccuracy()); realmLocation.setSpeed(location.getSpeed()); final GeofenceRealmLocation geofenceRealmLocation = new GeofenceRealmLocation(geofenceRequestID, realmLocation); try (Realm realm = Realm.getInstance(PSApplicationClass.Config)) { realm.executeTransaction(r -> r.copyToRealmOrUpdate(geofenceRealmLocation)); } // Clear notification try { NotificationManager notifManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notifManager.cancel(1010); } catch (Exception e) { Utils.appendLog("Error removing notification: " + e.getMessage(), "I", Constants.GEOFENCE); } Utils.appendLog("Geofence Transitions Service setGeofenceRequest Success adding geofences!" + location.getLatitude() + " , " + location.getLongitude() + " / accuracy: " + location.getAccuracy(), "I", "#updategeo " + Constants.GEOFENCE); }) .addOnFailureListener(e -> { Utils.appendLog("GEOFENCETEST addGeofences FAILURE: " + e.getMessage(), "E", Constants.GEOFENCE); isSettingGeofence = false; // Complete your incomplete location provider check logic here try { LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); boolean networkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); if (!networkEnabled) { new Handler(Looper.getMainLooper()).post(() -> { // Add logic to prompt user to enable location services }); } } catch (Exception ex) { Utils.appendLog("Error checking location provider: " + ex.getMessage(), "E", Constants.GEOFENCE); } }); } } } // Helper methods (adjusted to fit JobIntentService) private GeofencingRequest getGeofencingRequest(ArrayList<Geofence> geofences) { return new GeofencingRequest.Builder() .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_EXIT) .addGeofences(geofences) .build(); } private void removeGeofencesAndStartTracking() { if (mGeofencingClient != null) { mGeofencingClient.removeGeofences(getGeofencePendingIntent(this)) .addOnSuccessListener(aVoid -> { Utils.appendLog("Geofence removed successfully", "I", Constants.TRACKER + "#updategeo"); PSLocationService.getInstance(this).startTracking(); }) .addOnFailureListener(e -> { Utils.appendLog("Failed to remove geofence: " + e.getMessage(), "E", Constants.GEOFENCE); }); } } }
Step 4: Register Components in AndroidManifest.xml
Make sure to add the BroadcastReceiver and JobIntentService with the required permission:
<receiver android:name=".GeofenceBroadcastReceiver" /> <service android:name=".GeofenceJobIntentService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false" />
Key Fixes & Best Practices
- PendingIntent Type: Using
getBroadcast()instead ofgetService()avoids Android 8+ background service restrictions. - JobIntentService Launch: Always use
enqueueWork()instead of direct service starts—this is non-negotiable for JobIntentService. - Android 12+ Compliance: Added
FLAG_IMMUTABLEto the PendingIntent to avoid runtime crashes. - State Reset: Reset
isSettingGeofenceon failure to ensure future Geofence attempts work. - Resource Management: Used try-with-resources for Realm to prevent memory leaks.
内容的提问来源于stack exchange,提问作者rosu alin

