Sunday, May 15, 2011

Downloading Files in Android

In the Connexions app, users can select to download a PDF or EPUB version of a textbook or chapter of a book. The PDF or EPUB is downloaded to a Connexions folder on their SD Card or on the Phone's storage if there is no SD Card. The code to handle the download is in the downloadFile() method of the DownloadHandler class.

public void downloadFile(final Context context, final String url,final String fileName)
{
/**
* The directory to store the files in
*/
final String STORAGE_PATH = "Connexions/";
/**
* Download buffer size
*/
final int BUFFER_SIZE = 1024 * 23;
final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, new Intent(context,context.getClass()), 0);

// configure the notification
final Notification notification = new Notification(R.drawable.download_icon, "Downloading " + fileName, System.currentTimeMillis());
notification.flags = notification.flags |=Notification.FLAG_AUTO_CANCEL;
notification.contentIntent = pendingIntent;
notification.setLatestEventInfo(context, "Downloading file", "Downloading " + fileName, pendingIntent);

final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

notificationManager.notify(42, notification);

Thread downloadThread = new Thread()
{
public void run()
{

try
{
File cnxDir = new File(Environment.getExternalStorageDirectory(), STORAGE_PATH);
if(!cnxDir.exists())
{
cnxDir.mkdir();
}
File file = new File(cnxDir, fileName);
URL urlObj = new URL(url);
URLConnection con = urlObj.openConnection();
//Log.d("MenuHandler.download", "length = " + length);
BufferedInputStream bis = new BufferedInputStream(con.getInputStream(), BUFFER_SIZE);

FileOutputStream fos = new FileOutputStream(file);
byte[] bArray = new byte[BUFFER_SIZE];
int current = 0;
int read = 0;
while(current != -1)
{
fos.write(bArray,0,current);
current = bis.read(bArray, 0, BUFFER_SIZE);
read = read + current;
}
fos.close();
bis.close();
notificationManager.cancel(42);

}
catch(Exception ioe)
{
Log.d("DownloadHandler.download", "Error: " + ioe.toString(), ioe);
}
}
};
downloadThread.start();
}

The method sets up a NotificationManager so the user can know that the file is being downloaded. The Notification is a set of download icons that appear on the Notification bar until the download is complete. The download itself happens in a separate Thread. Any operation outside the UI should be done in a separate thread. The download can be a long running process, so DownloadHandler is called by a Service. See this post for details. The number 42 is just an identifier for the Thread. It can be any number. In the Thread code, I check to see if the Connexions directory exists and create it if it does not. The actual download is basic Java IO type code. After the download, I stop the notification and close all the resources.

I previously was including a progress bar on the notifications drawer, but it was causing an OutOfMemory error on most downloads. Once I removed it, the error went away.

As always, you can browse the source or download a zip file of the source. Connexions for Android is available in the Android Market or from the Connexions website.