简体   繁体   中英

BroadcastReceiver has onReceive() called unexpectedly

I have an app in which I want to perform lookup some data every, say, 15 mins. I have a service that I start with an alarm, but I also want to make sure that there is a network connection before I start looking.

To do this I think I should use a BroadcastReceiver to watch for changes to the network state. I have wrapped a broadcast receiver to help with this:

public abstract class NetworkMonitor extends BroadcastReceiver
{
  boolean mDoingStuff;

  public abstract void doStuff();

  public NetworkMonitor()
  {
    mDoingStuff = false;

    IntentFilter networkStateFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
    MyApp.getContext().registerReceiver(this, networkStateFilter);      
  }

  @Override
  public void onReceive(Context context, Intent intent)
  {
    // network state changes, you can process it, information in intent
    ConnectivityManager cn = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo info = ConnectivityManagerCompat.getNetworkInfoFromBroadcast(cn, intent);

    // Only use Wifi Connections for updating this
    if (info.isConnectedOrConnecting() && !mDoingStuff)
    {
      mDoingStuff = true;
      doStuff();
    }
  }
}

I then use that in a service like:

public class WidgetUpdateService extends Service
{
  @Override
  public int onStartCommand(Intent intent, int flags, int startId)
  {
    // Build the async task to get the data
    final MyAsyncTask mTask = new MyAsyncTask();

    // Register an interest in  when the network changes
    new NetworkMonitor(false)
    {
      public void doStuff()
      {
        mTask.execute();
      }
    };

    // Make sure that if we get shut down then we get started again correctly.
    return START_REDELIVER_INTENT;
  }


  protected class MyAsyncTask extends AsyncTask<Void, Void, Void>
  {
    public MyAsyncTask()
    {
    }

    @Override
    protected Integer doInBackground(Void... arg0)
    {
      // do work
    }

    @Override
    protected void onPostExecute(Integer result)
    {
      WidgetUpdateService.this.stopSelf();
    }

    @Override
    protected void onCancelled(Integer result)
    {
      WidgetUpdateService.this.stopSelf();
    }
  }
}

Where MyAsyncTask is an inner class that will cause the service to stopSelf() when it has completed.

This kinda works but:

  1. I'm getting (according to logcat) far more calls to NetworkMonitor.doStuff() than I would expect. It seems that even when the service has been stopped (after the async task has finished correctly) the NetworkMonitor instance is still being receiving intents about changes to the network state. Why is this?
  2. Do I need to have a variable to store the NetworkMonitor() instance in the service, or can I just have an anonymous instance like this? Looking at the docs the BroadcastReceiver should clear itself up after onReceive() has finished.
  3. Why do I need NetworkMonitor.mDoingStuff ? I'm guessing that if I can work out why the NetworkMonitor is not clearing itself up after onReceive() has finished then I may not need it any more?
  4. Is this a sensible way of doing this or am I asking for trouble?

Please let me know if you need any more info, I'll be happy to supply it.

This kinda works

It's ghastly code, IMHO.

It seems that even when the service has been stopped (after the async task has finished correctly) the NetworkMonitor instance is still being receiving intents about changes to the network state. Why is this?

Because you never unregister the receiver. It will keep going -- and leaking memory like a sieve -- until your process is terminated.

Do I need to have a variable to store the NetworkMonitor() instance in the service, or can I just have an anonymous instance like this?

You need to have an instance so you can unregister it later. Registration and unregistration of the receiver should be done by the service; your register-the-receiver-in-its-constructor is part of what makes your code ghastly IMHO.

Looking at the docs the BroadcastReceiver should clear itself up after onReceive() has finished

A manifest-registered BroadcastReceiver lives for a single broadcast. A BroadcastReceiver registered via registerReceiver() lives until unregisterReceiver() .

Why do I need NetworkMonitor.mDoingStuff?

You have bigger problems.

Is this a sensible way of doing this

Not really.

First, you will crash on your second broadcast, as you cannot execute() an AsyncTask instance multiple times.

Second, see the aforementioned failing-to-unregister issues.

Third, if you want to have a service that does one thing, then goes away, use an IntentService .

So, let's roll all the way back to the top:

I have an app in which I want to perform lookup some data every, say, 15 mins. I have a service that I start with an alarm, but I also want to make sure that there is a network connection before I start looking.

The right way to do this is:

  • Have your AlarmManager event route to a BroadcastReceiver . This is particularly important if you are using a _WAKEUP alarm type, as such events are only reliable if you use a BroadcastReceiver PendingIntent .

  • In that BroadcastReceiver , in onReceive() , if you have a network connection, send a command to an IntentService to do the work (and, if you are using a _WAKEUP alarm type, consider my WakefulIntentService , so the device stay awake while you're doing this).

  • If, instead, there does not appear to be a network connection, have your BroadcastReceiver enable another manifest-registered BroadcastReceiver set up to watch for CONNECTIVITY_ACTION events -- use PackageManager and setComponentEnabledSetting() for this.

  • In the CONNECTIVITY_ACTION BroadcastReceiver , in onReceive() , if you determine that you now have a network connection, kick off your IntentService (same as what you would do from the AlarmManager receiver if you already had a connection).

  • In the IntentService / WakefulIntentService , do your work in onHandleIntent() . This already has a background thread and will already call stopSelf() when there is no more work to be done.

  • In the IntentService / WakefulIntentService , in onDestroy() , disable the CONNECTIVITY_ACTION BroadcastReceiver via PackageManager and setComponentEnabledSetting() , returning you to your original state.

This way:

  1. You don't leak memory, as you are doing here.

  2. You don't have to mess with threading code, as you are doing here.

  3. You don't have to worry about whether or not your process gets kicked out of memory in between the alarm and gaining connectivity.

  4. If connectivity is blocked for some time (eg, airplane mode), you don't register N receivers and set up N AsyncTasks , as you are doing here. Instead, you will get control again whenever connectivity changes occur in the future after your alarm has gone off.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM