Initializing and scheduling app size collection job
Ilya Vorobiev
Posted on March 12, 2023
This is the third and the last part in the series of articles describing how to calculate the size of an uncompressed app on disk and report it. This part covers how to stitch all the components implemented in the previous parts. Make sure you read the first and the second parts before proceeding to this one.
One of the requirements, I mentioned in the first part was “The solution shouldn’t impair the user experience especially when the app is in the foreground”. I will use jetpack WorkManager
to make sure this requirement is fulfilled and to initialise the system.
Before defining the constraint, let’s stitch all the components together. Our Worker will look like that:
class AppSizeWorker(private val appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
// In debug can return LogCatAppsizeTransport
// In prod will return the transport that sends report to the backend
val transport: AppSizeTransport = provideTransport()
val appSizeCollector = AppSizeCollector(appContext, transport)
// Should fetch the config from backend or cache
val appSizeCollectorConfig: AppSizeCollectorConfig = fetchAppSizeCollectorConfig()
// Will try to scan disk and report the result. The function is main-safe
val result = appSizeCollector.traverseAndCollect(appSizeCollectorConfig)
return if (result.isSuccess) {
Result.success()
} else if (runAttemptCount < 3) {
Result.retry()
} else {
Result.failure()
}
}
}
Note: since we are retrying if the result of the collection was not successful, we will need to provide a backoff policy when creating the worker.
This job should always be done in the background and not urgent to execute immediately. That’s why the natural limitations for the worker will be that the job is periodic and device should be idle. A daily snapshot of the disk should be representative enough to analyse, however, the frequency might be lower if on average your users use the app less often.
I would also add the requirement that the device should be charging. Given that phone anyway needs to be charged once a day on average, it is unlikely we will miss reports due to that constraint, but we will keep the battery life longer, by not introducing heavy background jobs.
Overall the worker request will look like this:
val appSizeCalculateRequest = PeriodicWorkRequestBuilder<AppSizeWorker>(Duration.ofDays(1))
.setConstraints(Constraints(
requiresCharging = true,
requiresDeviceIdle = true,
))
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, Duration.ofHours(1))
.build()
WorkManager.getInstance(this).enqueue(appSizeCalculateRequest)
In the end
Now, when we have a part that collects and calculates the size of our application, we can use the report it generates, to analyse, protect and reduce it.
There are some additional steps needed to use the system to full benefit, e.g. you need to save results to a file instead of log to analyse the result with the end-to-end tests, or you need to implement a network layer to report the analysis to the backend. I will leave these topics out of the scope of this series but feel free to reach out if you want to know more about how to make it work end to end.
Posted on March 12, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024