Sunday 12 April 2015

Using Async Tasks in Android

Background

The whole android story behind this posts revolves around a single concept - the UI Thread. Lets understand what is this UI thread and what makes it so important. 

  • Each application has a main thread which is also called UI thread.
  • All the application components that are a part of same process will use this same UI thread. 
  •  All the android life cycle methods, the system callbacks, user interactions etc are handled by this UI thread.
Now you see why this main thread (UI Thread) is so important. You should avoid performing expensive time consuming operations on main thread as it will block your application altogether. This is one of the reasons why you may see ANR (App not responding) messages on you android device.

Note : Generally all components in an android application will run in same process unless you explicitly specify components to run in different processed in the manifest file.

So in this post we will see how to run your time consuming processes in Async task which basically runs your time consuming process in a new thread.

Why you should not do time consuming processing on UI Thread?

I am creating a simple that has a download button. To simulate a time consuming operation I am simply going to make the main thread sleep for 5 seconds.

My MainActivity code is as follows - 

package com.opensourceforgeeks.asynctaskdemo;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
/**
 * @author athakur
 */
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button downloadButton = (Button) findViewById(R.id.download_button);
        downloadButton.setOnClickListener(new OnClickListener() {
           
            @Override
            public void onClick(View v) {
                try {
                    //sleep for 5 seconds
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
 

After you install the app on your android device or emulator  click on download button. You should see the following screen - 



Note :  As usual I am skipping showing the resource code in the posts assuming you should be comfortable creating one now. If not you can always go back and see basic post -


So there you go. You got an ANR (App Not responding). This means your code is very bad and poor quality. Now lets see how we can make use of Async task to get rid of this ANR.

Using Aysnc Task for time consuming  processing

I am going to slightly change the code now. Will add a textView at the top which will show the download status - stop, progress and completed. 




Code is as follows - 


package com.opensourceforgeeks.asynctaskdemo;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
/**
 * @author athakur
 */
public class MainActivity extends Activity {

    Button downloadButton;
    TextView downloadStatus;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        downloadButton = (Button) findViewById(R.id.download_button);
        downloadStatus = (TextView) findViewById(R.id.downlaodStatus);
        downloadButton.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                new DownloadAsynctask().execute("TestInput");
            }
        });
    }
    
    class DownloadAsynctask extends AsyncTask<String, Integer, Boolean> {    
        @Override
        protected String doInBackground(String... params) {
            downloadStatus.setText(R.string.download_progress);
            try {
                //sleep for 5 seconds
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            downloadStatus.setText(R.string.download_completed);
            return null;
        }
        
    }
}

Ok Go ahead and install the app and click on the download button.



Opps! Your app would crash with following error.

04-12 16:05:51.241: E/AndroidRuntime(11317): FATAL EXCEPTION: AsyncTask #1
04-12 16:05:51.241: E/AndroidRuntime(11317): Process: com.opensourceforgeeks.asynctaskdemo, PID: 11317
04-12 16:05:51.241: E/AndroidRuntime(11317): java.lang.RuntimeException: An error occured while executing doInBackground()
....
04-12 16:05:51.241: E/AndroidRuntime(11317): Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
....

But what happened here?

Note : Only the thread that created and rendered a View can alter it and that thread is main UI thread. As I mentioned before Async Task starts a new thread and doInBackground method essentially runs in that thread. So you cannot update any of the UI elements from this thread. You can only do that from UI thread.

Next natural question would be how will that be possible? Is there a callback? Well, there are. There are other methods in AsyncTask that we can override to leverage it. For example methods like onPreExecute(), onProgressUpdate() and onPostExecute() run in UI thread and can be used to update the UI elements.

So let us make that changes. Few changes that I am going to make in the upcoming code - 
  • Instead of making thread sleep for 5 seconds in one go I am going to make thread sleep 5 times each for 1 sec to show how onProgressUpdate() methods works.
  • I will override onPreExecute(), onProgressUpdate(), and onPostExecute()  to show how they work. Typically I will update the downloadStatus TextView that we had in above code with corresponding download status.
Code is as follows -

package com.opensourceforgeeks.asynctaskdemo;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
/**
 * @author athakur
 */
public class MainActivity extends Activity {

    Button downloadButton;
    TextView downloadStatus;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        downloadButton = (Button) findViewById(R.id.download_button);
        downloadStatus = (TextView) findViewById(R.id.downlaodStatus);
        downloadButton.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                new DownloadAsynctask().execute("TestInput");
            }
        });
    }
    
    class DownloadAsynctask extends AsyncTask<String, Integer, Boolean> {    

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            downloadStatus.setText(R.string.download_progress);
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            downloadStatus.setText(getString(R.string.download_progress) + " : " + values[0] + "%");
        }

        @Override
        protected Boolean doInBackground(String... params) {
            
            //sleep for 5 seconds
            for (int i=0;i<5;i++) {
                try {
                    Thread.sleep(1000);
                    publishProgress((i+1)*20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return false;
                }
            }

            return true;
        }
        

        @Override
        protected void onPostExecute(Boolean result) {
            super.onPostExecute(result);
            if(result) {
                downloadStatus.setText(R.string.download_completed);
            }
            else {
                downloadStatus.setText(R.string.download_incompleted);
            }
            
        }
        
    }
}


And as expected this time your application should not crash and you should see following set of screens on click of download button -





Note : Skipping few screenshots here. You should see update progress at 20%, 40%, 60% and 80%.


Few important points

  • Notices the generics in Aysnctask definition? -  class DownloadAsynctask extends AsyncTask<String, Integer, Boolean>
  • Here first generic argument String is the type of argument that you will give in execute() method when starting async task. We have used new DownloadAsynctask().execute("TestInput"); This is the argument that doInBackground() methods receives - protected Boolean doInBackground(String... params)
  • Next generic argument in Integer. It is basically what you would receive in 
    onProgressUpdate() method. The argument in this method will be array of generic value specified as 2nd generic argument. You can see protected void onProgressUpdate(Integer... values) 
  • Last we have Boolean. This is basically the value that 
    doInBackground() methods returns to onPostExecute() methods. You can see - return true; in the doInBackground method and protected void onPostExecute(Boolean result).
  •  doInBackground() may call publishProgress() as we have used above which will make a call back to onProgressUpdate() to update the progress of background thread.

Related Links

t> UA-39527780-1 back to top