Asynctask to executor
Skill almasumdev/awesome-java-android-agent-skills/.github/skills/migration/asynctask-to-executor
Step-by-step migration from deprecated AsyncTask to modern java.util.concurrent and WorkManager. Use this when modernizing legacy Java Android code that still uses AsyncTask.From its SKILL.md
npx -y skills add almasumdev/awesome-java-android-agent-skills --skill asynctask-to-executorAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.7 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
Migrating AsyncTask to ExecutorService / WorkManager
Instructions
AsyncTask has been deprecated since API 30. It leaks Activities, serializes tasks globally on older devices, and has no structured cancellation. Replace it with either a scoped ExecutorService + Handler or WorkManager depending on the use case.
1. Decision Tree
- Short-lived, tied to a screen (load, parse, save on click) →
ExecutorService+ViewModel. - Deferrable, must run even if the app dies (upload, sync) →
WorkManager. - Reactive pipeline with cancellation-on-recycle → RxJava 3 (see
rxjava3skill).
2. Pattern: In-Activity AsyncTask
Before:
private class LoadTask extends AsyncTask<Void, Void, List<Article>> {
@Override
protected List<Article> doInBackground(Void... voids) {
return repo.loadAll();
}
@Override
protected void onPostExecute(List<Article> items) {
if (isDestroyed()) return;
adapter.submitList(items);
}
}
After (ExecutorService + ViewModel + LiveData):
@HiltViewModel
public class ArticleListViewModel extends ViewModel {
private final ArticleRepository repo;
private final ExecutorService io = Executors.newSingleThreadExecutor();
private final MutableLiveData<List<Article>> items = new MutableLiveData<>();
@Inject
ArticleListViewModel(ArticleRepository repo) { this.repo = repo; }
public LiveData<List<Article>> items() { return items; }
public void load() {
io.execute(() -> items.postValue(repo.loadAll()));
}
@Override
protected void onCleared() {
io.shutdownNow();
}
}
The Fragment observes items() via getViewLifecycleOwner(). No leak, no isDestroyed() guard, cancellation is automatic when the ViewModel is cleared.
3. Pattern: AsyncTask with Progress
Before:
class DownloadTask extends AsyncTask<String, Integer, File> {
protected File doInBackground(String... urls) {
publishProgress(0);
// ...
publishProgress(100);
return file;
}
protected void onProgressUpdate(Integer... p) { progressBar.setProgress(p[0]); }
}
After:
public record DownloadState(int progress, File file, String error) { }
public void download(String url) {
MutableLiveData<DownloadState> state = new MutableLiveData<>();
io.execute(() -> {
try {
for (int p : steps) {
state.postValue(new DownloadState(p, null, null));
}
File out = doDownload(url);
state.postValue(new DownloadState(100, out, null));
} catch (IOException e) {
state.postValue(new DownloadState(0, null, e.getMessage()));
}
});
}
4. Pattern: Background Sync (move to WorkManager)
Before:
new SyncTask().execute(); // started from BOOT_COMPLETED receiver
After:
public class SyncWorker extends Worker {
private final ArticleRepository repo;
@AssistedInject
public SyncWorker(@Assisted Context ctx, @Assisted WorkerParameters params,
ArticleRepository repo) {
super(ctx, params);
this.repo = repo;
}
@NonNull
@Override
public Result doWork() {
try {
repo.syncBlocking();
return Result.success();
} catch (IOException e) {
return getRunAttemptCount() < 3 ? Result.retry() : Result.failure();
}
}
}
// Enqueue
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
PeriodicWorkRequest req = new PeriodicWorkRequest.Builder(
SyncWorker.class, 6, TimeUnit.HOURS)
.setConstraints(constraints)
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"article-sync", ExistingPeriodicWorkPolicy.KEEP, req);
WorkManager survives process death, retries on backoff, and respects constraints.
5. Cancellation Map
| AsyncTask call | Replacement |
|---|---|
task.cancel(true) | future.cancel(true) or executor.shutdownNow() |
isCancelled() in doInBg | Check Thread.currentThread().isInterrupted() |
onCancelled() | catch (InterruptedException) or future cancellation callback |
SERIAL_EXECUTOR | Executors.newSingleThreadExecutor() |
THREAD_POOL_EXECUTOR | Executors.newFixedThreadPool(n) with named threads |
6. Step-by-Step Migration
- List all
AsyncTasksubclasses (grep -r 'extends AsyncTask'). - For each, classify as: UI-scoped, deferrable, or reactive.
- Replace with the corresponding pattern above.
- Delete the
AsyncTasksubclass. - Turn on the lint check
AsyncTaskUsage(android.lint.warningsAsErrors = true) to prevent regressions. - Audit for
Handler(Looper.getMainLooper())+ background thread combinations — many are also hand-rolled AsyncTasks. Consolidate onLiveData.postValueorrunOnUiThreadat the boundary only.
Checklist
-
grep -rn 'AsyncTask' src/main/javareturns zero results. - Every background executor is owned by a lifecycle (
ViewModel.onCleared,WorkManager, orshutdownon app exit). - No
new Handler()without an explicitLooper. -
WorkManageris used for anything that must survive the process. - Lint rule
AsyncTaskUsageis set toerror.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.