2

I have an audio recording app, for which I am currently developing a widget.

Recording is performed by an audio engine running in a service put in the foreground state.

Whenever the audio engine state changes to pause/play/record, a broadcast is sent, and handled by a receiver which updates the widget.

So that clicking on the record button in the widget starts recording, which causes the broadcast to be sent, and in the end the record button to turn red in the widget. Same thing happens if you start recording from within the app instead of the widget.

Everything's good so far.

Now, if the service gets killed for some reason, onDestroy() is not called and I don't have a chance to send a pause or shutdown broadcast so that the record button turns off. Result: the button stays red whereas recording has stopped.

This is some of the worst thing that can happen.

Why? The user takes a look at his home screen, he/she sees the red button and thinks recording is still being performed.

It's not like with music playback, where the user can notice when music stops playing through the headphones... With recording, you don't hear nothing if it stops.

I can understand the need for the system to kill services. I'm fine with that. I don't need the service to get restarted and all that.

But I need to update the widget if the service shuts down, so that the user is not misled, thinking that his interview/concert/rehearsal/memo is still being recorded, while it's not.

So how can I update the widget quickly if the service gets killed?

olivierg
  • 10,200
  • 4
  • 30
  • 33

3 Answers3

2

I have found what I consider to be an answer to my own question.

I return START_STICKY from onStartCommand() in my service.

If I manually kill the service from DDMS, I get this in logcat:

I/ActivityManager( 2500): Process com.foo.bar (pid 21874) has died.
W/ActivityManager( 2500): Scheduling restart of crashed service com.foo.bar/.FooBarService in 5000ms

And a few seconds later:

I/ActivityManager( 2500): Start proc com.foo.bar for service com.foo.bar/.FooBarService: pid=22036 uid=10107 gids={1015, 3003} 

The service does restart, and that allows me to send a broadcast from onStartCommand() to indicate that the audio engine is paused (I don't resume recording automatically).

The widget provider reacts to this broadcast and updates the widget appropriately.

This works on Froyo and solves my problem: the user is not misled when looking at the widget. Recording stopped because the engine crashed, and the widget reflects this.

It's not bullet-proof, as it relies on the system restarting the service automatically, but I think that's quite safe.

olivierg
  • 10,200
  • 4
  • 30
  • 33
0

Foreground service's process will be killed when user swipes it away in recent tasks list.

At swipe moment android will call (*) you service's onTaskRemoved method. You can update your widget right there:

@Override
public void onTaskRemoved(Intent rootIntent) {
    Log.i(TAG, "onTaskRemoved, stopping service");

    AppWidgetManager widgetManager       = AppWidgetManager.getInstance(this);
    ComponentName    widgetComponentName = new ComponentName(this, MyWidgetProvider.class);
    widgetManager.updateAppWidget(widgetComponentName, buildWidgetRemoteViews());

    stopSelf();
}

If you call stopSelf inside onTaskRemoved, service will finish normally: onDestroy will be called and it will not be scheduled for restart.
If you do not call stopSelf, Android will kill process and continue as with usual service kill - according to onStartCommand return value:

  • START_NOT_STICKY - service will not be restarted
  • START_STICKY - service will be restarted with empty intent
  • ... etc

Let me emphasis this - even if you don't call stopSelf, service will stop functioning right upon onTaskRemoved exit. So you can't send broadcast or schedule something on Handler. However, if you really want broadcast, use AlarmManager:

final int requestCode = 101;
final AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
final PendingIntent clearWidget = PendingIntent.getBroadcast(
        this,
        requestCode,
        getClearWidgetIntent(),
        PendingIntent.FLAG_UPDATE_CURRENT);

// if broadcast arrives while process is shutting down, widget update may fail
// 100ms was always OK on devices I've tested and instantaneous for user
final long startDelay = 100;
alarmManager.set(AlarmManager.ELAPSED_REALTIME,
                 SystemClock.elapsedRealtime() + startDelay,
                 clearWidget);

I've faced the same problem as you and originally solved it the same way - cleared widget in onStartCommand. But on one of the devices service's restart has been always scheduled in 5 seconds, which is very visible to the user and makes a feeling of a slow app.

Notes:
*) actually it could be not called - see docs

Alexander Malakhov
  • 3,383
  • 2
  • 33
  • 58
0

So how can I update the widget quickly if the service gets killed?

You can't. Hence, use startForeground() to indicate to Android that your service is part of the foreground user experience.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • My service is already put in the foreground state as mentioned in my question. I've never seen it killed in my tests, but according to some reports, I'm pretty sure that this happened to some users (who run a lot of apps, etc...). What about using a separate watch dog service to monitor the audio engine service? This could also be useful in case of sigsev or uncaught exception (very unlikely but nothing's perfect). – olivierg Apr 23 '11 at 14:29
  • @olivierg: "I'm pretty sure that this happened to some users" -- if you say so. "What about using a separate watch dog service to monitor the audio engine service?" -- personally, I'd just dump the app widget, or redesign it to not try to show the live recording state. App widgets are not designed to handle this scenario. The user will know if recording is going on based upon the `Notification` you supplied to `startForeground()`. – CommonsWare Apr 23 '11 at 14:38
  • @CommonsWare: yes it happened to some users, and also more on certain devices than on some others. Yes, there's a notification, but the red record widget button is much more attractive to the eye. Not showing the recording state is odd, especially when the competition is showing it together with the current time refreshed every second. I don't display a such clock to avoid frequent widget updates. But this clock also helps the user ensure that everything's ok. So isn't this a limit case where frequent widget updates could make sense? Recording is already consuming power anyway.. – olivierg Apr 23 '11 at 14:50
  • @olivierg: "So isn't this a limit case where frequent widget updates could make sense?" ::shrug:: Beats me. I have not attempted to implement audio recording, let alone measure its power consumption. – CommonsWare Apr 23 '11 at 15:11
  • @CommonsWare: This isn't strictly about audio recording. It's a case where the device is very busy doing something important, where it isn't going to sleep, and so where certain considerations about other types of widget may not apply. It seems like the "don't update your widget often" rule is intended to prevent widgets from consuming little power repeatedly on long periods of time. But, frequent widget updates here would only occur during the hard work session, that is for a relatively short period of time. Is it so heavy to update a widget every second during say 30 minutes? – olivierg Apr 23 '11 at 15:24
  • Ok, I posted a new question about this very topic: http://stackoverflow.com/questions/5765212/is-it-ok-to-update-a-widget-frequently-for-a-short-period-of-time – olivierg Apr 23 '11 at 15:54