Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

Threading in Android

, 30 Apr 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Threading in Android

Introduction

Again another resume article about Android. This time I will cover threading. And in the accompanying source code, there is an application for Android allowing you to experiment with the various options available.

Not many assumptions are made on the knowledge required for understanding this article. A basic knowledge of Android programming and knowing the concept of a thread should suffice.

Background

Long Running Tasks on the UI Thread

A usual Android application has only a single thread on which all work done by your application is executed. This thread is known as the UI thread. That also includes anything done in your eventhandlers and any updating of the user interface. The result of this is that if you are doing anything in an eventhandler that can take a long time to execute, the UI of your application will freeze/not be updated during this time. What's even more, Android will show a dialog telling the user that the application is not responding!

buttonDoIt.setOnClickListener(
new Button.OnClickListener(){   
  @Override  public void onClick(View arg0) 
  {   
    LongRunningTask();
  }       
});

In which the method LongRunningTask looks like:

public void LongRunningTask()
{
  for(int i = 0; i < cnt; i++)
  {
    try {
      Thread.sleep(1000);
    } catch (Exception e) {
      Log.v("Error: ", e.toString());
    }
    // You will not see anything of this because
    //    this loop is blocking the updating of the UI
    //  so you will not see the textViews text being set
    textView.setText("Progress: " + i);
  }
}

What you need to do is execute your long running task on another thread than the UI thread. For this, you will need to create an Android thread.

Long Running Tasks on their Own Thread

To execute a long running task in another thread, you create a new thread and implement the long running code in the thread’s run() method. After this, you start the thread by calling its start() method.

However, there is a caveat here to. You cannot just update your UI from this other thread because of thread synchronization issues. Android enforces that updating the UI can only be done from a single thread: the UI thread. And therefore, you must delegate the updating back to the UI thread. You do this by inserting a message into the messagequeue of the UI thread. I will explain this a bit further, but for now, here’s the code how you do this.

// This handler will be associated with the UI thread
Handler uiHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    textView.setText("Progress: " + msg.what);            
  }
};

private void CreateThread() {
  Thread t = new Thread() {
    public void run() {
      for(int i = 0; i < cnt; i++)
      {
        try {
          Thread.sleep(1000);
        } catch (Exception e) {
          Log.v("Error: ", e.toString());
        }
        if(feedBackByHandler)
        {
          // This is not allowed and will throw an exception.
          textView.setText("Progress: " + i);
        }
        else
        {
          // You can update the UI by sending messages to the UI thread
          uiHandler.sendMessage(uiHandler.obtainMessage(i));
        }
      }
    }
  };
  t.start();
}

Long Running Tasks on their Own Thread using AsyncTask

In the above item, you had to write a lot of boilerplate code for doing something as simple as performing a long running task and informing the UI of the progress made. Fortunately, there is a simpler way: you can create your own class derived from AsyncTask.

The AsyncTask class provides 4 methods you can override:

  • doInBackground: Here you do the actual work. This method cannot access any UI controls and is executed on a separate thread.
  • onProgressUpdate: Called when publishing progress updates by calling publishProgress in doInBackground. You can directly access any UI controls in this method because it is executed on the UI thread.
  • onPreExecute: Called just before calling doInBackground. You can directly access any UI controls in this method because it is executed on the UI thread.
  • onPostExecute: Called just after calling doInBackground. You can directly access any UI controls in this method because it is executed on the UI thread.

What this does is execute your work on a separate thread, but handle all the messaging needed to update your UI on the UI thread. As a result, the code becomes:

class LongRunningAsyncTask extends AsyncTask<String, Integer, Integer> {
  @Override
   protected Integer doInBackground(String... dummy) {
    int i = 0;
    for(; i < cnt; i++)
    {
      try {
        Thread.sleep(1000);
      } catch (Exception e) {
        Log.v("Error: ", e.toString());
      }
      if(feedBackInBackground)
      {
        // This is not allowed and will throw an exception.
        textView.setText("Progress: " + i);
      }
      else
      {
        // By publishing your progress, Android calls the onProgressUdate method  
        //    with the value provided
        publishProgress(i);
      }
    }
    
    return i;
   }

  @Override
   protected void onProgressUpdate(Integer... progress) {
     textView.setText("Progress: " + progress[0]);
   }

  @Override
   protected void onPreExecute() {
     textView.setText("Started!");
   }

  @Override
   protected void onPostExecute(Integer result) {
     textView.setText("Finished with " + result + "!");
   }
}
   
@Override
public void onCreate(Bundle savedInstanceState) {
  
   Button buttonDoIt = (Button)findViewById(R.id.buttonDoIt);       
   buttonDoIt.setOnClickListener(
    new Button.OnClickListener(){   
      @Override  public void onClick(View arg0) 
      {   
        feedBackInBackground = chkOnBackground.isChecked();
        new LongRunningAsyncTask().execute("");
      }       
    });     
}

Threads, Loopers and Handlers

You will surely have an idea of what a thread is, but what is a handler and a looper?

A handler is the Android way of making it possible to handle custom messages in the messageloop of a thread. Okay, what does this mean?

As you already know, a thread is a flow of execution in your application and it can only do one single thing at a time. If you provide it an implementation of the run() method, it will execute this method and when the method is finished, the thread will end its execution and be no longer available.

In code, you would have something like this:

Thread t = new Thread() {
  public void run() {
    // whatever you do here, when your 
    //    code gets at the end of this method
    //    the thread will be finished
  }
};
t.start();

You can imagine that you would like a thread which is always available to you to do some stuff. You could create a queue in which you insert messages and those messages tell the thread what to do. In the thread, you write a loop which continuously checks this queue for messages and if you find one, you execute the corresponding work. This is called “the messageloop”.

In code, you would have something like this:

Thread t = new Thread() {
  public void run() {
    for(;;)
    {
      // we are effectively in an infinite loop,
      //    so this thread will never stop
      // Check if we have anything to do
      if(messageQueue.size() != 0)
      {
        // do whatever is in the messagequeue
      }
    }
  }
};
t.start();

Threads you create yourself have no such messageloop by default, but the UI thread does. This messageloop is used to process the messages send by the Android operating system to your application. If you click a button, this results in a message pushed on the messagequeue, and the messageloop pulls it from this queue and handles it by calling your onClick event handler. While it is executing your onClick handler, it cannot do anything else. The creation of the queue and the managing of messages in the queue is done by a Looper object. If you want to put your own custom messages in the queue to be processed by this thread, you need a Handler object: if the Looper sees your custom message in the messagequeue, it will call the Handler of the thread and hand it your message. In the handleMessage of your custom Handler, you then process this message.

Resuming we have the following:

  • Thread: A thread is a flow of execution in your program
  • Looper: A looper implements a for loop and a queue into which messages can be posted. Inside the for-loop, it checks for messages in the queue.
  • Handler: A handler is called by a looper to process the messages in the queue. The handler must know what to do by the id of the message.
  • Message: A message identifies some work to perform. You create messages by calling any of the obtainMessage methods of the handler.

A Thread can have only a single Looper and a single Handler associated with it. So if you create a Looper or a Handler in a thread, Android will check if one exists already and if so, will hand you the existing one, and if not it will create a new object for you and automatically associate the looper or handler with the thread on which you created the object.

Long Running Tasks using Handlers: Blocking the UI Thread

So, the UI Thread already has a Looper associated with it implementing the default application messageloop. If you supply it a handler, then this will get used by the messageloop.

Because a thread can only do a single thing at a time, if you execute a long running operation in your handler, the messageloop cannot handle any other messages, like subsequent clicking of for example a checkbox.

// This handler will be associated with the UI thread, hence this long running 
//    operation will prevent the ui thread of processing any other messages posted
//    to its messagequeue
Handler uiHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    for(int i = 0; i < cnt; i++)
    {
      try {
        Thread.sleep(1000);
      } catch (Exception e) {
        Log.v("Error: ", e.toString());
      }
    }
    
    textView.setText(textView.getText()+"Did you succeed?");            
  }
};    
   
@Override
public void onCreate(Bundle savedInstanceState) {
  
  Button buttonDoIt = (Button)findViewById(R.id.buttonDoIt);       
  buttonDoIt.setOnClickListener(
      new Button.OnClickListener(){   
        @Override  public void onClick(View arg0) 
        {   
          // Send a custom message to the UI thread
          // This will start the long running operation on the UI thread
          uiHandler.sendMessage(uiHandler.obtainMessage()); 
        }       
      });
}

Long Running Tasks using Handlers: Not Blocking the UI Thread

To have a handler that does not block the UI thread, you must associate it with a different thread, thus you must create a new thread, then associate a new looper with it and finally a handler. And it is to this handler you must send your message.

public class HandlerNonBlockingHandlerActivity extends Activity {
  
  Handler threadHandler = null;
  
  // This handler is created on the UI thread en thus associated with that thread
  //    this means that any code executed by this handler is executed on the UI thread
  Handler uiHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      textView.setText(textView.getText()+"Did you succeed?");            
    }
  };
    
  private void CreateThread() {
  
    // We create a new thread
    Thread t = new Thread() {
      public void run() {
        
        Looper.prepare();
        
        // In this thread we create a handler which is associated with this thread
        //    thus, everything executed by this handler is executed on this separate thread
        //    and as a result, we are not blocking the UI thread
        threadHandler = new Handler() {
          @Override
          public void handleMessage(Message msg) {
            for(int i = 0; i < cnt; i++)
            {
              try {
                Thread.sleep(1000);
              } catch (Exception e) {
                Log.v("Error: ", e.toString());
              }
            }
            uiHandler.sendMessage(uiHandler.obtainMessage());         
          }
        };
        
        Looper.loop();

      }
    };
    t.start();
  }
     
  @Override
  public void onCreate(Bundle savedInstanceState) {
    
        Button buttonDoIt = (Button)findViewById(R.id.buttonDoIt);       
        buttonDoIt.setOnClickListener(
            new Button.OnClickListener(){   
              @Override  public void onClick(View arg0) 
              {   
                threadHandler.sendMessage(threadHandler.obtainMessage()); 
              }       
          });
        
        CreateThread();
  }
}

Do Try This At Home: The Code

The code has seven activities demonstrating the concepts explained above.

When you startup the application, you see the following screen:

Each entry in the list demonstrates a concept and provides controls to experiment.

Action On UIThread: LongRunningTaskOnUIThread

This sample demonstrates what you should not do: call the long running method in the onClick handler.

public class LongRunningTaskOnUIThread extends Activity {
     
  public void LongRunningTask()
  {
    for(int i = 0; i < cnt; i++)
    {
      try {
        Thread.sleep(1000);
      } catch (Exception e) {
        Log.v("Error: ", e.toString());
      }
      // You will not see anything of this because
      //    this loop is blocking the updating of the UI
      //  so you will not see the textViews text being set
      textView.setText("Progress: " + i);
    }
  }
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.text_view);
      textView=(TextView)findViewById(R.id.textView);        
      editTextTaskDuration=(EditText)findViewById(R.id.editTextTaskDuration);
      editTextTaskDuration.setText("" + cnt);
      
      Button buttonDoIt = (Button)findViewById(R.id.buttonDoIt);       
      buttonDoIt.setOnClickListener(
         new Button.OnClickListener(){   
           @Override  public void onClick(View arg0) 
           {   
            String taskDurationAsString = editTextTaskDuration.getText().toString();
            cnt = Integer.parseInt(taskDurationAsString);
             LongRunningTask();
           }       
       });
           
  }
  
  private TextView textView;    
  private EditText editTextTaskDuration;
  private int cnt = 5;
}

Running the sample shows the following screen:

If you push the button, there are three things to notice:

  1. Although the code updates a textbox inside the loop, when running the program the text in the textbox does not get updated until after about 5 seconds (or whatever task duration you filled in in the edit box): the time it takes to finish the loop.
  2. When clicking the checkbox during the first 5 seconds after pressing the button, the checkbox does not alter its state.
  3. If you set the task duration to a high value (20 did it for me), you’ll get an exception. That is because Android notices you are blocking the UI thread.

These two different things happening have a common cause: your loop is executed on the UI thread and therefore prohibits any processing of other events.

Action On Thread: LongRunningTaskOnOwnThread

This activity demonstrates executing a long running operation on another thread and giving feedback about progression in the UI.

public class LongRunningTaskOnOwnThread extends Activity {
  
  // This handler will be associated with the UI thread
  Handler uiHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      textView.setText("Progress: " + msg.what);            
    }
  };
    
  private void CreateThread() {
    Thread t = new Thread() {
      public void run() {
        for(int i = 0; i < cnt; i++)
        {
          try {
            Thread.sleep(1000);
          } catch (Exception e) {
            Log.v("Error: ", e.toString());
          }
          if(feedBackByHandler)
          {
            // This is not allowed and will throw an exception.
            textView.setText("Progress: " + i);
          }
          else
          {
            // You can update the UI by sending messages to the UI thread
            uiHandler.sendMessage(uiHandler.obtainMessage(i));
          }
        }
      }
    };
    t.start();
  }
     
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.text_view_withcheckbox);
    textView=(TextView)findViewById(R.id.textView);
    chkOnHandler = (CheckBox)findViewById(R.id.checkBoxConfig);
    editTextTaskDuration=(EditText)findViewById(R.id.editTextTaskDuration);
    editTextTaskDuration.setText("" + cnt);
    
        Button buttonDoIt = (Button)findViewById(R.id.buttonDoIt);       
        buttonDoIt.setOnClickListener(
            new Button.OnClickListener(){   
              @Override  public void onClick(View arg0) 
              {                       
                feedBackByHandler = chkOnHandler.isChecked();
                String taskDurationAsString = editTextTaskDuration.getText().toString();
                cnt = Integer.parseInt(taskDurationAsString);
                CreateThread();
              }       
          });
              
  }
  
  private TextView textView;
  private CheckBox chkOnHandler;
  private EditText editTextTaskDuration;
  private int cnt = 5;
  private boolean feedBackByHandler = true;
}

Running the sample shows the following screen:

The checkbox “Update on custom thread” allows to select on which thread the UI should be updated, the UI thread being the only correct one of course. So if you check the option the application will crash, what is to be expected.

Running this code you will notice two things, the opposite of what happened above:

  1. If you select the correct update option (see above), you will notice you receive the correct feedback in the UI.
  2. If you try to check the bottom checkbox, you will succeed.

By executing your long running operation on a different thread, you are no longer blocking the UI thread, thus allowing the UI to respond to other events.

Action with Async: LongRunningTaskWithAsyncTask

In the previous activity, we had to write a lot of boiler plate code. This activity does exactly the same but uses an AsyncTask object, which provides feedback automatically on the UI thread.

public class LongRunningTaskWithAsyncTask extends Activity {

  class LongRunningAsyncTask extends AsyncTask<String, Integer, Integer> {
    @Override
       protected Integer doInBackground(String... dummy) {
      int i = 0;
      for(; i < cnt; i++)
      {
        try {
          Thread.sleep(1000);
        } catch (Exception e) {
          Log.v("Error: ", e.toString());
        }
        if(feedBackInBackground)
        {
          // This is not allowed and will throw an exception.
          textView.setText("Progress: " + i);
        }
        else
        {
          // By publishing your progress, Android calls the onProgressUdate method  
          //    with the value provided
          publishProgress(i);
        }
      }
      
      return i;
       }

    @Override
       protected void onProgressUpdate(Integer... progress) {
         textView.setText("Progress: " + progress[0]);
       }

    @Override
       protected void onPreExecute() {
         textView.setText("Started!");
       }

    @Override
       protected void onPostExecute(Integer result) {
         textView.setText("Finished with " + result + "!");
       }
  }
     
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.text_view);
    textView=(TextView)findViewById(R.id.text_view_withcheckbox);
    chkOnBackground = (CheckBox)findViewById(R.id.checkBoxConfig);
    chkOnBackground.setText("Update in doInBackground");
    editTextTaskDuration=(EditText)findViewById(R.id.editTextTaskDuration);
    editTextTaskDuration.setText("" + cnt);
    
       Button buttonDoIt = (Button)findViewById(R.id.buttonDoIt);       
       buttonDoIt.setOnClickListener(
         new Button.OnClickListener(){   
           @Override  public void onClick(View arg0) 
           {   
              feedBackInBackground = chkOnBackground.isChecked();
              String taskDurationAsString = editTextTaskDuration.getText().toString();
              cnt = Integer.parseInt(taskDurationAsString);
              new LongRunningAsyncTask().execute("");
           }       
       });           
  }
  
  private TextView textView;
  private CheckBox chkOnBackground;
  private EditText editTextTaskDuration;
  private int cnt = 5;
  private boolean feedBackInBackground = false;
}

Running the sample shows the following screen:

Blocking Action: HandlerBlockingHandlerActivity

This demonstrates the fact that a handler is associated with the thread on which it is created. It also demonstrates that simply the fact of creating a handler is not sufficient to make sure your long running operations aren’t blocking the UI !

In this case, the handler is created on the UI thread, thus the long running operation is executed on the UI thread again blocking any updates of the UI.

public class HandlerBlockingHandlerActivity extends Activity {
  
  // This handler will be associated with the UI thread, hence this long running 
  //    operation will prevent the ui thread of processing any other messages posted
  //    to its messagequeue
  Handler uiHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      for(int i = 0; i < cnt; i++)
      {
        try {
          Thread.sleep(1000);
        } catch (Exception e) {
          Log.v("Error: ", e.toString());
        }
      }
      
      textView.setText(textView.getText()+"Did you succeed?");            
    }
  };    
     
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.text_view);
    textView=(TextView)findViewById(R.id.textView);
    editTextTaskDuration=(EditText)findViewById(R.id.editTextTaskDuration);
    editTextTaskDuration.setText("" + cnt);
    
        Button buttonDoIt = (Button)findViewById(R.id.buttonDoIt);       
        buttonDoIt.setOnClickListener(
            new Button.OnClickListener(){   
              @Override  public void onClick(View arg0) 
              {   
                   String taskDurationAsString = editTextTaskDuration.getText().toString();
                cnt = Integer.parseInt(taskDurationAsString);
                
            // Send a custom message to the UI thread
            // This will start the long running operation on the UI thread
                uiHandler.sendMessage(uiHandler.obtainMessage()); 
              }       
          });
  }
  
  private TextView textView;
  private EditText editTextTaskDuration;
  private int cnt = 5;  
}

Running the sample shows the following screen:

Again you have the same two controls: a textbox in which we’d like to show the progress and a checkbox for you to check.

In this case, none of the above will succeed because the handler was created on the UI thread.

NonBlocking Action: HandlerNonBlockingHandlerActivity

This is the counterpart of the above:

Here, first a Thread is created and inside it a Looper to have a message queue. Then a Handler is created which is associated to the created thread.

public class HandlerNonBlockingHandlerActivity extends Activity {
  
  Handler threadHandler = null;
  
  // This handler is created on the UI thread en thus associated with that thread
  //    this means that any code executed by this handler is executed on the UI thread
  Handler uiHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      textView.setText(textView.getText()+"Did you succeed?");            
    }
  };
    
  private void CreateThread() {
  
    // We create a new thread
    Thread t = new Thread() {
      public void run() {
        
        Looper.prepare();
        
        // In this thread we create a handler which is associated with this thread
        //    thus, everything executed by this handler is executed on this separate thread
        //    and as a result, we are not blocking the UI thread
        threadHandler = new Handler() {
          @Override
          public void handleMessage(Message msg) {
            for(int i = 0; i < cnt; i++)
            {
              try {
                Thread.sleep(1000);
              } catch (Exception e) {
                Log.v("Error: ", e.toString());
              }
            }
            uiHandler.sendMessage(uiHandler.obtainMessage());         
          }
        };
        
        Looper.loop();

      }
    };
    t.start();
  }
     
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.text_view);
    textView=(TextView)findViewById(R.id.textView);
    editTextTaskDuration=(EditText)findViewById(R.id.editTextTaskDuration);
    editTextTaskDuration.setText("" + cnt);
    
        Button buttonDoIt = (Button)findViewById(R.id.buttonDoIt);       
        buttonDoIt.setOnClickListener(
            new Button.OnClickListener(){   
              @Override  public void onClick(View arg0) 
              {   
                
                   String taskDurationAsString = editTextTaskDuration.getText().toString();
                cnt = Integer.parseInt(taskDurationAsString);
                
                threadHandler.sendMessage(threadHandler.obtainMessage()); 
              }       
          });
        
        CreateThread();
  }
  
  private TextView textView;
  private EditText editTextTaskDuration;
  private int cnt = 5;
}

Deepdive into Handler: DeepDiveHandler

Now, let us dive a little deeper into what a Handler is actually capable of and how to send Messages.

public class DeepDiveHandler extends Activity {
  
  static final int MessageFromHandler = 0;
  static final int MessageAfterLooper = 1;

  static final int MessageShowText = 0;
  static final int MessageQuitLooper = 1;
  
  Handler threadHandler = null;
  Handler uiHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      switch (msg.what)
      {
      case MessageFromHandler:
        long uptimeSec = SystemClock.uptimeMillis() / 1000;
        long minutes = uptimeSec / 60;
        uptimeSec = uptimeSec % 60;
        
        textView.setText("Message=" + minutes + ":" + uptimeSec);            
        break;
      case MessageAfterLooper:
        textView.setText("After Looper.Loop()");            
      }
    }
  };
    
  private void CreateThread() {
    Thread t = new Thread() {
      public void run() {
        
        Looper.prepare();
        
        threadHandler = new Handler() {
          @Override
          public void handleMessage(Message msg) {
            switch (msg.what)
            {
            case MessageShowText:
              Message uiMsg = uiHandler.obtainMessage();
              uiMsg.what = MessageFromHandler;
              uiHandler.sendMessage(uiMsg);         
              break;
            case MessageQuitLooper:
              this.getLooper().quit();         
              break;
            }
          }
        };
        
        // you will not get past here until you call the looper's quit() method
        Looper.loop();
        
        // this code only gets executed when the looper is stopped by calling it's quit() method
        Message uiMsg = uiHandler.obtainMessage();
        uiMsg.what = MessageAfterLooper;
        uiHandler.sendMessage(uiMsg);         

      }
    };
    t.start();
  }
     
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.deepdivehandler_view);
    textView=(TextView)findViewById(R.id.textView);
    
        Button buttonStartIt = (Button)findViewById(R.id.buttonStartIt);       
        buttonStartIt.setOnClickListener(
            new Button.OnClickListener(){   
              @Override  public void onClick(View arg0) 
              {   
                CreateThread();
              }       
          });
        
        Button buttonDoIt = (Button)findViewById(R.id.buttonDoIt);       
        buttonDoIt.setOnClickListener(
            new Button.OnClickListener(){   
              @Override  public void onClick(View arg0) 
              {   
                Message showMsg = threadHandler.obtainMessage();
                showMsg.what = MessageShowText;
                threadHandler.sendMessage(showMsg); 
              }       
          });
        
        Button buttonStopIt = (Button)findViewById(R.id.buttonStopIt);       
        buttonStopIt.setOnClickListener(
            new Button.OnClickListener(){   
              @Override  public void onClick(View arg0) 
              {   
                Message quitLooperMsg = threadHandler.obtainMessage();
                quitLooperMsg.what = MessageQuitLooper;
                threadHandler.sendMessage(quitLooperMsg); 
              }       
          });

  }
  
  private TextView textView;
}

In the sample screen, you have 3 buttons:

  1. Start thread: This button creates a new thread with an associated Looper and Handler
  2. Take action: This button sends messages to the Handler created by the “Start thread” button
  3. Stop thread: This button also sends a message to the Handler created by the “Start thread” button, but with a different payload than the “Take action” button, resulting in the thread to stop running.

Ok, what is happening here?

The “Take action” button sends a message to the Handler with a what-parameter of MessageShowText. This results in the Handler sending a message to the UI handler which then updates the text in the TextView.

The “Stop thread” button sends a message to the Handler with a what-parameter of MessageQuitLooper. This results in a call of the Looper‘s quit()-method thus ending the looper and executing any code after the Looper‘s loop()-method call.

Deepdive into Async: DeepDiveAsync

Next, we’ll dive a little deeper in the functionality of AsyncTask.

public class DeepDiveAsync extends Activity {
  
  class DeepDiveAsyncTask extends AsyncTask<String, Integer, Integer> {
    @Override
       protected Integer doInBackground(String... dummy) {
      int i = 0;
      try {
        for(; i < m_duration; i++)
        {
          Thread.sleep(1000);
          // if you do not check for cancellation you will not be able to 
          //    cancel your task using cancel(false)
          if(m_checkForCancellation) {
            if(isCancelled()) {
              // Just checking for cancellation isn't enough.
              // If you do not somehow return here prematurely
              //    your task will not be canceled
              return i;
            }
          }
          
          // By publishing our progress, android will call the 
          //    onProgressUpdate method with the argument given here
          publishProgress(i);
        }
      } catch (Exception e) {
        Log.v("Error: ", e.toString());
      }
      
      // The value we return here will be forwarded to the
      //    onPostExecute method
      return i;
       }

    @Override
       protected void onProgressUpdate(Integer... progress) {
         textView.setText("Progress: " + progress[0]);
       }

    @Override
       protected void onPreExecute() {
         textView.setText("Started!");
       }

    @Override
       protected void onPostExecute(Integer result) {
         textView.setText("Finished with " + result + "!");
       }
    
    @Override
    protected void onCancelled() {
         textView.setText("Cancelled!");
    };
    
    public int getDuration()
    {
      return m_duration;
    }
    
    public void setDuration(int duration)
    {
      m_duration = duration;
    }
    
    public boolean getCheckForCancellation()
    {
      return m_checkForCancellation;
    }
    
    public void setCheckForCancellation(boolean checkForCancellation)
    {
      m_checkForCancellation = checkForCancellation;
    }
    
    private int m_duration = 5;
    private boolean m_checkForCancellation = false;
  }
     
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.deepdiveasync_view);
    textView=(TextView)findViewById(R.id.textView);
    editTextTaskDuration=(EditText)findViewById(R.id.editTextTaskDuration);
    checkBoxCheckForCancelation=(CheckBox)findViewById(R.id.checkBoxCheckForCancelation);
    
    deepDiveAsyncTask = new DeepDiveAsyncTask();
    
    editTextTaskDuration.setText(Integer.toString(deepDiveAsyncTask.getDuration()));  
    checkBoxCheckForCancelation.setChecked(deepDiveAsyncTask.getCheckForCancellation());
    
    
      Button buttonDoIt = (Button)findViewById(R.id.buttonDoIt);       
      buttonDoIt.setOnClickListener(
      new Button.OnClickListener(){   
        @Override  public void onClick(View arg0) 
        {   
          String taskDurationAsString = editTextTaskDuration.getText().toString();
          int taskDuration = Integer.parseInt(taskDurationAsString);
          deepDiveAsyncTask.setDuration(taskDuration);
          deepDiveAsyncTask.setCheckForCancellation(checkBoxCheckForCancelation.isChecked());
          deepDiveAsyncTask.execute("");
        }       
      });
      
      Button buttonCancelItWithInterrupt = (Button)findViewById(R.id.buttonCancelItWithInterrupt);       
      buttonCancelItWithInterrupt.setOnClickListener(
      new Button.OnClickListener(){   
        @Override  public void onClick(View arg0) 
        {   
          deepDiveAsyncTask.cancel(true);
        }       
      });
      
      Button buttonCancelItNoInterrupt = (Button)findViewById(R.id.buttonCancelItNoInterrupt);       
      buttonCancelItNoInterrupt.setOnClickListener(
      new Button.OnClickListener(){   
        @Override  public void onClick(View arg0) 
        {   
          deepDiveAsyncTask.cancel(false);
        }       
      });
        
  }
  
  private TextView textView;
  private EditText editTextTaskDuration;
  private CheckBox checkBoxCheckForCancelation;
  
  private DeepDiveAsyncTask deepDiveAsyncTask;
}

In the sample application, we have 3 buttons:

  1. Take action: This button creates the AsyncTask using the parameters provided in the Task duration EditText and the Check for cancellation CheckBox
  2. Cancel action with interrupt: This button calls the AsyncTask‘s cancel(boolean mayInterruptIfRunning) with a parameter with value true allowing Android to interrupt the thread running the task.
  3. Cancel action no interrupt: This button calls the AsyncTask‘s cancel(boolean mayInterruptIfRunning) with a parameter with value false not allowing Android to interrupt the thread running the task.

What is happening?

The “Cancel action with interrupt” button calls the AsyncTask‘s cancel-method with a parameter of value true. This way, we let Android interrupt the action immediately which we can see when pressing this button: the method onCancelled() of the AsyncTask is called immediately showing the message “Cancelled!”

The “Cancel action no interrupt” button calls the exact same method but with a parameter of value false. The result of this is that if you now call the method isCancelled() in your doInBackground method implementation, it will return true and YOU can end the method. Read that sentence again: yes YOU are responsible for calling the isCancelled() method and taking the necessary steps to stop the execution. In the provided test application, the checkbox “Check for cancellation” allows you to bypass this check. If you do that, you will notice that your task is NOT cancelled.


Filed under: Android, CodeProject Tagged: android, codeproject, threading

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Serge Desmedt
Software Developer (Senior)
Belgium Belgium
No Biography provided
Follow on   LinkedIn

Comments and Discussions

 
GeneralExcellent!! PinmemberFlavio Gomez3-Jun-14 19:38 
GeneralRe: Excellent!! PinmemberSerge Desmedt3-Jun-14 20:14 
QuestionLong running tasks on their own thread using AsyncTask PinmemberMember 1050815728-Apr-14 21:53 
AnswerRe: Long running tasks on their own thread using AsyncTask [modified] PinmemberSerge Desmedt29-Apr-14 9:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141220.1 | Last Updated 30 Apr 2014
Article Copyright 2014 by Serge Desmedt
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid