Android Download Manager

When your Android app needs to download big files (more then 10 MB) you are facing a decision. Should you write your own downloader, or use android built-in Download Manager? I suggest that you use Download Manager as many other apps use it (e.g. Google Translate, Speech,...), and has a lot of useful options:

  • download only over wifi
  • hide download history
  • transfer (move) the file to another folder (private or public sotrage)
  • show the download progress in your activity

In my case I needed the first three options as displaying the download progress in my app was not a requirement (it is also shown in notification area). Here is the code that downloads a file using the Download Manager:

public static void downloadFile(Context context){
    DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
    DownloadManager.Request request = new DownloadManager.Request(Uri.parse("URI for your file"));

    //file type
    request.setMimeType("application/pdf");

    //if you want to download only over wifi
    request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);

    //to set title of download
    request.setTitle("your title");

    //if your download is visible in history of downloads
    request.setVisibleInDownloadsUi(false);

    //you should store unique queue id
    long queueId = downloadManager.enqueue(request);

    //I store queue id in sqllite
    DownloadDataSource ds = new DownloadDataSource(context);
    ds.open();
    ds.insert(id, queueId);
    ds.close();
}

NOTE: I stored queue id from download manager in sqllite database. In many examples on internet they just store it in a variable in class. This is not a good practice, especially if you allow multiple concurrent downloads or if the user can navigate to other activities (you loose local state of variables).

After we set the Download Manager to downloading the file, we need to provide a broadcast receiver to receive status message when the download is completed (or failed). I registered my broadcast receiver in the android manifest file. In many examples you may see that people register it in the class that starts the download. Once again, as I can have more than one concurrent download, and the user can switch activities in my app, I decided to make my receiver available everywhere in my app. Here is the code in android manifest file:

<receiver
    android:name=".common.DownloadReceiver"
    android:enabled="true" >
    <intent-filter>
        <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
    </intent-filter>
</receiver>

And here is DownloadReceiver class:

public class DownloadReceiver extends BroadcastReceiver {
    public DownloadReceiver() {}

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
            long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
            DownloadManager.Query query = new DownloadManager.Query();
            query.setFilterById(downloadId);
            DownloadManager mDownloadManager =             
                (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);
            Cursor c = mDownloadManager.query(query);
            if (c.moveToFirst()) {
                int columnIndex = c
                    .getColumnIndex(DownloadManager.COLUMN_STATUS);

                //if completed successfully
                if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)){
                    String uri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_URI));
                    //check if this is your download uri
                    if (!uri.contains("com.example.app"))
                        return;

                    //here you get file path so you can move
                    //it to other location if you want
                    String downloadedPackageUriString = 
                      c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));

                    //notify your app that download was completed 
                    //with local broadcast receiver
                    Intent localReceiver = new Intent();
                    localReceiver.putExtra("ID", id);
                    LocalBroadcastManager
                        .getInstance(context)
                        .sendBroadcast(localReceiver);
                }else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)){
                    //if failed you can make a retry or whatever
                    //I just delete my id from sqllite
                }
            }
        }
    }
}

NOTE: You should check if the file you are being notified about is actually from your app. You can do this in two ways. One is to check if the uri of the downloaded file is correct. The other way is to check the enqueued id.

The last thing I did was to notify my app that a file download is completed. I have done that with the help of local broadcast receiver. All you have to do is to register the receiver in activity (or fragment) where you want to handle the message. I have done that in onCreate method:

LocalBroadcastManager.getInstance(getActivity().getApplicationContext())
    .registerReceiver(mDownloadCompletedReceiver,new IntentFilter(RECEIVER_INTENT_NAME));

To notify the receiver you call it this way:

Intent localReceiver = new Intent();
LocalBroadcastManager.getInstance(context).sendBroadcast(localReceiver);

That was the easiest way to handle downloading of big files in my app. Afterwars I can only say that it was a good decision to use it.