Get to Grips with DownloadManager

Downloading Files in Android

MarkMurphy
Downloading-Files-in-Android-Teaser-Pic

‘The Busy Coder’s Guide to Android Development’ author Mark Murphy looks at the challenges of downloading large files on mobile devices.

Downloading large files on a mobile device is rather
complicated. While simple HTTP operations are, well, simple, there
are many challenges involved in edge cases.

Let’s see what kind of challenges we have to face:

• Determining whether the user is on WiFi or mobile data, and if
so, whether the download should occur.

• Handling when the user, previously on WiFi, moves out of range
of the access point and “fails over” to mobile data.

• Ensuring the device stays awake while the download
proceeds.

Android 2.3 has added DownloadManager to handle these large
downloads. Using this class is less complicated than the
alternative of writing all of it yourself. However, it presents its
own set of challenges.

In this article, we will cover the use of DownloadManager, by
examining the Internet/Download sample project from the GitHub
repository for The Busy Coder’s Guide to Android Development
[1].

The Permissions

To use DownloadManager, you will need to hold the Internet
permission. Depending on where you elect to download the file, you
may also need the Write_External_Storage permission. However, at
the time of this writing, if you lack sufficient permissions, you
may get an error complaining that you are missing
Acces_All_Downloads. This appears to be a bug [2] in the
DownloadManager implementation – it should be complaining about
Internet and/or Write_External_Storage. You do not need to hold the
Acces_All_Downloads permission, which is not even documented as of
Android 2.3. For example, Listing 1 shows the manifest for the
Internet/Download application.

The Layout

Our sample application has a simple layout, consisting of three
buttons (Listing 2):

• One to kick off a download

• One to query the status of a download

• One to display a system-supplied activity containing the
roster of downloaded files

Requesting the Download

To kick off a download, we first need to get access to the
DownloadManager. This is a so-called “system service”. You can call
getSystemService() on any activity (or other Context), provide it
the identifier of the system service you want, and receive the
system service object back. However, since getSystemService()
supports a wide range of these objects, you need to cast it to the
proper type for the service you requested.

So, for example, here is a line from onCreate() of the
DownloadDemo activity where we get the DownloadManager:

mgr=(DownloadManager)getSystemService(DOWNLOAD_SERVICE);

Most of these managers have no close() or release() or
goAwayPlease() sort of methods – you can just use them and let
garbage collection take care of cleaning them up. Given the
manager, we can now call an enqueue() method to request a download.
The name is relevant – do not assume that your download will begin
immediately, though often times it will. The enqueue() method takes
a DownloadManager. Request object as a parameter. The Request
object uses the builder pattern, in that most methods return the
Request itself, so you can chain a series of calls together with
less typing. For example, the top-most button in our layout is tied
to a startDownload() method in DownloadDemo, shown in Listing
3.

We are downloading a sample MP4 file, and we want to download it
to the external storage area. To do the latter, we are using
getExternalStoragePublicDirectory() on Environment, which gives us
a directory suitable for storing a certain class of content. In
this case, we are going to store the download in the
Environment.DIRECTORY_DOWNLOADS, though we could just as easily
have chosen Environment. DIRECTORY_MOVIES, since we are downloading
a video clip. Note that the File object returned by
getExternalStorage-PublicDirectory() may point to a not-yet-created
directory, which is why we call mkdirs() on it, to ensure the
directory exists.

We then create the DownloadManager.Request object, with the
following attributes:

• We are downloading the specific URL we want, courtesy of the
Uri supplied to the Request constructor

• We are willing to use either mobile data or WiFi for the
download (setAllowedNetworkTypes()), but we do not want the
download to incur roaming charges (setAllowed-OverRoaming())

• We want the file downloaded as test.mp4 in the downloads area
on the external storage (setDestinationInExternalPublicDir())

We also provide a name (setTitle()) and description
(set-Description()), which are used as part of the notification
drawer entry for this download. The user will see these when they
slide down the drawer while the download is progressing.

The enqueue() method returns an ID of this download, which we
hold onto for use in querying the download status.

Keeping Track of Download Status

If the user presses the Query Status button, we want to find out
the details of how the download is progressing. To do that, we can
call query() on the DownloadManager. The query() method takes a
DownloadManager. Query object, describing what download(s) you are
interested in. In our case, we use the value we got from the
enqueue() method when the user requested the download (Listing
4).

The query() method returns a Cursor, containing a series of
columns representing the details about our download. There are a
series of constants on the DownloadManager class outlining what is
possible. In our case, we retrieve (and dump to LogCat):

• The ID of the download (COLUMN_ID)

• The amount of data that has been downloaded to date
(COLUMN_BYTES_DOWNLOADED_SO_FAR)

• What the last-modified timestamp is on the download
(COLUMN_LAST_MODIFIED_TIMESTAMP)

• Where the file is being saved to locally
(COLUMN_LOCAL_URI)

• What the actual status is (COLUMN_STATUS)

• What the reason is for that status (COLUMN_REASON)

There are a number of possible status codes (e.g.,
STATUS_FAILED, STATUS_SUCCESSFUL, STATUS_RUNNING). Some, like
STATUS_FAILED, may have an accompanying reason to provide more
details.

What the User Sees

The user, upon launching the application, sees our three pretty
buttons (Figure 1).

Clicking the first disables the button while the download is
going on, and a download icon appears in the status bar (though it
is a bit difficult to see, given the poor contrast between
Android’s icon and Android’s status bar), see Figure 2.

Sliding down the notification drawer shows the user the progress
in the form of a ProgressBar widget (Figure 3). Tapping on the
entry in the notification drawer returns control to our original
activity, where they see a Toast (Figure 4). If they tap the middle
button during the download, a Toast will appear indicating that the
download is in progress (Figure 5). Additional details are also
dumped to LogCat, visible via DDMS or adb logcat (Listing 5).

Once the download is complete, tapping the middle button will
indicate that the download is, indeed, complete, and final
information about the download is emitted to LogCat (Listing 6).
Tapping the bottom button brings up the activity displaying all
downloads, including both successes and failures (Figure 6). And,
of course, the fi le is downloaded. In Android 2.3, in the
emulator, our chosen location maps to /mnt/sdcard/
Downloads/test.mp4.

Limitations

DownloadManager works with HTTP URLs, but not HTTPS (SSL) URLs.
This is unfortunate, as more and more sites are switching to SSL
encryption across the board, to deal with various security
challenges. Hopefully, in the future, DownloadManager will have
more options here.

If you display the list of all downloads, and your download is
among them, it is a really good idea to make sure that some
activity (perhaps one of yours) is able to respond to an
ACTION_VIEW Intent on that download’s MIME type.

Otherwise, when the user taps on the entry in the list, they
will get a Toast indicating that there is nothing available to view
the download. This may confuse users. Alternatively, use
setVisibleInDownloadsUi() on your request, passing in false, to
suppress it from this list.

Author
MarkMurphy
Mark Murphy is the founder of CommonsWare and the author of the Busy Coder
Comments
comments powered by Disqus