A Complete Guide to Android DownloadManager API

Written by

in

To build a custom download manager in Android, you generally have two choices: wrap Android’s native DownloadManager API inside a clean architectural layer or build a fully custom engine from scratch using WorkManager and Ktor/Retrofit.

Wrapping the native service is highly recommended for most use cases because the operating system automatically handles network changes, connection retries, and system notifications in the background. Method 1: Wrapping the Native DownloadManager

This approach uses a Clean Architecture abstraction to manage files securely and listen to system broadcasts. 1. Define the Downloader Interface

Create an interface to keep your business logic separated from the framework.

interface Downloader { fun downloadFile(url: String, fileName: String): Long } Use code with caution. 2. Implement the Android Downloader

Implement the interface using the system’s DownloadManager. This configures network constraints, file destinations, and notification behaviors.

import android.app.DownloadManager import android.content.Context import android.net.Uri import android.os.Environment class AndroidDownloader(context: Context) : Downloader { private val downloadManager = context.getSystemService(DownloadManager::class.java) override fun downloadFile(url: String, fileName: String): Long { val request = DownloadManager.Request(Uri.parse(url)) .setMimeType(“application/octet-stream”) .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE) // Allow both networks .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) // System notification .setTitle(fileName) .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName) // Save to public downloads return downloadManager.enqueue(request) // Starts background process and returns unique ID } } Use code with caution. 3. Create a Broadcast Receiver

The system sends a broadcast when a download completes. Create a custom receiver to handle this event.

import android.app.DownloadManager import android.content.BroadcastReceiver import android.content.Context import android.content.Intent class DownloadCompletedReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == “android.intent.action.DOWNLOAD_COMPLETE”) { val -1L) if (id != -1L) { println(“Download with ID $id finished successfully!”) // Trigger any app-specific local updates or DB changes here } } } } Use code with caution. 4. Register Manifest Permissions

Declare the internet permission and register your receiver inside the AndroidManifest.xml file.

Use code with caution. Method 2: Building a Fully Custom Downloader Engine

If you need advanced features—such as sending JSON request bodies via POST requests, custom HTTP authentication tokens, or downloading directly to strict app-private internal storage directories—the native DownloadManager will fail. Instead, build an engine using Jetpack WorkManager. 1. Define the Download Worker

WorkManager guarantees that tasks run even if the user exits your application.

import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import androidx.work.workDataOf import java.io.File import java.io.FileOutputStream import java.net.URL class CustomDownloadWorker( context: Context, workerParams: WorkerParameters ) : CoroutineWorker(context, workerParams) { override suspend fun doWork(): Result { val fileUrl = inputData.getString(“KEY_URL”) ?: return Result.failure() val fileName = inputData.getString(“KEY_FILE_NAME”) ?: return Result.failure() // Define your app’s secure internal private storage directory val targetFile = File(applicationContext.filesDir, fileName) return try { URL(fileUrl).openStream().use { inputStream -> FileOutputStream(targetFile).use { outputStream -> val buffer = ByteArray(4096) var bytesRead: Int // Track download progress manually while (inputStream.read(buffer).also { bytesRead = it } != -1) { outputStream.write(buffer, 0, bytesRead) } } } Result.success(workDataOf(“KEY_PATH” to targetFile.absolutePath)) } catch (e: Exception) { e.printStackTrace() Result.failure() } } } Use code with caution. 2. Enqueue the Custom Download Job

Trigger the background task with specific system constraints (e.g., waiting for an unmetered Wi-Fi connection).

import androidx.work.Constraints import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import androidx.work.workDataOf fun startCustomDownload(context: Context, url: String, name: String) { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.UNMETERED) // Require Wi-Fi .build() val downloadRequest = OneTimeWorkRequestBuilder() .setInputData(workDataOf(“KEY_URL” to url, “KEY_FILE_NAME” to name)) .setConstraints(constraints) .build() WorkManager.getInstance(context).enqueue(downloadRequest) } Use code with caution. Which approach should you choose? Native DownloadManager Wrapper Custom Engine (WorkManager) Simplicity Extremely High; system handles everything Moderate; requires manual thread management Private Files Impossible; cannot access app internal storage Fully supported; writes anywhere HTTP Methods Only standard GET requests Supports custom headers, tokens, and POST OS Management Managed natively by system service process Managed by background app background jobs

If you want to track real-time UI download progress or see how to integrate this architecture with Jetpack Compose UI, tell me what you would like to build next! Android HTTP POST download manager – Stack Overflow

Comments. … DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); Request request = new Request(Uri. parse( stackoverflow.com vrsrohit/Android-Custom-Download-Manager – GitHub

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *