Antipattern: freezing the UI with an AsyncTask

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

In my previous post I described how to freeze the UI with a Broadcast Receiver.

It is very important to understand which methods run in Main UI Thread.

In this post we can find an example OF WHAT NOT TO DO WITH AN ASYNCTASK.

We use an AsyncTask to execute background tasks in order to avoid the UI to become unresponsive, but not all methods run on the background thread.

In our AsyncTask class we must override doInBackground() which is invoked on the background thread.
There are three other optional methods each of which is invoked from the UI thread:
  • onPreExecute – this is called before doInBackground()

  • onProgressUpdate – this is called while doInBackground() is executing

  • onPostExecute – this is called after doInBackground()

If your app ties up the UI thread for more than 5 seconds, Android will throw up the Application Not Responding (ANR) dialog which gives the user the opportunity to quit your app.

In our BAD example (intentionally bad) we will launch a Async Task.
public class AsyncFreezingActivity extends SherlockActivity {

   protected AsyncTask mAsyncTask = null;

   private static final String TAG = "AsyncFreezingActivity";

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

  /**
   * Launch Async Task
   */
   private void launchAsyncTask() {
      Toast.makeText(this, getString(R.string.text_asynctask), 1000).show();
      new AsyncFreezyTask(this).execute();
   }

   @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:
           launchAsyncTask();
           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 onPreExecute method.
This means that our AsyncTask blocks any UI handling.
public class AsyncFreezyTask extends AsyncTask {
 
   private final Activity activity;
   private static final String TAG = "AsyncFreezyTask";

   private ProgressBar progress;
   private int count = 0;
   StringBuilder sb;

   public AsyncFreezyTask(Activity activity) {
     this.activity = activity;
   }

   @Override
   protected void onPreExecute() {
      progress = (ProgressBar) activity.findViewById(R.id.asyncTaskProgress);
      
      Log.d(TAG, "onPreExecute");

      // This is an example of WHAT NOT TO DO !! This method runs
      // in Main UI Thread
      networkCall();  //You should make network call in doInBackground() method
  
      //This is an example of WHAT NOT TO DO !!
      try{
          Thread.sleep(5000);
      }catch(Exception e){}
  
      Log.d(TAG, "onPreExecute END");
   }

   @Override
   protected String doInBackground(Void... params) {
      String ret = null;
      count = params.length;
      for (int i = 0; i < count; i++) {
         publishProgress(i);
      }
      if (sb!=null)
        ret = sb.toString();
      return ret;
   }

   @Override
   protected void onProgressUpdate(Integer... values) {
      progress.setMax(count);
      progress.setProgress(values[0]);
   }

   @Override
   protected void onPostExecute(String result) {

      // This is an example of WHAT NOT TO DO !! This method runs
      // in Main UI Thread
      parsing(result);

      Toast.makeText(activity, result, Toast.LENGTH_SHORT).show();
   }

  /**
   * Network call.... You should make network call in doInBackground() method  !!!
   */
   private void networkCall() {
      // something...
   }

   private void parsing(String br) {
      // something...
   }
}

A good way to illustrate and comprehend which methods are invoked from the UI thread is by using a debugger. Put a breakpoint in these 3 methods.

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

Here we can see where runs onPreExecute method:

We can see that it was called on the main UI thread.

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

You should avoid any long tasks in this method,particularly a network call.

When the breakpoint on the doInBackground is suspended in the debugger, we can see that it is not in main UI thread, but in a seperate thread.

In a similar way we can see how onPostExecute() is invoked from the UI thread.

You can get code from GitHub:

Comments

Post a Comment

Popular posts from this blog

AntiPattern: freezing a UI with Broadcast Receiver

NotificationListenerService and kitkat

How to centralize the support libraries dependencies in gradle