⚠️ สำคัญ: AsyncTask ถูก Deprecated แล้ว
AsyncTask ถูก Deprecated ตั้งแต่ API 30 (Android 11) และถูกลบออกใน API 34 (Android 14) ไม่ควรใช้ในโปรเจกต์ใหม่ ใช้ Kotlin Coroutines, RxJava, หรือ ExecutorService แทน
บทความนี้เก็บไว้เพื่อ Maintenance Legacy Code และเข้าใจหลักการ
หลักการทำงานของ AsyncTask
AsyncTask ช่วยให้ทำงาน Background Thread และ Update UI Thread ได้ง่าย โดยมี 4 ขั้นตอน:
- onPreExecute() - UI Thread: เตรียมความพร้อม (แสดง Progress)
- doInBackground(Params...) - Background Thread: ทำงานหนัก (Network, DB, Compute)
- onProgressUpdate(Progress...) - UI Thread: อัปเดต Progress (เรียกจาก publishProgress)
- onPostExecute(Result) - UI Thread: จัดการผลลัพธ์
Generic Types: AsyncTask<Params, Progress, Result>
- Params: ประเภท Parameter สำหรับ execute() และ doInBackground
- Progress: ประเภท Progress สำหรับ publishProgress() และ onProgressUpdate
- Result: ประเภท Result สำหรับ doInBackground return และ onPostExecute
ตัวอย่าง: ดาวน์โหลดภาพ (Legacy Code)
Layout (activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<Button
android:id="@+id/btnDownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ดาวน์โหลดภาพ" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
style="@android:style/Widget.ProgressBar.Horizontal"
android:visibility="gone" />
<ImageView
android:id="@+id/imageView"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="16dp"
android:scaleType="centerCrop"
android:visibility="gone" />
</LinearLayout>
MainActivity.java (Legacy AsyncTask)
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
private Button btnDownload;
private ProgressBar progressBar;
private ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnDownload = findViewById(R.id.btnDownload);
progressBar = findViewById(R.id.progressBar);
imageView = findViewById(R.id.imageView);
btnDownload.setOnClickListener(v -> {
btnDownload.setEnabled(false);
new DownloadImageTask().execute("https://www.tutorialspoint.com/images/tp-logo-diamond.png");
});
}
private class DownloadImageTask extends AsyncTask {
@Override
protected void onPreExecute() {
super.onPreExecute();
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress(0);
imageView.setVisibility(View.GONE);
}
@Override
protected Bitmap doInBackground(String... urls) {
String url = urls[0];
try {
URL imageUrl = new URL(url);
HttpURLConnection connection = (HttpURLConnection) imageUrl.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream input = connection.getInputStream();
// คำนวณ Progress (ถ้า Server รองรับ Content-Length)
int contentLength = connection.getContentLength();
int downloaded = 0;
byte[] buffer = new byte[4096];
java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream();
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
downloaded += bytesRead;
if (contentLength > 0) {
int progress = (downloaded * 100) / contentLength;
publishProgress(progress); // เรียก onProgressUpdate
}
}
byte[] data = output.toByteArray();
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
input.close();
output.close();
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressBar.setProgress(values[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
progressBar.setVisibility(View.GONE);
btnDownload.setEnabled(true);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
imageView.setVisibility(View.VISIBLE);
} else {
Toast.makeText(MainActivity.this, "ดาวน์โหลดล้มเหลว", Toast.LENGTH_SHORT).show();
}
}
}
}
AndroidManifest.xml - Permission
...
ปัญหาของ AsyncTask (ทำไมถึง Deprecated)
| ปัญหา | คำอธิบาย |
|---|---|
| Memory Leak | Inner Class ถือ Reference Activity → Leak เมื่อ Rotate/Finish |
| Lifecycle Unaware | ไม่รู้จัก Lifecycle ทำงานต่อแม้ Activity Destroy |
| Thread Pool Limit | Serial Executor (API 13+) รันทีละ Task, Parallel เดิมแค่ 5 Threads |
| Error Handling | ยากต่อการจัดการ Exception และ Cancellation |
| Testing | ยากต่อการ Unit Test |
| Boilerplate | โค้ดเยอะ สร้าง Class ทุก Task |
การแก้ปัญหา Memory Leak (ถ้าบังคับใช้)
// ใช้ Static Inner Class + WeakReference private static class DownloadImageTask extends AsyncTask{ private final WeakReference activityRef; private final WeakReference progressRef; private final WeakReference imageRef; DownloadImageTask(MainActivity activity) { this.activityRef = new WeakReference<>(activity); this.progressRef = new WeakReference<>(activity.progressBar); this.imageRef = new WeakReference<>(activity.imageView); } @Override protected void onPostExecute(Bitmap bitmap) { MainActivity activity = activityRef.get(); if (activity == null || activity.isFinishing() || activity.isDestroyed()) return; ProgressBar pb = progressRef.get(); ImageView iv = imageRef.get(); if (pb != null) pb.setVisibility(View.GONE); if (iv != null && bitmap != null) { iv.setImageBitmap(bitmap); iv.setVisibility(View.VISIBLE); } } // ... doInBackground, onProgressUpdate เหมือนเดิม }
Modern Replacement: Kotlin Coroutines (แนะนำ)
// build.gradle
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"
// ใน ViewModel
class MainViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState.Idle)
val uiState: StateFlow = _uiState
sealed interface UiState {
data class Loading(val progress: Int) : UiState
data class Success(val bitmap: Bitmap) : UiState
data class Error(val message: String) : UiState
object Idle : UiState
}
fun downloadImage(url: String) {
viewModelScope.launch {
_uiState.value = UiState.Loading(0)
try {
val bitmap = withContext(Dispatchers.IO) {
// Network call here (OkHttp/Retrofit)
downloadBitmap(url) // suspend function
}
_uiState.value = UiState.Success(bitmap)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message ?: "Unknown error")
}
}
}
}
// ใน Activity/Fragment (Collect)
lifecycleScope.launch {
viewModel.uiState.collect { state ->
when (state) {
is UiState.Loading -> { progressBar.progress = state.progress; progressBar.visible() }
is UiState.Success -> { imageView.setImageBitmap(state.bitmap); imageView.visible() }
is UiState.Error -> { Toast.makeText(this, state.message, Toast.LENGTH_SHORT).show() }
}
}
}
ตัวเลือกอื่นๆ
- ExecutorService / ThreadPoolExecutor - Low-level Control
- RxJava (Single/Completable) - Reactive, Operator มาก
- WorkManager - Deferred/Guaranteed Background Work (Sync, Backup, Upload)
- Handler + Looper - Simple UI Update จาก Thread
สรุป
- AsyncTask Deprecated - ห้ามใช้ในโปรเจกต์ใหม่
- Legacy Code: ใช้
Static Inner Class + WeakReferenceป้องกัน Leak - Migration: Kotlin Coroutines + Flow + ViewModel (Standard)
- WorkManager สำหรับ Background Work ที่ต้องทำให้เสร็จ (แม้ Kill App)
- เข้าใจหลักการ Thread/Handler/Looper เป็นพื้นฐานสำคัญ