Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

AsyncTask trong Android - Ví dụ và giải thích

Lưu ý quan trọng: AsyncTask đã deprecated từ API 30 (Android 11). Google khuyến nghị dùng Kotlin Coroutines, RxJava, hoặc Executor/ThreadPool. Bài viết này chỉ để hiểu cơ chế legacy và maintain code cũ.

Cơ chế AsyncTask

AsyncTask cho phép chạy background operation và publish kết quả lên UI thread mà không cần tự quản lý Handler/Thread. Nó dùng 3 generic types: AsyncTask.

4 phương thức vòng đời

  1. onPreExecute(): Chạy trên UI thread trước background task. Dùng show ProgressBar, disable button.
  2. doInBackground(Params...): Chạy trên background thread. Thực hiện công việc nặng (network, DB, compute). Không được touch UI ở đây.
  3. onProgressUpdate(Progress...): Chạy trên UI thread. Gọi từ doInBackground qua publishProgress(). Cập nhật progress bar, text.
  4. onPostExecute(Result): Chạy trên UI thread sau khi doInBackground xong. Nhận result, cập nhật UI, dismiss dialog.

Ví dụ: Tải ảnh từ URL

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="Tải ảnh" />

    <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/ivImage"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:layout_marginTop="16dp"
        android:scaleType="centerCrop"
        android:contentDescription="Ảnh tải về" />
</LinearLayout>

MainActivity.java

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 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 ivImage;
    private DownloadImageTask currentTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnDownload = findViewById(R.id.btnDownload);
        progressBar = findViewById(R.id.progressBar);
        ivImage = findViewById(R.id.ivImage);

        btnDownload.setOnClickListener(v -> startDownload());
    }

    private void startDownload() {
        String imageUrl = "https://www.tutorialspoint.com/images/tp-logo-diamond.png";
        
        // Hủy task cũ nếu đang chạy
        if (currentTask != null && currentTask.getStatus() == AsyncTask.Status.RUNNING) {
            currentTask.cancel(true);
        }
        
        currentTask = new DownloadImageTask();
        currentTask.execute(imageUrl);
    }

    private class DownloadImageTask extends AsyncTask {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            btnDownload.setEnabled(false);
            progressBar.setVisibility(View.VISIBLE);
            progressBar.setProgress(0);
            ivImage.setImageBitmap(null);
        }

        @Override
        protected Bitmap doInBackground(String... urls) {
            String urlString = urls[0];
            Bitmap bitmap = null;
            
            try {
                URL url = new URL(urlString);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setDoInput(true);
                connection.connect();
                
                InputStream input = connection.getInputStream();
                
                // Có thể publish progress nếu biết content-length
                int contentLength = connection.getContentLength();
                if (contentLength > 0) {
                    // Đọc từng phần và publish progress (phức tạp hơn)
                    // Ví dụ đơn giản: decode toàn bộ
                    bitmap = BitmapFactory.decodeStream(input);
                    publishProgress(100); // Giả lập
                } else {
                    bitmap = BitmapFactory.decodeStream(input);
                }
                
                input.close();
            } catch (Exception e) {
                e.printStackTrace();
                if (isCancelled()) return null;
            }
            return bitmap;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            progressBar.setProgress(values[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            btnDownload.setEnabled(true);
            progressBar.setVisibility(View.GONE);
            
            if (bitmap != null && !isCancelled()) {
                ivImage.setImageBitmap(bitmap);
            } else {
                // Xử lý lỗi/hủy
                // Toast.makeText(MainActivity.this, "Tải thất bại", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        protected void onCancelled(Bitmap bitmap) {
            super.onCancelled(bitmap);
            btnDownload.setEnabled(true);
            progressBar.setVisibility(View.GONE);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (currentTask != null) {
            currentTask.cancel(true);
        }
    }
}

AndroidManifest.xml - Quyền INTERNET

<manifest ...>
    <uses-permission android:name="android.permission.INTERNET" />
    <application ...> ... </application>
</manifest>

Generic Types Giải Thích

  • Params: Kiểu tham số truyền vào execute() (ví dụ: String - URL)
  • Progress: Kiểu dữ liệu progress (ví dụ: Integer - percent)
  • Result: Kiểu kết quả trả về (ví dụ: Bitmap)

Các trạng thái AsyncTask

  • PENDING: Chưa execute
  • RUNNING: Đang chạy doInBackground
  • FINISHED: Hoàn thành (onPostExecute hoặc onCancelled)

Thay thế hiện đại (Khuyến nghị)

Kotlin Coroutines + ViewModel

// ViewModel
class MainViewModel : ViewModel() {
    private val _image = MutableLiveData()
    val image: LiveData = _image
    
    fun downloadImage(url: String) {
        viewModelScope.launch(Dispatchers.IO) {
            try {
                val bitmap = downloadBitmap(url) // suspend function
                _image.postValue(bitmap)
            } catch (e: Exception) {
                // handle error
            }
        }
    }
}

// Activity
viewModel.image.observe(this) { bitmap ->
    imageView.setImageBitmap(bitmap)
}

ExecutorService / ThreadPool

ExecutorService executor = Executors.newSingleThreadExecutor();
Handler mainHandler = new Handler(Looper.getMainLooper());

executor.execute(() -> {
    Bitmap bitmap = downloadImage(url);
    mainHandler.post(() -> imageView.setImageBitmap(bitmap));
});

Chạy ứng dụng (Legacy)

Nhấn "Tải ảnh" → ProgressBar hiện → Ảnh hiển thị trong ImageView. Nếu nhấn lại khi đang tải → Task cũ bị cancel, task mới bắt đầu.

Lưu ý quan trọng khi dùng AsyncTask (Legacy)

  • Memory leak: Inner class nắm reference Activity → Dùng static inner class + WeakReference
  • Configuration change (rotate) kill Activity → Task vẫn chạy → Crash khi update UI → Dùng ViewModel/LiveData
  • Serial execution trên single thread (API 11-18) vs Parallel (API 19+) → Dùng executeOnExecutor(THREAD_POOL_EXECUTOR) cho parallel
  • Không dùng cho long-running task (service, download lớn) → Dùng WorkManager/Foreground Service