RemoteLeakCanaryWorkerService.kt
TLDR
This file contains the implementation of the RemoteLeakCanaryWorkerService
class, which is a subclass of RemoteWorkerService
from the Android WorkManager library. It also includes a nested class FakeAppContextConfigurationProvider
that provides a custom configuration for the WorkManager
instance.
Classes
RemoteLeakCanaryWorkerService
This class extends RemoteWorkerService
and is used by the LeakCanary library to run background tasks related to memory leak detection in a separate process. It includes the following methods:
-
getApplicationContext()
: Overrides thegetApplicationContext()
method fromRemoteWorkerService
to return a fake app context that provides a custom configuration forWorkManager
. -
onCreate()
: Overrides theonCreate()
method fromRemoteWorkerService
to installAppWatcher
and manually trigger its installation in the application.
FakeAppContextConfigurationProvider
This nested class extends ContextWrapper
and implements Configuration.Provider
. It provides a fake app context and a custom configuration for WorkManager
. It includes the following methods:
-
getApplicationContext()
: Overrides thegetApplicationContext()
method fromContextWrapper
to return itself as the application context. -
getWorkManagerConfiguration()
: Overrides thegetWorkManagerConfiguration()
method fromConfiguration.Provider
to return aConfiguration
instance with a default process name.
package leakcanary.internal
import android.app.Application
import android.content.Context
import android.content.ContextWrapper
import androidx.work.Configuration
import androidx.work.multiprocess.RemoteWorkerService
import leakcanary.AppWatcher
/**
* Using a custom class name instead of RemoteWorkerService so that
* hosting apps can use RemoteWorkerService without a naming conflict.
*/
class RemoteLeakCanaryWorkerService : RemoteWorkerService() {
/**
* RemoteLeakCanaryWorkerService is running in the :leakcanary process, and androidx startup only
* initializes WorkManager in the main process. In RemoteWorkerService.onCreate(),
* WorkManager has not been init, so it would crash. We can't blindly call init() as developers
* might be calling WorkManager.initialize() from Application.onCreate() for all processes, in
* which case a 2nd init would fail. So we want to init if nothing has init WorkManager before.
* But there's no isInit() API. However, WorkManager will automatically pull in the application
* context and if that context implements Configuration.Provider then it'll pull the
* configuration from it. So we cheat WorkManager by returning a fake app context that provides
* our own custom configuration.
*/
class FakeAppContextConfigurationProvider(base: Context) : ContextWrapper(base),
Configuration.Provider {
// No real app context for you, sorry!
override fun getApplicationContext() = this
override fun getWorkManagerConfiguration() = Configuration.Builder()
// If the default package name is not set, WorkManager will cancel all runnables
// when initialized as it can't tell that it's not running in the main process.
// This would lead to an extra round trip where the canceling reaches the main process
// which then cancels the remote job and reschedules it and then only the work gets done.
.setDefaultProcessName(packageName)
.build()
}
private val fakeAppContext by lazy {
// We set the base context to the real app context so that getting resources etc still works.
FakeAppContextConfigurationProvider(super.getApplicationContext())
}
override fun getApplicationContext(): Context {
return fakeAppContext
}
override fun onCreate() {
// Ideally we wouldn't need to install AppWatcher at all here, however
// the installation triggers InternalsLeakCanary to store the application instance
// which is then used by the event listeners that respond to analysis progress.
if (!AppWatcher.isInstalled) {
val application = super.getApplicationContext() as Application
AppWatcher.manualInstall(
application,
// Nothing to watch in the :leakcanary process.
watchersToInstall = emptyList()
)
}
super.onCreate()
}
}