简体   繁体   中英

WorkManager doesn't work in library when client calls WorkManager.initialize()

I am writing an Android library that uses WorkManager. Somewhere in the code I call something like this:

val uploadTripRequest = OneTimeWorkRequest.Builder(UploadTripWorker::class.java)
    .setConstraints(someConstraints)
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)
    .build()

WorkManager.getInstance(context)
    .enqueueUniqueWork(WORKER_NAME, ExistingWorkPolicy.REPLACE, uploadTripRequest)

In my build.gradle I have: implementation 'androidx.work:work-runtime:2.2.0'

Normally, everything works perfectly. Problems come when the client app calls https://developer.android.com/reference/kotlin/androidx/work/WorkManager.html#initialize , for example:

WorkManager.initialize(context, Configuration.Builder().setWorkerFactory(myWorkerFactory).build())

When this is called, WorkManager from my library is not working as expected. doWork method is not called on my UploadTripWorker , but it is executed on the worker provided by client's myWorkerFactory .

From my perspective, the WorkManager is unusable in the library, since it is a singleton and someone can change its behaviour via initialize method. But I hope I am wrong.

Is there any way I can solve this problem? Of course, I can't say to all my clients not to use WorkManager.initialize() method.

WorkManager v2.1 introduced the DelegatingWorkerFactory class that can help in these cases.

It's still requires that the application developer implements it in the right way, as you said WorkManager is a singleton.

The key point is that, if an application requires a custom initialization and a custom WorkerFactory, it should use a DelegatingWorkerFactory and then add it's own WorkerFactory to it.

The key point here is that the application's WorkerFactory should check the worker class name to be sure that it's the correct one (usually injecting some parameters in the constructor). If the class name is not what it's expected by the application it can just return null and the DelegatingWorkerFactory will take care to find the correct worker class to instantiate.

Something like (in the application code):

class MyWorkerFactory(
    private val myInjectedParam: InjectedParam
) : WorkerFactory() {

    override fun createWorker(
        appContext: Context,
        workerClassName: String,
        workerParameters: WorkerParameters
    ): ListenableWorker? {

        return when (workerClassName) {
            MyWorker::class.java.name ->
                MyWorker(appContext, workerParameters, myInjectedParam)
            else ->
                // Return null, so that the base class can delegate to the default WorkerFactory.
                null
        }
    }
}

The app needs then to add this WorkerFactory to the DelegatingWorkerFactory that it has setup as custom WorkerFactory using:

private fun initializeWorkManager (myInjectedParam: MyInjectedParam): WorkManager
{ 
    val appContext = getApplication<MyApplication>()
    val factory = appContext.workManagerConfiguration.workerFactory
            as DelegatingWorkerFactory
    factory.addFactory(MyWorkerFactory(myInjectedParam))

    return WorkManager.getInstance(appContext)
}

While in the application class you have:

class MainApplication : Application(), Configuration.Provider {

    val delegatingWorkerFactory: DelegatingWorkerFactory

    // Setup custom configuration for WorkManager with a DelegatingWorkerFactory
    override fun getWorkManagerConfiguration(): Configuration {
        return Configuration.Builder()
            .setMinimumLoggingLevel(android.util.Log.INFO)
            .setWorkerFactory(delegatingWorkerFactory)
            .build()
    }
}

If you have your Worker and WorkerFactory in a library project, disabling the default initializer in library's manifest seems to do the trick. Because library will have a different package than the application, default initializer seems to get triggered, unless you disable it.

<provider android:name="androidx.work.impl.WorkManagerInitializer"
          android:authorities="<library's package>.analytics.workmanager-init"
          tools:node="remove" />

Setup that worked for me.

My App contains:

  • Configuration/Provider
  • DelegatingWorkerFactory() being set to the above Configuration

My Library contains:

  • Worker
  • WorkerFactory
  • WorkRequestBuilder
  • Enqueing the workrequest

But what intrigues me is this annotation in WorkerFactory source code:

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)

Not sure if this should've warned me that this method is being called from a different package.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM