diff options
author | Sumir Kataria <sumir@google.com> | 2018-06-07 16:40:01 -0700 |
---|---|---|
committer | Sumir Kataria <sumir@google.com> | 2018-06-07 16:46:40 -0700 |
commit | 4e34c1f8146c680986965fa1695cbd530b684121 (patch) | |
tree | 7a352fa9552ef2024bed88e401e72aee13f1bac2 | |
parent | 97c46766591189e7ce62706ce9614ebce99a222d (diff) | |
download | support-4e34c1f8146c680986965fa1695cbd530b684121.tar.gz |
Add pruneWork(Sync) methods.
This method will prune work matching the following criteria:
- Finished (succeeded, failed, cancelled)
- Zero unfinished dependents
This, together with cancelAll, gives the ability for people
to clear jobs and solves the "observation of a growing list
of jobs" problem.
Change-Id: Ied5571f3d143659f5d52cc4e1983e96329e91390
Fixes: 79950952
Test: Added and ran tests.
6 files changed, 136 insertions, 0 deletions
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java index 71686008903..a96c677ece4 100644 --- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java +++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java @@ -1333,6 +1333,38 @@ public class WorkManagerImplTest { @Test @SmallTest + public void pruneFinishedWork() { + OneTimeWorkRequest enqueuedWork = new OneTimeWorkRequest.Builder(TestWorker.class).build(); + OneTimeWorkRequest finishedWork = + new OneTimeWorkRequest.Builder(TestWorker.class).setInitialState(SUCCEEDED).build(); + OneTimeWorkRequest finishedWorkWithUnfinishedDependent = + new OneTimeWorkRequest.Builder(TestWorker.class).setInitialState(SUCCEEDED).build(); + OneTimeWorkRequest finishedWorkWithLongKeepForAtLeast = + new OneTimeWorkRequest.Builder(TestWorker.class) + .setInitialState(SUCCEEDED) + .keepResultsForAtLeast(999, TimeUnit.DAYS) + .build(); + + insertWorkSpecAndTags(enqueuedWork); + insertWorkSpecAndTags(finishedWork); + insertWorkSpecAndTags(finishedWorkWithUnfinishedDependent); + insertWorkSpecAndTags(finishedWorkWithLongKeepForAtLeast); + + insertDependency(enqueuedWork, finishedWorkWithUnfinishedDependent); + + mWorkManagerImpl.synchronous().pruneWorkSync(); + + WorkSpecDao workSpecDao = mDatabase.workSpecDao(); + assertThat(workSpecDao.getWorkSpec(enqueuedWork.getStringId()), is(notNullValue())); + assertThat(workSpecDao.getWorkSpec(finishedWork.getStringId()), is(nullValue())); + assertThat(workSpecDao.getWorkSpec(finishedWorkWithUnfinishedDependent.getStringId()), + is(notNullValue())); + assertThat(workSpecDao.getWorkSpec(finishedWorkWithLongKeepForAtLeast.getStringId()), + is(nullValue())); + } + + @Test + @SmallTest public void testSynchronousCancelAndGetStatus() { OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); insertWorkSpecAndTags(work); diff --git a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java index f01e363152a..9f633d8e3e0 100644 --- a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java +++ b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java @@ -21,6 +21,7 @@ import android.support.annotation.WorkerThread; import java.util.List; import java.util.UUID; +import java.util.concurrent.TimeUnit; /** * Blocking methods for {@link WorkManager} operations. These methods are expected to be called @@ -129,6 +130,20 @@ public interface SynchronousWorkManager { long getLastCancelAllTimeMillisSync(); /** + * Prunes all eligible finished work from the internal database in a synchronous fashion. + * Eligible work must be finished ({@link State#SUCCEEDED}, {@link State#FAILED}, or + * {@link State#CANCELLED}), with zero unfinished dependents. + * <p> + * <b>Use this method with caution</b>; by invoking it, you (and any modules and libraries in + * your codebase) will no longer be able to observe the {@link WorkStatus} of the pruned work. + * You do not normally need to call this method - WorkManager takes care to auto-prune its work + * after a sane period of time. This method also ignores the + * {@link OneTimeWorkRequest.Builder#keepResultsForAtLeast(long, TimeUnit)} policy. + */ + @WorkerThread + void pruneWorkSync(); + + /** * Gets the {@link WorkStatus} of a given work id in a synchronous fashion. This method is * expected to be called from a background thread. * diff --git a/work/workmanager/src/main/java/androidx/work/WorkManager.java b/work/workmanager/src/main/java/androidx/work/WorkManager.java index 83891dccef4..5960e50fcab 100644 --- a/work/workmanager/src/main/java/androidx/work/WorkManager.java +++ b/work/workmanager/src/main/java/androidx/work/WorkManager.java @@ -26,6 +26,7 @@ import androidx.work.impl.WorkManagerImpl; import java.util.Arrays; import java.util.List; import java.util.UUID; +import java.util.concurrent.TimeUnit; /** * WorkManager is a library used to enqueue work that is guaranteed to execute after its constraints @@ -288,6 +289,19 @@ public abstract class WorkManager { public abstract void cancelAllWork(); /** + * Prunes all eligible finished work from the internal database. Eligible work must be finished + * ({@link State#SUCCEEDED}, {@link State#FAILED}, or {@link State#CANCELLED}), with zero + * unfinished dependents. + * <p> + * <b>Use this method with caution</b>; by invoking it, you (and any modules and libraries in + * your codebase) will no longer be able to observe the {@link WorkStatus} of the pruned work. + * You do not normally need to call this method - WorkManager takes care to auto-prune its work + * after a sane period of time. This method also ignores the + * {@link OneTimeWorkRequest.Builder#keepResultsForAtLeast(long, TimeUnit)} policy. + */ + public abstract void pruneWork(); + + /** * Gets a {@link LiveData} of the last time all work was cancelled. This method is intended for * use by library and module developers who have dependent data in their own repository that * must be updated or deleted in case someone cancels their work without their prior knowledge. diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java index 5b659d1c67b..13ba38f3528 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java +++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java @@ -43,6 +43,7 @@ import androidx.work.impl.utils.CancelWorkRunnable; import androidx.work.impl.utils.ForceStopRunnable; import androidx.work.impl.utils.LiveDataUtils; import androidx.work.impl.utils.Preferences; +import androidx.work.impl.utils.PruneWorkRunnable; import androidx.work.impl.utils.StartWorkRunnable; import androidx.work.impl.utils.StopWorkRunnable; import androidx.work.impl.utils.taskexecutor.TaskExecutor; @@ -340,6 +341,7 @@ public class WorkManagerImpl extends WorkManager implements SynchronousWorkManag } @Override + @WorkerThread public void cancelUniqueWorkSync(@NonNull String uniqueWorkName) { assertBackgroundThread("Cannot cancelAllWorkByNameBlocking on main thread!"); CancelWorkRunnable.forName(uniqueWorkName, this).run(); @@ -351,6 +353,7 @@ public class WorkManagerImpl extends WorkManager implements SynchronousWorkManag } @Override + @WorkerThread public void cancelAllWorkSync() { assertBackgroundThread("Cannot cancelAllWorkSync on main thread!"); CancelWorkRunnable.forAll(this).run(); @@ -367,6 +370,18 @@ public class WorkManagerImpl extends WorkManager implements SynchronousWorkManag } @Override + public void pruneWork() { + mTaskExecutor.executeOnBackgroundThread(new PruneWorkRunnable(this)); + } + + @Override + @WorkerThread + public void pruneWorkSync() { + assertBackgroundThread("Cannot pruneWork on main thread!"); + new PruneWorkRunnable(this).run(); + } + + @Override public LiveData<WorkStatus> getStatusById(@NonNull UUID id) { WorkSpecDao dao = mWorkDatabase.workSpecDao(); LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData = diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java index 7d9775d380f..f08658fb088 100644 --- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java +++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java @@ -291,4 +291,17 @@ public interface WorkSpecDao { + ")" ) List<WorkSpec> getEligibleWorkForScheduling(); + + /** + * Immediately prunes eligible work from the database meeting the following criteria: + * - Is finished (succeeded, failed, or cancelled) + * - Has zero unfinished dependents + */ + @Query("DELETE FROM workspec WHERE " + + "state IN " + COMPLETED_STATES + + " AND (SELECT COUNT(*)=0 FROM dependency WHERE " + + " prerequisite_id=id AND " + + " work_spec_id NOT IN " + + " (SELECT id FROM workspec WHERE state IN " + COMPLETED_STATES + "))") + void pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast(); } diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java new file mode 100644 index 00000000000..bf93f4602a6 --- /dev/null +++ b/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java @@ -0,0 +1,47 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.work.impl.utils; + +import android.support.annotation.RestrictTo; + +import androidx.work.impl.WorkDatabase; +import androidx.work.impl.WorkManagerImpl; +import androidx.work.impl.model.WorkSpecDao; + +/** + * A Runnable that prunes work in the background. Pruned work meets the following criteria: + * - Is finished (succeeded, failed, or cancelled) + * - Has zero unfinished dependents + * + * @hide + */ +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +public class PruneWorkRunnable implements Runnable { + + private WorkManagerImpl mWorkManagerImpl; + + public PruneWorkRunnable(WorkManagerImpl workManagerImpl) { + mWorkManagerImpl = workManagerImpl; + } + + @Override + public void run() { + WorkDatabase workDatabase = mWorkManagerImpl.getWorkDatabase(); + WorkSpecDao workSpecDao = workDatabase.workSpecDao(); + workSpecDao.pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast(); + } +} |