1 package leakcanary.internal 2 3 import android.app.Application 4 import android.content.Context 5 import android.content.ContextWrapper 6 import androidx.work.Configuration 7 import androidx.work.multiprocess.RemoteWorkerService 8 import leakcanary.AppWatcher 9 10 /** 11 * Using a custom class name instead of RemoteWorkerService so that 12 * hosting apps can use RemoteWorkerService without a naming conflict. 13 */ 14 class RemoteLeakCanaryWorkerService : RemoteWorkerService() { 15 16 /** 17 * RemoteLeakCanaryWorkerService is running in the :leakcanary process, and androidx startup only 18 * initializes WorkManager in the main process. In RemoteWorkerService.onCreate(), 19 * WorkManager has not been init, so it would crash. We can't blindly call init() as developers 20 * might be calling WorkManager.initialize() from Application.onCreate() for all processes, in 21 * which case a 2nd init would fail. So we want to init if nothing has init WorkManager before. 22 * But there's no isInit() API. However, WorkManager will automatically pull in the application 23 * context and if that context implements Configuration.Provider then it'll pull the 24 * configuration from it. So we cheat WorkManager by returning a fake app context that provides 25 * our own custom configuration. 26 */ 27 class FakeAppContextConfigurationProvider(base: Context) : ContextWrapper(base), 28 Configuration.Provider { 29 30 // No real app context for you, sorry! getApplicationContextnull31 override fun getApplicationContext() = this 32 33 override fun getWorkManagerConfiguration() = Configuration.Builder() 34 // If the default package name is not set, WorkManager will cancel all runnables 35 // when initialized as it can't tell that it's not running in the main process. 36 // This would lead to an extra round trip where the canceling reaches the main process 37 // which then cancels the remote job and reschedules it and then only the work gets done. 38 .setDefaultProcessName(packageName) 39 .build() 40 } 41 42 private val fakeAppContext by lazy { 43 // We set the base context to the real app context so that getting resources etc still works. 44 FakeAppContextConfigurationProvider(super.getApplicationContext()) 45 } 46 getApplicationContextnull47 override fun getApplicationContext(): Context { 48 return fakeAppContext 49 } 50 onCreatenull51 override fun onCreate() { 52 // Ideally we wouldn't need to install AppWatcher at all here, however 53 // the installation triggers InternalsLeakCanary to store the application instance 54 // which is then used by the event listeners that respond to analysis progress. 55 if (!AppWatcher.isInstalled) { 56 val application = super.getApplicationContext() as Application 57 AppWatcher.manualInstall( 58 application, 59 // Nothing to watch in the :leakcanary process. 60 watchersToInstall = emptyList() 61 ) 62 } 63 super.onCreate() 64 } 65 } 66