Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

Android AsyncTask: คู่มือและตัวอย่าง (Deprecated)


⚠️ สำคัญ: 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 ขั้นตอน:

  1. onPreExecute() - UI Thread: เตรียมความพร้อม (แสดง Progress)
  2. doInBackground(Params...) - Background Thread: ทำงานหนัก (Network, DB, Compute)
  3. onProgressUpdate(Progress...) - UI Thread: อัปเดต Progress (เรียกจาก publishProgress)
  4. 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 LeakInner Class ถือ Reference Activity → Leak เมื่อ Rotate/Finish
Lifecycle Unawareไม่รู้จัก Lifecycle ทำงานต่อแม้ Activity Destroy
Thread Pool LimitSerial 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 เป็นพื้นฐานสำคัญ