Antipattern: freezing the UI with a Service and an IntentService

The worst thing that can happen to your app's responsiveness is an "Application Not Responding" (ANR) dialog.

In my previous posts I described how to freeze the UI with a Broadcast Receiver and with a Async Task.

In this post I'll freeze the UI with a Service and with an IntentService.

A Service is an application component that can perform long-running operations in the background.
We should read official doc very carefully.
  • A Service is not a separate process. The Service object itself does not imply it is running in its own process
  • A Service is not a thread. It is not a means itself to do work off of the main thread (to avoid Application Not Responding errors)
It should be simple...if you do use a service, it still runs in the main thread of its hosting by default, so you should still create a new thread within the service if it performs intensive or blocking operations.
The standard pattern for implementing a Service is to create and run a new Thread from onStartCommand() to perform the processing in the background, and then stop the Service when it’s been completed.

I have often seen code in onStartCommand() method that creates a separate Thread.
This is fine, but do not forget that this method is not executed in separate thread.

If you launch service from your Activity, onStartCommand() will be execute in main GUI Thread.

In our BAD example (intentionally bad) we will launch a Service.
public class ServiceFreezingActivity extends SherlockActivity {

    protected ServiceFreezy mService = null;

    private static final String TAG = "ServiceFreezingActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.service);
       getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }
 
    /**
     * 
     */
    private void newEvent() {
       Toast.makeText(this, getString(R.string.text_newevent), 1000).show();
    }

    /**
     * Launch Async Task
     */
    private void launchService() {
       Toast.makeText(this, getString(R.string.text_service), 1000).show();
       Intent serviceIntent = new Intent(this,ServiceFreezy.class);
       startService(serviceIntent);
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
       getSupportMenuInflater().inflate(R.menu.freezing_menu, menu);
       return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
       switch (item.getItemId()) {
       case android.R.id.home:
          NavUtils.navigateUpFromSameTask(this);
          return true;
       case R.id.menu_refresh:
          launchService();
          return true;
       case R.id.menu_newevent:
          newEvent();
          return true;
       }

       return super.onOptionsItemSelected(item);
    }
}
In our example we will take "a long running task" in onStartCommand() method. This means that our Service blocks any UI handling.
public class ServiceFreezy extends Service {

    private static final String TAG = "ServiceFreezy";

    public ServiceFreezy() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

       // This is an example of WHAT NOT TO DO !!
       // A Service is not a separate process.
       // This method runs in Main Thread.... avoid long task in this method
       try {
          Thread.sleep(7500);
       } catch (Exception e) {}

       Log.d(TAG, "onStartCommand END");

       OwnThread thread = new OwnThread();
       thread.start();

       return START_STICKY;
    }

    @Override
    public void onCreate() {
       super.onCreate();
       Log.d(TAG, "onCreate");
    }

    private class OwnThread extends Thread {
      @Override
      public void run() {

         Log.d(TAG, "Separate Thread");
         try {
            Thread.sleep(10000);
         } catch (Exception e) {}
         Log.d(TAG, "Separate Thread END");
      }
    }

    @Override
    public IBinder onBind(Intent intent) {
       return null;
    }
}

Launch app, click refresh button and the freezing up of the UI is gone!

Here we can see where runs onStartCommand() method:

...."Application Not Responding" (ANR) dialog...

We can see as OwnThread instance runs in separate Thread.

We can do the same with an IntentService.
It is a subclass of Service that uses a worker thread to handle all start requests, one at a time.
If we see source code we can find where it creates a thread.

You should pay attention to OnCreate() and onStartCommand() methods.
Both methods run in Main Thread... therefore also in this case you should avoid long task in these methods.
Indeed, you should not override onStartCommand() method for your IntentService.

It is very simple to obtain a ANR dialog with an IntentService.
This is our bad code.
public class IntentServiceFreezy extends IntentService {

   private static final String TAG = "IntentServiceFreezy";

   public IntentServiceFreezy() {
      super("IntentService");
   }

   @Override
   public void onCreate() {
      super.onCreate();
  
      // This is an example of WHAT NOT TO DO !!
      // This method runs in Main Thread.... avoid long task in this method
      try {
         Thread.sleep(2500);
      } catch (Exception e) {
      }
  
      Log.d(TAG, "onCreate");
   }

   @Override
   public int onStartCommand(Intent intent, int flags, int startId) {

      // This is an example of WHAT NOT TO DO !!
      // You should not override this method for your IntentService.
      // This method runs in Main Thread.... avoid long task in this method
      try {
        Thread.sleep(7500);
      } catch (Exception e) {}

      Log.d(TAG, "onStartCommand END");

      return super.onStartCommand(intent, flags, startId);
   }

   @Override
   protected void onHandleIntent(Intent intent) {

      Log.d(TAG, "Separate Thread");
      try {
         Thread.sleep(1000);
      } catch (Exception e) {}
      Log.d(TAG, "Separate Thread END");

    }
}
Here you can see where runs OnCreate() method.

We can see as onHandleIntent() instance runs in separate Thread.

You can get code from GitHub:

Popular posts from this blog

Expand and collapse animation

How to centralize the support libraries dependencies in gradle

Android-5: Card and images with rounded corners in Android 4