Pony Express Tutorial Part 2a: Are You Being Served?

If you have read part 1 of this tutorial series then you will know that I have been developing an Android app called Pony Express, and that I am using it to demonstrate various aspects of the Android API, and their integration into a complete app.  Pony Express is a pod-catcher and as such needs to be able to download podcasts.  Part 1 dealt with parsing an RSS feed in order to determine the URLs from which the podcast episodes can be downloaded.  This part will introduce the Android Service class, and use it to download and save episodes of the Linux Outlaws podcast to the SD Card of your Android device.

Podcasts down the pipe.

Downloading podcasts is not an instantaneous process; it takes some time.  In addition, it does not require any user input once started.  These two things make the use of an Android service ideal for this task.  Services are designed to run in the background until they either complete, or are explicitly stopped.  However, they do not by default run in a separate process, or in a separate thread from the main application.  This means that if you use them for CPU-intensive or blocking tasks (such as downloading), they need to be run in a separate thread to avoid making the user interface unresponsive.  Fortunately, Android has a sub-class of Service, IntentService, that sets up a worker thread by itself; thereby saving us the hassle of doing it.  So, how do we use the IntentService class to download the podcasts?

Service!

IntentServices are very simple to use.  We simply create a new class extending IntentService, and override the abstract method onHandleIntent() with the code we want to run in the worker thread.

package org.sixgun.ponyexpress.service;

import android.app.IntentService;
/**
*Service that handles the downloading and saving of podcasts.
*It queues multiple calls to startService from the calling Activity
*and does them one at a time in a worker thread.  When it has finished all
* its work it stops itself.
*/

public class Downloader extends IntentService {
    public Downloader() {
        //We need to call the Constructor of the superclass with a name for our class.
        //This is required but only useful for debugging
        super("Downloader");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
    //Code to do the downloading goes here.
    }
}

The first thing our Downloader service is going to need is the URL from which to download the podcast.  In Part 1 of this series we created an RSS parser class that got the URLs for each episode from the RSS feed.  So, how do we get each of these to our Downloader?  To answer that we are going to have to look at how services are started.

‘First Serve.’

Services, including IntentServices, can be started in two ways.  The methods startService() and bindService() can both start a service, but how do we specify which Service to start?  Well, both of these methods require something called an Intent to be passed to them as a parameter.  An Intent can either be Explicit or Implicit: Explicit intents designate the target Service by name.  Implicit intents describe the Action required, or data type to handle and Android determines which is the best Service (or Activity) to start based on that.  Here, we will be using the simpler Explicit intent since we know that we want to start our Downloader service.

Intents can also hold data in a Bundle called ‘extras’ and it is in this ‘extras’ bundle that we will put the URL of the podcast episode to download.  So, in order to start our Downloader service from the main Pony Express app, we create a new intent designating the Downloader class (and the Context(this), don’t worry about Context here, we’ll cover it next time), add the episode URL to the Extras bundle, and then call startService(intent) with the intent we created.

Intent intent = new Intent(this,Downloader.class);
intent.putExtra("URL", "http://media.libsyn.com/media/linuxoutlaws/linuxoutlaws159.ogg");
startService(intent);

The Android system will then start an instance of Downloader for us and pass the Intent to onHandleIntent(Intent intent), which we can then access like this:


private static final String TAG = "PonyExpress Downloader";
private URL mUrl;
@Override
protected void onHandleIntent(Intent intent) {
		Bundle data = intent.getExtras();
		mUrl = getURL(data.getString("URL"));
                //Rest of the code to handle the download goes here.
}
/**
	 * Parse the url string to a URL type.
	 * @param _url string from the Intent.
	 * @return URL object.
*/
private URL getURL(String _url) {
	URL url;
	try {
		url = new URL(_url);
	} catch (MalformedURLException e) {
		Log.e(TAG, "Episode URL badly formed.", e);
		return null;
	}		return url;
}

Now that the Downloader has the URL we need to do one more thing before commencing the download.  We need to prepare the path where we will store the episode on the device’s SD Card.

‘The Path to Enlightenment…’

Files saved on the SD Card of an Android device can either be stored in an application specific path, or in a one of several ‘themed’ directories such as Music, Podcasts, Ringtones, Pictures etc..  Storage in an application specific directory means that the files can be made private to the application, and, in Android 2.2, they will be deleted when the application is deleted.  Files in the themed directories are available to many applications and are not deleted automatically.  For Pony Express I decided to use an application specific directory and so that is what I will demonstrate here.  The Android documentation specifies that this Pony Express directory should have the path:

/Android/data/<package_name>/files/

where <package_name> is org.sixgun.ponyexpress for Pony Express.  To create this path we first need to get a File instance that represents the root of the SD Card, like this.

File root = Environment.getExternalStorageDirectory();

Now, we create a new File instance representing the desired Podcast path and create it.

File path = new File(root, "/Android/data/org.sixgun.ponyexpress/files/");
path.mkdirs();

Next we need a filename with which to save the episode.  The URL of each episode has the filename at the end, so with some URL and String manipulation we can split the end off and use that as the filename like so.

final String filename_path = mUrl.getFile();
final String filename = filename_path.substring(filename_path.lastIndexOf('/'));

The call to getFile() with our example URL returns “linuxoutlaws/linuxoutlaws159.ogg”, so we need to split the string at the ‘/’ to give us just the filename.

To write to this file we have to create a FileOutputStream object.

try {
    FileOutputStream outFile = new FileOutputStream(new File(path,filename));
} catch (FileNotFoundException e) {
    Log.e(TAG, "Cannot open FileOutputStream for writing.",e);
}

We are now ready to open a connection to the server hosting the podcast and to download the episode.

Energize!

To download the file we open an InputStream with the URL and read the bytes from that stream into a buffer.  The buffer contents are then written to the FileOutputStream we created.

try {
    final InputStream inFile = mUrl.openStream();
} catch (IOException e) {
    throw new RuntimeException(e);
}
byte[] buffer = new byte[1024];
int size = 0;
try {
    while ((size = inFile.read(buffer)) > 0 ) {
        outFile.write(buffer,0, size);
    }
} catch (IOException e) {
    Log.e(TAG, "Error reading/writing to file.", e);
}

So there we have it.  The episode has now been downloaded and saved to the SD Card.  If the Downloader IntentService hasn’t been sent anymore intents to handle it will close its worker thread and finish.

That is it for Part 2a, we have touched on Services, and IntentServices in particular, introduced Intents, covered saving files to the SD Card, and downloading files from the Internet.  Of course, this Downloader is quite basic; in particular it doesn’t notify the user about what it is doing, and it doesn’t check that there is a working internet connection before trying to download the podcast.  These things and more I will cover in part 2b, where I will add a few bells and whistles to the Downloader and make it more resilient.

Advertisements

About Paul Elms

Stay at home Dad, ex-biologist, wannabe Android app developer.
This entry was posted in Android, Development, Pony Express. Bookmark the permalink.

4 Responses to Pony Express Tutorial Part 2a: Are You Being Served?

  1. lauren says:

    nice! i’ve also recently started android development and this is filling in some blanks very nicely … thank you
    🙂

  2. Paul Elms says:

    I’m glad it is of use to someone 🙂 Thanks for your nice comments.

  3. Terrence Precht says:

    Found your blog post via live search the other day and absolutely think its great. Keep up the excellent work.

  4. jiduvah says:

    Nice and easy to follow, thanks. It really helped me out

Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s