Into Open Source


AsyncTask makes Threading fun

Geplaatst in Artikel door Dirk op 11/09/2010
Tags: , , , , , ,

Threading can be a hard issue for developers and whenever you have to implement some threading yourself it’s always a hard job and the code you produce will never look great! So if we can avoid to implement Runnable and all the stuff we probably will. However I implemented some threading myself in an Android application and it didn’t look nice at all!

Threading in an Android application? Indeed, and here’s why. In Android if you want to show a loading dialog while you are loading some data from let’s say a web service (which can take some time to finish as you are not entirely sure what kind of internet connection is available on the device) you have to start using threading. The reason why you have to use threading for it is because the loading dialog needs to be updated all the time (with a percentage, some graphical circle that goes around etc) and at the exact same time you want to get your data from your service.So here is how I originally implemented it in the MyEpisodes Manager project:

public class EpisodesWatchListActivity extends Activity {
        private Runnable viewEpisodes;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.watchlist);
            //Do some other stuff on creation
            reloadEpisodes();
            //Do some other stuff on creation
        }

        private void reloadEpisodes() {
		showDialog(EPISODE_LOADING_DIALOG);
		viewEpisodes = new Runnable() {
			@Override
			public void run() {
				getEpisodes();
			}
		};

		Thread thread =  new Thread(null, viewEpisodes, "EpisodeRetrievalBackground");
		thread.start();
        }

	private void getEpisodes() {
		try {
			episodes = myEpisodesService.retrieveEpisodes(episodesType, user);
		} catch (InternetConnectivityException e) {
			String message = "Could not connect to host";
			Log.e(LOG_TAG, message, e);
			exceptionMessageResId = R.string.internetConnectionFailureReload;
		} catch(FeedUrlParsingException e) {
			String message = "Exception occured:";
			Log.e(LOG_TAG, message, e);
			exceptionMessageResId = R.string.watchListUnableToReadFeed;
		} catch (Exception e) {
			String message = "Exception occured:";
			Log.e(LOG_TAG, message, e);
			exceptionMessageResId = R.string.defaultExceptionMessage;
		}
		runOnUiThread(returnEpisodes);
	}

	private Runnable returnEpisodes = new Runnable() {
            //Update the view (input fields, lists,...)
            dismissDialog(EPISODE_LOADING_DIALOG);
        }
}

A little explanation of what will happen and why:

  • On creation of the activity the method to (re)load the data is called.
  • Next the dialog is started on the current thread (which is also known as the UI-thread).
  • Now we’ll create a Thread in which the actual data will be retrieved, to make things a bit more understandable we created a separate method for purpose.
  • After the data is loaded in the application we still have two things left to do, first of all the view must be updated with the new data loaded, next the loading dialog should be hidden. And here happens a little magic.  In your entire application there will be only one thread which you can use to update the view, this thread we call the UI-thread. Therefore we can use the runOnUiThread(Runnable runnable) method available in the Activity super class.
  • In this runnable-instance we should first update the view and when it’s all done we should dismiss the dialog (or in other words just close the dialog)

Now because this looks like one big mess Android came up with a simple solution, called AsyncTasks. I’ve re-written the previous example to fit in with the AsyncTasks and suddenly it looks a lot more clean. See the example here:

public class EpisodesWatchListActivity extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.watchlist);
            //Do some other stuff on creation
            reloadEpisodes();
            //Do some other stuff on creation
        }

        private void reloadEpisodes() {
            AsyncTask asyncTask = new AsyncTask() {
                @Override
                protected void onPreExecute() {
                    showDialog(EPISODE_LOADING_DIALOG);
                }

                @Override
                protected Object doInBackground(Object... objects) {
                    getEpisodes();
                    return 100L; //In my case I do not need a return value so I return some default value!
                }

                @Override
                protected void onPostExecute(Object o) {
                    updateView();
                    dismissDialog(EPISODE_LOADING_DIALOG);
                }
            };
            asyncTask.execute();
        }

	private void getEpisodes() {
		try {
			episodes = myEpisodesService.retrieveEpisodes(episodesType, user);
		} catch (InternetConnectivityException e) {
			String message = "Could not connect to host";
			Log.e(LOG_TAG, message, e);
			exceptionMessageResId = R.string.internetConnectionFailureReload;
		} catch(FeedUrlParsingException e) {
			String message = "Exception occured:";
			Log.e(LOG_TAG, message, e);
			exceptionMessageResId = R.string.watchListUnableToReadFeed;
		} catch (Exception e) {
			String message = "Exception occured:";
			Log.e(LOG_TAG, message, e);
			exceptionMessageResId = R.string.defaultExceptionMessage;
		}
	}

	private void updateView() {
            //Update the view (input fields, lists,...)
        }
}

I hope you notice that this code is a lot more easier to read than the previous. What it does:

  • First of all a new AsyncTask gets instantiated and implemented.
  • The method doInBackground(Object… objects) is the only that must be implemented. So the name already tells you that this is the method that will start a new thread and execute some stuff you define in that thread. In our case the data gets loaded from a web service.
  • Next we also have to implement the onPreExecute() method in order to be able to start the loading dialog.
  • And off course the onPostExecute(Object e) method which will hide the loading dialog when the data is loaded.
  • As you see the runOnUiThread(Runnable runnable) has been removed from the getEpisodes() method but everything done in this method has been moved the onPostExecute(Object e) method as this last mentioned method will not run in a seperate thread but on the UI-thread itself!

I also created a sample Android project called ‘LoadingDialogsAndThreading’ which you can find it on Google Code in the my-android-samples project.
It’s easy, not a lot of work to change your code and it makes it lot more readable for other developers. So definitely something to look into!

7 Reacties naar 'AsyncTask makes Threading fun'

Abonneer je op reacties met RSS of TrackBack naar 'AsyncTask makes Threading fun'.


  1. [...] AsyncTask makes Threading fun [...]

  2. ddewaele zei,

    What happens when you change the screen from portrait to landscape (or vice versa) after executing this code ?

    Here; during the first run, the progress dialog is properly shown and is removed after fetching the data. However, when I change the screen (from portrait to landscape), onCreate is called again, fetching the data, but now the progress dialog remains on the screen.

  3. Dirk zei,

    If you are having troubles with the orientation changing you should have a look at my other post: http://dendirken.wordpress.com/2010/09/12/android-changing-orientation-issues/

    There are two solutions for the orientation-issue. One prevents that the onCreate method is called, the other handles the re-generation of the onCreate… If you have any more questions don’t hesitate to ask!

  4. ddewaele zei,

    Hi,

    Another option is to use the onRetainNonConfigurationInstance() method of your activity to return whatever state you want to restore in onCreate (using the getLastNonConfigurationInstance() method.

    Spent some time today figuring out the different options regarding dialogs and screen orientations and came up with the following :

    http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/

    I guess it requires some plumbing code to get consistent and crash-free behavior with asyncTasks during screen orientation changes.

    Also noticed in that post that your loadDataAndShowLoadingDialog is called on the UI thread, Will that ever show the loadingdialog if a long running operation is blocking that thread ?

  5. Dirk zei,

    Indeed the method is called from the UI thread, but in loadDataAndShowLoadingDialog method you should implement the async-tasks. That would mean that in the loadDataAndShowLoadingDialog method you would start a loading dialog on the UI-thread and then start a second thread which will load you data…

    Your code would look like this:

    public void onCreate(Bundle savedInstanceState) {
    // … Do some of your own stuff
    loadDataAndShowLoadingDialog();
    // … Continue your own stuff
    }

    public void loadDataAndShowLoadingDialog() {
    loadData(); //Using a separate method to load the data will allow you to re-use the data loading method from the menu for example
    }

    private void loadData() {
    AsyncTask asyncTask = new AsyncTask() {
    @Override
    protected void onPreExecute() {
    showDialog(LOADING_DIALOG); //This loading dialog will be run on the UI-thread
    }
    @Override
    protected Object doInBackground(Object… objects) {
    //Here goes you data loading, something that takes long and could block the application if not run in a thread!
    return 100L;
    }

    @Override
    protected void onPostExecute(Object o) {
    dismissDialog(LOADING_DIALOG); //If the data loading thread is finished the dialog will be dismissed on the UI-thread
    }
    };
    asyncTask.execute();
    }

    I hope this is clear enough, if not don’t hesitate to ask more… :)

  6. Maurice zei,

    Hi I have looked at your code, and I need some help. A progress d is not showing up and it throws a error saying it cannot close the dialog that i opened because it was never opened, other than that the code works good, i just cant show a dialog, the method is being ran from UI. Please help!!! code below.
    {
    AsyncTask asyncTask = new AsyncTask() {
    @Override
    protected void onPreExecute() {
    showDialog(1);
    }

    @Override
    protected Void doInBackground(Object… objects) {

    if(islogin(mainusername.getText().toString().toLowerCase(),mainpassword.getText().toString())){
    GetOthersLocations();
    Intent myIntent = new Intent(MainClass.this, TabBrowse.class);
    startActivityForResult(myIntent, 0);
    }
    else
    {
    Toast.makeText(getApplicationContext(), “Incorrect Username or Password.”, 4).show();
    mainpassword.setText(“”);
    }
    return null;
    }

    @Override
    protected void onPostExecute(Object o) {
    dismissDialog(1);

    }
    };
    asyncTask.execute();

    }

  7. Dirk zei,

    Maurice,

    If I have a look at your code everything seems fine. You start an async task which will create a dialog with id 1, then do some stuff and after all hide the dialog with id 1.
    So that seems just fine for me but did you override the onCreateDialog(id) method? Cause if you don’t Android does not know what exactly to create (a loading dialog, a warning dialog,…) and what content to show in it.

    I quickly created an example project, you can find it here: http://code.google.com/p/my-android-samples/source/browse/#svn/trunk/LoadingDialogsAndThreading%3Fstate%3Dclosed
    What the project does: when starting the app the reloadData is called, that method creates an async task (starts a loading window, sleeps for 10000 miliseconds as if some heavy data loading is going on, and then when finished removes the loading dialog).

    So also the onCreateDialog(int id) method can be found in here!


Geef een reactie

Fill in your details below or click an icon to log in:

WordPress.com logo

Je reageert onder je WordPress.com account. Log Out / Bijwerken )

Twitter-afbeelding

Je reageert onder je Twitter account. Log Out / Bijwerken )

Facebook foto

Je reageert onder je Facebook account. Log Out / Bijwerken )

Verbinden met %s


Follow

Get every new post delivered to your Inbox.