In the case where we add new fields to a Job's InputData, we want to
make sure that initialize() is called in the try block so that if it
fails, it simply fails the job (allowing the user to retry with the new
field) instead of crashing.
There are rare corner cases where a Job could be preempted by the
JobScheduler and then be rescheduled before the preempted job finished
running. This could create weird race conditions with bad consequences.
To fix this, we create a fun locking system that prevents two jobs with
the same UUID from running at the same time.
This gives us more control over when it happens, as well as lets us set
things like the debug level. Also let's us get rid of the synchronized
block we had in Application#onCreate().
Occasionally a job may be run when the app is in a network-restricted
mode, like a form of doze. When this happens, jobs can timeout due to
lack of network access, causing a cascade of job delays. This is
particularly bad in the case of message retrieval.
To prevent this, if a job that normally requires network detects that no
network is available when running, then we start a foreground
notification.
Previously, we retried based on a count. Now we've added the ability to
keep retrying for a specified time, using exponential backoff to
throttle attempts.
We have to make some changes, and it's gotten to the point where
maintaining it as a separate library is more hassle than it's worth,
especially with Google releasing WorkManager as the preferred job
scheduling library.