简体   繁体   English

BroadcastReceiver的onReceive()被意外调用

[英]BroadcastReceiver has onReceive() called unexpectedly

I have an app in which I want to perform lookup some data every, say, 15 mins. 我有一个应用程序,我想在其中每15分钟执行一次查找一些数据。 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. 为此,我认为我应该使用BroadcastReceiver来监视网络状态的变化。 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. 其中MyAsyncTask是一个内部类,它将在服务完成后导致stopSelf()。

This kinda works but: 这种方法有效,但是:

  1. I'm getting (according to logcat) far more calls to NetworkMonitor.doStuff() than I would expect. 我(根据logcat)对NetworkMonitor.doStuff()调用比我期望的要多得多。 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. 看来,即使服务已停止(在异步任务正确完成之后), NetworkMonitor实例仍在接收有关网络状态更改的意图。 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? 我是否需要一个变量来将NetworkMonitor()实例存储在服务中,还是可以只拥有一个这样的匿名实例? Looking at the docs the BroadcastReceiver should clear itself up after onReceive() has finished. 查看文档, BroadcastReceiver应该在onReceive()完成后清除自身。
  3. Why do I need NetworkMonitor.mDoingStuff ? 为什么需要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? 我猜想,如果我能弄清楚为什么在onReceive()完成后NetworkMonitor无法清除自身,那么我可能不再需要它了吗?
  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. 看来,即使服务已停止(在异步任务正确完成之后),NetworkMonitor实例仍在接收有关网络状态更改的意图。 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? 我是否需要一个变量来将NetworkMonitor()实例存储在服务中,还是可以只拥有一个这样的匿名实例?

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 查看文档,广播接收器应在onReceive()完成后清除自身

A manifest-registered BroadcastReceiver lives for a single broadcast. 清单注册的BroadcastReceiver用于单个广播。 A BroadcastReceiver registered via registerReceiver() lives until unregisterReceiver() . 通过registerReceiver()注册的BroadcastReceiver一直存在,直到unregisterReceiver()为止。

Why do I need NetworkMonitor.mDoingStuff? 为什么需要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. 首先,由于您无法多次execute() AsyncTask实例,因此您将在第二次广播时崩溃。

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 . 第三,如果您想要一种服务做一件事情,然后走了,请使用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. 我有一个应用程序,我想在其中每15分钟执行一次查找一些数据。 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 . 使您的AlarmManager事件路由到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 . 如果您使用的是_WAKEUP警报类型,这尤其重要,因为只有使用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). 在该BroadcastReceiveronReceive() ,如果您具有网络连接,请向IntentService发送命令以完成工作(并且,如果使用的是_WAKEUP警报类型,请考虑我的WakefulIntentService ,以便设备在您处于睡眠状态时保持清醒状态)重新这样做)。

  • 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. 相反,如果似乎没有网络连接,请让您的BroadcastReceiver启用另一个清单注册的BroadcastReceiver来监视CONNECTIVITY_ACTION事件-为此,请使用PackageManagersetComponentEnabledSetting()

  • 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). CONNECTIVITY_ACTION BroadcastReceiveronReceive() ,如果确定现在已建立网络连接,请启动IntentService (与已经建立连接时从AlarmManager接收器执行的操作相同)。

  • In the IntentService / WakefulIntentService , do your work in onHandleIntent() . IntentService / WakefulIntentService ,在onHandleIntent()完成您的工作。 This already has a background thread and will already call stopSelf() when there is no more work to be done. 它已经有一个后台线程,并且在没有更多工作要做时已经调用了stopSelf()

  • In the IntentService / WakefulIntentService , in onDestroy() , disable the CONNECTIVITY_ACTION BroadcastReceiver via PackageManager and setComponentEnabledSetting() , returning you to your original state. IntentService / WakefulIntentServiceonDestroy() ,通过PackageManagersetComponentEnabledSetting()禁用CONNECTIVITY_ACTION BroadcastReceiver ,使您返回到原始状态。

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. 如果连接被阻止了一段时间(例如,飞行模式),则不会像在此处那样注册N个接收器并设置N个AsyncTasks Instead, you will get control again whenever connectivity changes occur in the future after your alarm has gone off. 取而代之的是,在您的警报响起以后,只要将来发生连接更改,您都将再次获得控制。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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