Sudhee
Sudhee

Reputation: 714

BroadcastReceiver in a Service : Design Issues

My App needs to listen to a broadcast event (HEADSET events) and act accordingly. I discovered that HEADSET events cannot be used via a minifest receiver as the flag FLAG_RECEIVER_REGISTERED_ONLY is enabled for this event. So, you can only register to this event dynamically inside your program. As my App would not be active during the events, I needed a service to handle this event , I register in onCreate and unregister in onDestroy. So far, so good. I implemented the service and started testing it, I discovered that the service can be killed by the system in case of a resource crunch and the system will automatically restart the service. However, the surprising thing is that the delay between kill and restart, which is completely random and could be HUGE. Sometimes, it was in seconds and other times it was in hours. So, you can never be sure that your service is running when the broadcast event happens !

I have browsed stackoverflow on that there were various suggestions. Have a dummy thread that does nothing, this will increase "importance" of the service, use service with startForeground - which causes a Notification. All these look like workarounds and not foolproof.

Is there any way of solving this in a foolproof manner ? What can be alternate solutions for handling "REGISTERED_ONLY" broadcast events ?

Thanks.

Upvotes: 2

Views: 703

Answers (1)

Sudhee
Sudhee

Reputation: 714

I did lot of trial and error programming and found a solution for this. So, I answer my own question.

Android Behaviour on Service Restarts: It is well known that an Android service can be force-stopped by the system in case there is a resource crunch. Android will automatically restart this service. This is unavoidable and you need to program your service to gracefully handle this scenario. Please note that there is a delay between service force-stop and restart. This delay is seemingly random and can be in hours. I discovered that there is pattern to this delay. After the very first force-stop, Android restarts the application in 5000ms (5 seconds). Further to this, if the service is force-stopped again (second time) within 60 seconds of first restart, Android updates the delay for next restart by 4 times (ie., 5000 x 4 = 20000ms = 20 seconds). After 20 seconds, Android restarts the service again (third time). If the service is again force-stopped (third time) within 60 seconds, after the third restart, the delay is updated to 4 times 20000 ms = 80000 ms = 80 seconds and so on. Consequently, if your service is getting force-killed within 60 seconds of automatic restart, the delay is increasing exponentially (5s, 20s, 80s, 320s, 1280s, ..........). If you service is not force-killed again within 60 seconds, the delay timer resets to 5 seconds for next force-stop and restart. In my opinion, having a restart timer of anything beyond 20 seconds is unreasonable, how are programmers supposed to write reliable apps with services if you are not even sure you service is running or not ?

Known Solutions & workarounds: There are several answers already in stack overflow which focus on how to ensure your service is not killed in the first place. Prime among them is to have the service start in foreground. See here to see how to do this. This causes a notification when the service is started. Starting a service in foreGround ensures that it is almost never killed. There were other suggested means like having a continuously running thread spawned from your service (this thread does not have to do anything). This is supposed to bump up the “importance” of the thread and service is not frequently killed. However, I suspect this may drain the battery.

New solution I discovered: I discovered another method of circumventing this issue, which does not need a foreGround service or a battery draining thread. You justed need to call startService() in your service in your onStartCommand when intent is null (ie., it is a system restart of the service, assuming the service create returned START_STICKY earlier). When you call startService(), the restart timer delay resets to 5 seconds always. Sample below

public class MyService extends Service {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) 
    {
        if( null == intent )
        {
            Log.d( "MyServiceTag", "This is a System Restart");
            /* restart the service with startService().
             * This will ensure that the delay between system force-stop and restart  
             * is always 5 seconds
             */
            startService( new Intent( getBaseContext(), MyService.class));
        }

        return START_STICKY;
    }


    @Override
    public void onDestroy()
    {
        Log.d( "MyServiceTag", "In onDestroy");
    }


    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }

}

If you have a more complicated scenario where you are passing parameters to your service via an intent and you are using START_REDELIVER_INTENT, you may need to use the “flags” to discover a restart and re-create the additional parameters in your intent before calling startService().

Worked for me. Hope this helps.

Upvotes: 6

Related Questions