Asynctask to executor
Skill almasumdev/awesome-java-android-agent-skills/.github/skills/migration/asynctask-to-executor
Curated agent skills, conventions, and workflows for building Java Android apps with AI coding agents.
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.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
5.7 KB, 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.