Question

I've a static class called "BatteryManagerHelper" wich monitors battery status via the Intent.ACTION_BATTERY_CHANGED intent. Each time system notifies a change in battery status, my class catch the changes, stores new values in the class public properties (charge, status, healt..) then raises the event "Changed"

public static event Action Changed;

Each Activity or Class in my project can subscribe to this event with code:

BatteryManagerHelper.Changed += BatteryManagerHelper_Changed;

private void BatteryManagerHelper_Changed()
{
  imgBattery.SetImageResource(some_resource);
}

it works fine.

However when Activity goes in OnPause() or OnDestory() I'd like to unsubscribe from the Change event, so no more notifications are made to my Activity.

I've tried with

BatteryManagerHelper.Changed -= BatteryManagerHelper_Changed;

and

BatteryManagerHelper.Changed -= null;

and

BatteryManagerHelper.Changed += null;

but no one seems to work. I get the event also in the Activity has been destroyed from the system.

So, first question is: how to correctly unsubscribe from events? Second question is: why a destroyed activity can continue to receive events?

Note: subscribe and unsubscribe code is placed on OnResume() and OnPause(). When Activity is paused then resumed I get two event notifications; on the tirth pause-resume I get three consecutive notifications and so on. It really seems that += is adding a new event listener fine, while -= is not able to remove the listener, keeping previous listeners references in play.

This is the static class code. I used a static class so it can be accessible anywhere in the code. Static class has (obviuslly) a private instance of a child-class for BroadcastReceiver callbacks.

using Android.Content;
using Android.Os;

using System;

namespace MenuDroidApp
{
  public static class BatteryManagerHelper
  {   
    private static BatteryBroadcastReceiverHelper batteryBroadcastReceiver = null;

    private static Intent batteryStatusIntent = null;

    public static event Action Changed;

    public enum HealthEnum    
    { 
      Unknown = 1,            // BATTERY_HEALTH_UNKNOWN
      Good,                   // BATTERY_HEALTH_GOOD
      OverHeat,               // BATTERY_HEALTH_OVERHEAT
      Dead,                   // BATTERY_HEALTH_DEAD
      OverVoltage,            // BATTERY_HEALTH_OVER_VOLTAGE
      UnspecifiedFailure,     // BATTERY_HEALTH_UNSPECIFIED_FAILURE
      Cold,                   // BATTERY_HEALTH_COLD
    }

    public enum PluggedEnum    
    { 
      NotPlugged = 0,
      PluggedAC,              // BATTERY_PLUGGED_AC
      PluggedUSB,             // BATTERY_PLUGGED_USB
      PluggedWireless,        // BATTERY_PLUGGED_WIRELESS
    }

    public enum StatusEnum    
    { 
      Unknown = 1,            // BATTERY_STATUS_UNKNOWN
      Charging,               // BATTERY_STATUS_CHARGING
      Discharging,            // BATTERY_STATUS_DISCHARGING
      NotCharging,            // BATTERY_STATUS_NOT_CHARGING
      Full,                   // BATTERY_STATUS_FULL     
    }

    public static int             Charge      { get; set; }    
    public static StatusEnum      Status      { get; set; }
    public static PluggedEnum     Plugged     { get; set; }
    public static HealthEnum      Health      { get; set; }
    public static bool            Present     { get; set; }
    public static string          Technology  { get; set; }
    public static int             Temperature { get; set; }
    public static int             Voltage     { get; set; }
    public static int             IconResID   { get; set; }

    static BatteryManagerHelper()
    {
      batteryBroadcastReceiver  = new BatteryBroadcastReceiverHelper();

      batteryBroadcastReceiver.Changed += batteryBroadcastReceiver_Changed;
    }

    private static void batteryBroadcastReceiver_Changed()
    {
      if (Changed != null) Changed();
    }

    public static void Start()
    {       
      batteryStatusIntent = Common.ApplicationContext.RegisterReceiver(batteryBroadcastReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

      batteryBroadcastReceiver.GetBatteryInfo(batteryStatusIntent);      
    }

    public static void Stop()
    {       
      Common.ApplicationContext.UnregisterReceiver(batteryBroadcastReceiver);
      batteryStatusIntent = null;
    }

    private class BatteryBroadcastReceiverHelper : BroadcastReceiver
    {
      public event Action Changed;

      public override void OnReceive(Context context, Intent intent)
      {
        GetBatteryInfo(intent);
      }

      public void GetBatteryInfo(Intent intent)
      { 
        BatteryManagerHelper.Charge       = intent.GetIntExtra(BatteryManager.EXTRA_LEVEL,  -1);
        BatteryManagerHelper.Status       = (StatusEnum)intent.GetIntExtra(BatteryManager.EXTRA_STATUS, 0);
        BatteryManagerHelper.Health       = (HealthEnum)intent.GetIntExtra(BatteryManager.EXTRA_HEALTH, 0);       
        BatteryManagerHelper.Plugged      = (PluggedEnum)intent.GetIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
        BatteryManagerHelper.Present      = intent.GetBooleanExtra(BatteryManager.EXTRA_PRESENT, false);
        BatteryManagerHelper.Technology   = intent.GetStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
        BatteryManagerHelper.Temperature  = intent.GetIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1);
        BatteryManagerHelper.Voltage      = intent.GetIntExtra(BatteryManager.EXTRA_VOLTAGE, -1);        
        BatteryManagerHelper.IconResID    = intent.GetIntExtra(BatteryManager.EXTRA_ICON_SMALL, -1);        


        if (Changed != null) Changed();
      }
    }  
  }

}

This is the code in my Fragment (this fragment is a System Toolbar replacement, my app is a fullscreen one), but you can also try in classic Activity class, on events OnResume() and OnPause()

public override void OnAttach(Activity activity)
{
  BatteryManagerHelper.Changed += BatteryManagerHelper_Changed;
}

public override void OnDetach()
{
  BatteryManagerHelper.Changed -= BatteryManagerHelper_Changed;
}

private void BatteryManagerHelper_Changed()
{
  imgBattery.SetImageResource(some_resource_id);
}

Ok, I've tried a different approach when subscribe/unsubscribe from event:

private Action batteryManager_Callback = null;

public override void OnResume()
{
  base.OnResume();

  batteryManager_Callback = new Action(BatteryManagerHelper_Changed);
  BatteryManagerHelper.Changed += batteryManager_Callback;
}

public override void OnPause()
{
  base.OnPause();

  BatteryManagerHelper.Changed -= batteryManager_Callback;
}

private void BatteryManagerHelper_Changed()
{
  imgBattery.SetImageResource(some_res_id);
}

Now it works fine. I use a new delegate object for each OnResume activity event and subscribe to BatteryManager event with that delegate. Delegate reference is also stored on private class member. When I want to unsubscribe, I use the same original delegate reference created in the OnResumed() and I'm sure that the same one specific delegate will be removed from event listeners queue.

So it seems that original code

public override void OnAttach(Activity activity)
{
  BatteryManagerHelper.Changed += BatteryManagerHelper_Changed;
}

public override void OnDetach()
{
  BatteryManagerHelper.Changed -= BatteryManagerHelper_Changed;
}

reference to callback function BatteryManagerHelper_Changed() is different at the moment of += and the moment of -=. But the Activity class instance is the same, this is very strange.. this example in C#/.NET enviroment is working fine.

Any idea about this behaviour? Is this correct by design?

thanks

Was it helpful?

Solution

When you compile this code:

private void BatteryManagerHelper_Changed()
{
  imgBattery.SetImageResource(some_res_id);
}

BatteryManagerHelper.Changed += BatteryManagerHelper_Changed;

Then the rhs is not of type delegate, and it will implicitly be wrapped by an instance of Action. The same happens when you unsubscribe:

BatteryManagerHelper.Changed -= BatteryManagerHelper_Changed;

This implicit wrapping does not happen when you do this:

batteryManager_Callback = new Action(BatteryManagerHelper_Changed);
...
BatteryManagerHelper.Changed += batteryManager_Callback;
...
BatteryManagerHelper.Changed -= batteryManager_Callback;

Due to the wrapping, the event handlers are not treated as the same instances.

Although this explains what you are seeing, it is not correct behavior. It will be fixed in the next release.

UPDATE

You can follow this issue here https://github.com/dot42/dot42/issues/13.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top