java.lang.IllegalArgumentException: Receiver not registered

I have TrackingService component to track the location of the buses in my city based on Crowdsourcing. The TrackingService is operating in the background, then the data is transmitted to the server. I have an Map Activity to display the location of the buses, the user selected in the MainActivity( as Filter).

The background TrackingService is started in the MainActivity when the app launches.

I notifing the map activity about the updated lcoation with the aid of the BroadcastReceiver as in the code below. The data is being retrieved to the the map activity but I am facing problem to unregister my bReceiver. I want to unregister the broadcast when the app goes in the background or when the user presses the back button but I am getting the error below:

How can I fix it?

Error:

08-27 22:43:04.594: E/AndroidRuntime(19588): FATAL EXCEPTION: main
08-27 22:43:04.594: E/AndroidRuntime(19588): Process: com.bustracker, PID: 19588
08-27 22:43:04.594: E/AndroidRuntime(19588): java.lang.RuntimeException: Unable to stop activity {com.bustracker/com.bustracker.Map}: java.lang.IllegalArgumentException: Receiver not registered: com.bustracker.Map$1@2483d256
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4156)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4219)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.app.ActivityThread.access$1500(ActivityThread.java:177)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1502)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.os.Handler.dispatchMessage(Handler.java:102)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.os.Looper.loop(Looper.java:145)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.app.ActivityThread.main(ActivityThread.java:5944)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at java.lang.reflect.Method.invoke(Native Method)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at java.lang.reflect.Method.invoke(Method.java:372)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1389)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1184)
08-27 22:43:04.594: E/AndroidRuntime(19588): Caused by: java.lang.IllegalArgumentException: Receiver not registered: com.bustracker.Map$1@2483d256
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.app.LoadedApk.forgetReceiverDispatcher(LoadedApk.java:822)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.app.ContextImpl.unregisterReceiver(ContextImpl.java:2038)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.content.ContextWrapper.unregisterReceiver(ContextWrapper.java:528)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at com.bustracker.Map.onStop(Map.java:418)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.app.Instrumentation.callActivityOnStop(Instrumentation.java:1275)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.app.Activity.performStop(Activity.java:6493)
08-27 22:43:04.594: E/AndroidRuntime(19588):    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4151)
08-27 22:43:04.594: E/AndroidRuntime(19588):    ... 10 more

TrackingService class:

public class TrackingService extends Service implements
        LocationListener {
    public double pLong;
    public double pLat;
    ...
        @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        detectLocation();
        return START_STICKY;
    }
    private void detectLocation() {
        lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 30 * 1000, 0,
                this);
    }
    @Override
    public void onLocationChanged(Location location) {

        if (location != null) {
        pLong = location.getLongitude();
        pLat = location.getLatitude();

        Intent intent = new Intent(Map.RECEIVE_latLng);
        intent.putExtra("location",location);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
           .....

     }  

}

Map activity:

    public class Map extends FragmentActivity implements OnMapReadyCallback   {
   public static final String RECEIVE_latLng = "com.bustracker.RECEIVE_latLng";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.map);

        LocalBroadcastManager bManager = LocalBroadcastManager.getInstance(this);
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(RECEIVE_latLng);
        bManager.registerReceiver(bReceiver, intentFilter);

    }


    private BroadcastReceiver bReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if(intent.getAction().equals(RECEIVE_latLng)) {
                              Location location = intent.getParcelableExtra("location");
             double lng = location.getLongitude();
             double lat = location.getLatitude();
             LatLng ll = new LatLng(lat, lng);
             MarkerOptions markerOpt = new MarkerOptions().title("My Location")
                        .position(ll);
             System.out.println("ABC map: "+ lat + " ; " + lng);
             myLocatMarker = map.addMarker(markerOpt);
            }
          }
        };      
      }
@Override
protected void onStop() {
    super.onStop();
    unregisterReceiver(bReceiver);      
}

Solution 1:

If you register in onCreate(), you have to unregister in onDestroy(). If you want to unregister in onStop() you have to register in onStart().

Have a look at the activity lifecycle here http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle

The reason for this is that onStop() is called when the Activity goes into the background, but is not necessarily destroyed. When the Activity comes back to the foreground onStart() is called, but not onCreate() so the BroadcastReceiver isn't re-registered. Then when the Activity goes back into the background, onStop() tries to unregister again, but the receiver has not been registered.

You also need to use the LocalBroadcastManager to unregister your receiver if you used it to register it like so:

LocalBroadcastManager.getInstance(this).unregisterReceiver(bReceiver);

LocalBroadcastManager is a class from the support library:

Helper to register for and send broadcasts of Intents to local objects within your process.

This is different from the same methods on Context which allow system-wide broadcasts.

Also see a similar question/answer here.

Solution 2:

(Of course, if you want) You may register or unregister them in onStop() and onResume() just wrap it with try-catch:

@Override
public void onResume(){
    super.onResume()
    try{ 
        getActivity().registerReceiver(receiver,filter); 
    }catch (Exception e){
        // already registered
    }
}

Or

@Override
public void onStop(){
    try{ 
        getActivity().unregisterReceiver(receiver);  
    }catch (Exception e){
        // already unregistered
    }
    super.onStop()
}

Solution 3:

Keep in mind that you must register and unregister on the same context. For example, don't register w/ the application context and unregister w/ the activity context.

Don't do this

getApplicationContext().registerReceiver(myReceiver, myIntentFilter);
unregisterReceiver(myReceiver);

Do this instead (inside an activity)

registerReceiver(myReceiver, myIntentFilter);
unregisterReceiver(myReceiver);

I normally register inside onPostResume() or onResume() and unregister in onPause().

Examples:

protected void onPostResume() {
    super.onPostResume();

    registerReceiver(myReceiver, myIntentFilter);
}

protected void onPause() {
    unregisterReceiver(myReceiver);

    //called after unregistering
    super.onPause();
}