WorkManager in Android

WorkManager in Android

What is WorkManager?

WorkManager is an API Android Jetpack provides for scheduling deferrable, asynchronous tasks that are expected to run reliably. It is especially suited for tasks that must be executed even if the app is killed or the device is rebooted.

Key features:

  • Ensures guaranteed execution of tasks.
  • Supports constraints, such as network availability, device charging, etc.
  • Allows chained or parallel work execution.
  • Suitable for both one-time and periodic tasks.


When Should You Use WorkManager?

Use WorkManager for tasks like:

  1. Uploading files: E.g., compressing and uploading user-generated media.
  2. Background data sync: Syncing app data with a server periodically.
  3. Database cleanup: Performing cleanup tasks at regular intervals.
  4. Analytics: Sending user interaction logs to a server.
  5. Deferred tasks: Executing tasks that are not time-critical.


Real-World Use Case: Compressing and Uploading Media

Let's walk through a practical example where WorkManager is used to:

  1. Compress multiple media files (images/videos).
  2. Upload them to a server.
  3. Notify the user upon completion.


1. Setting Up WorkManager

Add Dependencies

implementation "androidx.work:work-runtime-ktx:2.8.1"        

Basic Workflow

WorkManager tasks are defined in a Worker class:

  1. Extend the Worker or CoroutineWorker class.
  2. Define task logic in the doWork() method.
  3. Schedule the task using WorkRequest.


2. Full Implementation

Step 1: Compressing Media Files

We'll compress images using input/output streams (lightweight and dependency-free) and use FFmpeg (or placeholders) for video compression.

Step 2: Uploading Media Files

We'll upload files using Retrofit.


2.1 Worker Class

class PostUploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {

    override fun doWork(): Result {
        val mediaUris = inputData.getStringArray("media_uris") ?: return Result.failure()

        return try {
            // Step 1: Compress files
            val compressedFiles = compressMediaFiles(mediaUris, applicationContext)

            // Step 2: Upload files to the server
            uploadFiles(compressedFiles)

            Result.success() // Task successful
        } catch (e: Exception) {
            e.printStackTrace()
            Result.retry() // Retry on failure
        }
    }

    private fun compressMediaFiles(mediaUris: Array<String>, context: Context): List<File> {
        val compressedFiles = mutableListOf<File>()

        for (uri in mediaUris) {
            val inputFile = File(uri) // Convert URI to File
            val outputFile = File(context.cacheDir, "compressed_${inputFile.name}")

            if (inputFile.extension == "jpg" || inputFile.extension == "png") {
                compressImage(inputFile, outputFile, quality = 70)
            } else if (inputFile.extension == "mp4") {
                compressVideo(inputFile, outputFile)
            }

            compressedFiles.add(outputFile)
        }

        return compressedFiles
    }

    private fun compressImage(inputFile: File, outputFile: File, quality: Int = 80) {
        val bitmap = BitmapFactory.decodeFile(inputFile.absolutePath)
        FileOutputStream(outputFile).use { outputStream ->
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
        }
    }

    private fun compressVideo(inputFile: File, outputFile: File) {
        // Placeholder for video compression logic
        FileInputStream(inputFile).use { inputStream ->
            FileOutputStream(outputFile).use { outputStream ->
                inputStream.copyTo(outputStream)
            }
        }
    }

    private fun uploadFiles(files: List<File>) {
        val retrofit = createRetrofitService()
        val uploadService = retrofit.create(UploadService::class.java)

        for (file in files) {
            val requestBody = file.asRequestBody("multipart/form-data".toMediaType())
            val filePart = MultipartBody.Part.createFormData("file", file.name, requestBody)

            val response = uploadService.uploadMedia(filePart).execute()
            if (!response.isSuccessful) throw Exception("Upload failed for ${file.name}")
        }
    }

    private fun createRetrofitService(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://your-api-endpoint.com/") // Replace with your server URL
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
}

interface UploadService {
    @Multipart
    @POST("upload")
    fun uploadMedia(@Part file: MultipartBody.Part): Call<ResponseBody>
}        

2.2 Scheduling the Work

You can schedule the WorkManager task using OneTimeWorkRequest:

fun schedulePostUpload(context: Context, mediaUris: List<String>) {
    val inputData = Data.Builder()
        .putStringArray("media_uris", mediaUris.toTypedArray())
        .build()

    val uploadWorkRequest = OneTimeWorkRequestBuilder<PostUploadWorker>()
        .setInputData(inputData)
        .build()

    WorkManager.getInstance(context).enqueue(uploadWorkRequest)
}
        

2.3 Triggering the Task

When the user submits a post:

val mediaUris = listOf(
    "/storage/emulated/0/Download/photo1.jpg",
    "/storage/emulated/0/Download/video1.mp4"
)

schedulePostUpload(applicationContext, mediaUris)        

  • Work Constraints: Use constraints to optimize task execution, e.g., run only on Wi-Fi or when charging.
  • Chaining Work: WorkManager supports task chaining for sequential operations.
  • Resilience: WorkManager handles app restarts and device reboots seamlessly.


When to Use Foreground Service:

  1. Immediate Execution.
  2. User Awareness Required.
  3. Interruption Not Acceptable.
  4. High Priority Tasks.


Advantages:

  • Immediate execution.
  • Persistent notification ensures visibility to the user.
  • Can handle real-time or long-running tasks.


Disadvantages:

  • Higher battery consumption.
  • Requires the user to explicitly allow notifications.
  • Misuse may lead to app uninstalls or user frustration.


When to Use WorkManager:

  1. Guaranteed Execution.
  2. Deferrable Tasks.
  3. Constraints.
  4. Chained or Recurrent Tasks.


Advantages:

  • Guaranteed execution with system handling.
  • Supports constraints and retries.
  • Integrates with app lifecycle and system optimizations.


Disadvantages:

  • Cannot run tasks immediately in certain situations.
  • Slight overhead for scheduling with system APIs.


Conclusion

WorkManager is a powerful tool that helps Android developers handle background tasks reliably. In this article, we explored:

  1. The basics of WorkManager.
  2. A real-world use case of compressing and uploading media.
  3. Integration with Retrofit for server communication.
  4. Key tips from Philip Lackner's video.

With this knowledge, you can confidently implement reliable background tasks in your Android applications. Let me know if you'd like further clarifications or enhancements.


要查看或添加评论,请登录

Abdelrhman Ghanem的更多文章

  • Content Providers in Android

    Content Providers in Android

    What Is a Content Provider? A Content Provider acts as an interface for applications to access and modify data from…

  • Understanding URIs in Android

    Understanding URIs in Android

    A Uniform Resource Identifier (URI) is a string of characters uniquely identifying a resource. In Android, URIs are…

  • Foreground Services in Android

    Foreground Services in Android

    A Foreground Service is an Android service that performs a task while actively notifying the user, generally through a…

  • Broadcasts and Broadcast Receivers in Android

    Broadcasts and Broadcast Receivers in Android

    Broadcasts in Android allow applications and the Android system to send messages across apps and system components…

  • Intents and Intent Filters in Android

    Intents and Intent Filters in Android

    What is an Intent? An in Android is a messaging object used to request actions from other components like activities…

  • Android Resources and Qualifiers

    Android Resources and Qualifiers

    When building an Android app, you want it to look and work well across all devices. To do that, Android gives us…

  • Context in Android

    Context in Android

    What is Context? In Android, represents the current state of the application. It provides access to various resources…

  • Understanding Configuration Changes and ViewModel in Android

    Understanding Configuration Changes and ViewModel in Android

    1. What Are Configuration Changes? Configuration changes occur when the device environment changes in a way that…

  • Android Back Stack, Tasks, and Launch Modes

    Android Back Stack, Tasks, and Launch Modes

    In Android, managing screen navigation and app flows effectively depends on understanding the backstack, tasks, and…

  • Android Activity & Fragment Lifecycle

    Android Activity & Fragment Lifecycle

    In Android development, Activities and Fragments are essential components that help create engaging user interfaces…

社区洞察

其他会员也浏览了