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:

Comments

  1. But what WE MUST TO DO?
    where and how we have to run long tasks? For example, I need run five requests to the five different sites... how to implement it? without freezing UI...

    ReplyDelete
  2. It depends on your environment and what you want to achieve.
    It is important to use methods that do not run in the main thread.

    For long task, I would use a IntentService using the method onHandleIntent().
    If you have to update your activity, you can use a BroadcastReceiver that you can register in onResume() method, and unregister in onPause().

    Alternatively you can use an AsyncTask using the method doInBackground() or use your own Thread.

    ReplyDelete
  3. Thank you for your response.
    My task is: when you press the button - to update the information about the object. To do this, program makes a request on a few different sites, parses response, the result is stored in the database, and, if the activity is still alive, modifies it.

    Do I understand correct, that all requests should be executed in onHandleIntent from IntentService? With BroadcastReceiver everything clear.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Thank you for this wonderful information looking forward for more. I really appreciate your efforts to write such amazing piece of content for us. Are you looking for android services at reasonable prices.

    ReplyDelete
  6. Hey,
    Thank you, I appreciate that I am getting a lot of good and reliable information from your post. Thanks for sharing such a kind of wonderful post about Line Freezing.

    ReplyDelete

Post a Comment

Popular posts from this blog

Snippet: align a TextView around an image

NotificationListenerService and kitkat