13

Okay, so I'm working on an AppWidget that checks the battery level and displays it on a TextView. My code looks like this:

public class BattWidget extends AppWidgetProvider {

private RemoteViews views = new RemoteViews("com.nickavv.cleanwidgets", R.layout.battlayout);

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int appWidgetIds[]) {
    final int N = appWidgetIds.length;
    context.getApplicationContext().registerReceiver(this,new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
    for (int i = 0; i < N; i++) {
        int appWidgetId = appWidgetIds[i];
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }
}

@Override
public void onReceive(Context context, Intent intent) {
    super.onReceive(context, intent);
    Log.d("onReceive", "Received intent " + intent);
    if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
        Integer level = intent.getIntExtra("level", -1);
        views.setTextViewText(R.id.batteryText, level+"%");
        AppWidgetManager myAWM = AppWidgetManager.getInstance(context);
        ComponentName cn = new ComponentName(context, AirWidget.class);
        onUpdate(context, myAWM, myAWM.getAppWidgetIds(cn));
    }
}
}

And I'm getting concerned because as soon as I drop the widget onto my homescreen it begins firing off about 100 of those Log calls a second, saying it's receiving ACTION_BATTERY_CHANGED. Isn't this only supposed to be broadcast for each percent decrease? It actually caused my entire launcher to lag, I had to uninstall it. That can't be right.

Nick
  • 6,900
  • 5
  • 45
  • 66
  • Don't really know anything about this but is it worth setting up a service to monitor the battery and feed that back to the widgit at a predefined interval? – mAndroid Oct 02 '11 at 10:57

2 Answers2

26

My code looks like this:

You cannot register a BroadcastReceiver from another BroadcastReceiver and get reliable results. Android will terminate your process, because it doesn't think anything is running. The only way to listen for ACTION_BATTERY_CHANGED will be to register that receiver from an activity or a service.

Isn't this only supposed to be broadcast for each percent decrease?

Where do you see that documented? AFAIK, ACTION_BATTERY_CHANGED will be broadcast whenever the hardware feels like it. Also, bear in mind that other data changes within that Intent, such as temperature.

If you want to implement this app widget, do not register for ACTION_BATTERY_CHANGED the way you are. Instead:

  • Allow the user to choose a polling period via a SharedPreference (e.g., once a minute, once every 15 mintues)
  • Use AlarmManager to give you control on that polling period via a getBroadcast() PendingIntent
  • In that BroadcastReceiver, call registerReceiver() for ACTION_BATTERY_CHANGED but with a null BroadcastReceiver, as this will return to you the last Intent that was broadcast for that action (note: you will still need to use getApplicationContext() for this)
  • Use AppWidgetManager to update your app widget instances with the battery level pulled out of the Intent you retrieved in the preceding step (note: if you are setting them all to be the same, you do not need to iterate over the IDs -- use the updateAppWidget() that takes a ComponentName as a parameter)

This has several advantages:

  1. You do not care how often ACTION_BATTERY_CHANGED is broadcast
  2. The user gets to control how much battery you consume by doing these checks (should be negligible if you keep the polling period to a minute or more)
  3. Your process can be safely terminated in between polls, thereby making it less likely that users will attack you with task killers and semi-permanently mess up your app
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 3
    That can't be the best way to go about this. I've seen plenty of closed-source battery widgets that update their information as soon as a change occurs (BattStatt is a good example). I've never seen one that offers the user an update interval. Battery level widgets are supposed to always be accurate. – Nick Oct 02 '11 at 16:52
  • 1
    @Nick: "That can't be the best way to go about this" -- you are entitled to your opinion. "I've seen plenty of closed-source battery widgets that update their information as soon as a change occurs" -- which they do by wasting a bunch of RAM by keeping a service in memory all of the time. Users think that developers who do this are idiots, why is why task killers are so popular and the OS has to proactively kill such services off. – CommonsWare Oct 02 '11 at 17:15
  • @Nick: "Battery level widgets are supposed to always be accurate" -- since the battery level does not change particularly frequently, a polling architecture can be more than sufficiently accurate, while avoiding the RAM hit and inherent unreliability of keeping a service around all the time. – CommonsWare Oct 02 '11 at 17:16
  • Alright, you've got me convinced. Now, you say to register ACTION_BATTERY_CHANGED in "that" BroadcastReceiver. Would that be the onReceive function of my AppWidgetProvider? Because I tried that and it doesn't seem to be registering at all. – Nick Oct 02 '11 at 17:44
  • @Nick: "Would that be the onReceive function of my AppWidgetProvider?" -- no, it would be in a `BroadcastReceiver` you set up to receive alarms from `AlarmManager`. When the alarm event occurs based on your polling period, you get the then-current battery level via `registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));`. – CommonsWare Oct 02 '11 at 17:48
  • @CommonsWare Can you tell me why it's important to use getApplicationContext()? You indicated that in your third bullet point above. The onReceive method in my BroadcastReceiver has a context in its list of parameters. I've used it directly when registering my receiver for ACTION_BATTERY_CHANGED (in the manner that you advised). I've also used context.getApplicationContext(). etc. etc. In my testing I see no difference in behaviour. Thanks. – UpLate Sep 22 '13 at 09:44
  • @UpLate: "Can you tell me why it's important to use getApplicationContext()?" -- it used to crash when you tried using `registerRecevier()` directly using the passed-in `Context` to `onReceive()`, saying that registering a receiver with this `Context` was not allowed, even though we were not actually registering a receiver. Perhaps they relaxed that restriction in the past couple of years. Still, I would assume that at least Android 2.x has the restriction. – CommonsWare Sep 22 '13 at 11:12
  • @CommonsWare Ahhhhh!! Right you are. 1st gen Nexus 7, Android 4.3, with or without getApplicationContext(), behaviour is the same. Nexus One, Android 2.3.6, with getApplicationContext() and all's well. Without getApplicationContext() causes a crash. I'm supporting Gingerbread devices so this little tidbit has been valuable. Thanks. – UpLate Sep 22 '13 at 22:31
0

Well, your onUpdate is registering its own class as receiver for the batteryinfo intent. This intent is then immediately triggered for the first info. Your onReceive is calling your onUpdate again. We call this a loop. Hence the 100 logs a second ...