Search code examples
androidmultithreadingandroid-workmanager

WorkManager not working with Custom Configuration


I'm trying to run synchronous jobs using the WorkManager however it seems as if the configuration passed is partially ignored.

Below is my implementation:

App:

class App : Application(), HasAndroidInjector, Configuration.Provider {
@Inject
    lateinit var workerConfiguration: Configuration
override fun getWorkManagerConfiguration(): Configuration {
        Timber.tag(javaClass.name).d("RRR config ${workerConfiguration.executor.run { Thread.currentThread().name }}")
        return workerConfiguration
    }
}

The module injection: As you can see I set 1 SingleThreaded Executor with some specific ThreadFactory and custom WorkerFactory

@Singleton
@Provides
    fun provideWorkManagerConfiguration(
        globalWorkerFactory: GlobalWorkerFactory
    ): Configuration {
        val threadFactory = ThreadFactoryBuilder().setNameFormat("sync-worker-thread-%d").build()
        val executor = Executors.newSingleThreadExecutor(threadFactory)
        executor.submit { Log.d(javaClass.name,"RRR executor thread = ${Thread.currentThread().name}") }
        return Configuration.Builder()
            .setWorkerFactory(globalWorkerFactory)
            .setExecutor(executor)
            .build()
    }

GlobalWorkingFactory: Used to inject some params into workers

@Singleton
class GlobalWorkerFactory @Inject constructor(
    api: API
) : DelegatingWorkerFactory() {
    init {
        Log.d(javaClass.name, "RRR adding factory ${Thread.currentThread().name}")
        addFactory(WorkerFactory(api))
    }
}

WorkerFactory:

CustomWorkerFactory(
    private val api: API,
) : WorkerFactory() {

    override fun createWorker(
        appContext: Context,
        workerClassName: String,
        workerParameters: WorkerParameters
    ): CoroutineWorker? {
        return when (workerClassName) {
            SyncWorker::class.java.name -> {
                Log.d(javaClass.name, "RRR getting sync worker on ${Thread.currentThread().name}")
                SyncWorker(appContext, workerParameters, api)
            }
            else ->
                null
        }
    }
}

The Sync Worker doing some long running calls:


class SyncWorker constructor(
    appContext: Context,
    workerParams: WorkerParameters,
    private val api: API):
    CoroutineWorker(appContext, workerParams) {

override suspend fun doWork(): Result {

Timber.d("RRR START sync file ${uploadingFile.file.name} on thread ${Thread.currentThread().name}")

syncfile()

Timber.d("RRR returning result for ${uploadingFile.file.name}")

}

And finally the way I enquee the work:


fun addFileToWorker(context: Context, uploadingFile: UploadingFile) {
            val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.UNMETERED)
                .build()

            val syncWorkerRequest = OneTimeWorkRequestBuilder<SyncWorker>()
                .setInputData(
                    workDataOf(
                        "uploadingFile" to uploadingFileAdapter.toJson(
                            uploadingFile
                        )
                    )
                )
                .addTag(SYNC_WORK_TAG)
                .setConstraints(constraints)
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS)
                .build()

            val operation = WorkManager.getInstance(context)
                .enqueueUniqueWork(
                    Sunique_ID_for_each_job
                    ExistingWorkPolicy.KEEP,
                    syncWorkerRequest)
                .result
            operation.addListener({ Timber.d("RRR added file ${uploadingFile.file.name} ${Thread.currentThread().name}")}, { it.run() })
        }

Below are the log s how I get them and it shows that the files are being worked on on different thread than the one assigned:


core.worker.GlobalWorkerFactory: RRR adding factory main
di.module.ApplicationModule: RRR executor thread = sync-worker-thread-0
Activity: RRR calling process sessions
App: RRR config main
Utils$Companion: RRR added file 0.aes pool-14-thread-2
Utils$Companion: RRR added file 1635858822 pool-14-thread-3
CustomWorkerFactory: RRR getting sync worker on pool-14-thread-1
CustomWorkerFactory: RRR getting sync worker on pool-14-thread-2
SyncWorker: RRR START sync file 0.aes on thread DefaultDispatcher-worker-1
SyncWorker: RRR START sync file 1635858822 on thread DefaultDispatcher-worker-4
SyncWorker: RRR START sync file 0.aes on thread DefaultDispatcher-worker-3

Since I have explicitly defined 1 Thread and I'm successfully passing the configuration with my thread to the GlobalWorkerFactory, is it not supposed to respect the thread as seen on the first log ? ie: sync-worker-thread-0

I saw other posts suggesting to just APPEND the work to one worker, but I thought that's not how it's meant to be used. And even then, it's not the thread I specified that is used for the work with or without the APPEND.


Solution

  • From Android documentation about workers: Doc

    Note that CoroutineWorker.doWork() is a suspending function. Unlike Worker, this code does not run on the Executor specified in your Configuration. Instead, it defaults to Dispatchers.Default. You can customize this by providing your own CoroutineContext. In the above example, you would probably want to do this work on Dispatchers.IO, as follows:

    class CoroutineDownloadWorker(
        context: Context,
        params: WorkerParameters
    ) : CoroutineWorker(context, params) {
    
        override suspend fun doWork(): Result {
            withContext(Dispatchers.IO) {
                val data = downloadSynchronously("https://www.google.com")
                saveData(data)
                return Result.success()
            }
        }
    }
    

    In my case I created a newSingleThreadContext and passed it to my worker.