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

Cách dừng luồng AsyncTask trong Android

Hướng dẫn này trình bày cách hủy (cancel) một AsyncTask đang chạy trong Android. AsyncTask cho phép thực hiện tác vụ nền và cập nhật UI, nhưng cần cơ chế dừng đúng cách khi không còn cần thiết (ví dụ: người dùng thoát màn hình, nhấn nút Hủy).

Lưu ý quan trọng: AsyncTask đã bị deprecated từ API 30 (Android 11). Google khuyến nghị dùng Coroutines (Kotlin), ExecutorService, RxJava, hoặc WorkManager cho tác vụ nền. Bài viết này chỉ mang tính tham khảo cho dự án cũ.

Các bước thực hiện

Bước 1: Tạo dự án mới

Mở Android Studio, chọn File → New Project và điền thông tin cần thiết.

Bước 2: Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnDo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/btnCancel"
        android:layout_centerInParent="true"
        android:layout_marginBottom="25sp"
        android:text="Chạy AsyncTask" />

    <Button
        android:id="@+id/btnCancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/textView"
        android:layout_centerInParent="true"
        android:layout_marginBottom="20dp"
        android:text="Hủy" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textSize="20sp"
        android:gravity="center_horizontal" />

</RelativeLayout>

Bước 3: Code MainActivity (src/MainActivity.java)

import android.graphics.Color;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private Button btnDo, btnCancel;
    private TextView textView;
    private AsyncTask myTask;

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

        btnDo = findViewById(R.id.btnDo);
        btnCancel = findViewById(R.id.btnCancel);
        textView = findViewById(R.id.textView);

        btnDo.setOnClickListener(v -> {
            textView.setText("");
            myTask = new DownloadTask().execute("Task1", "Task2", "Task3", "Task4", "Task5");
        });

        btnCancel.setOnClickListener(v -> {
            if (myTask != null) {
                myTask.cancel(true); // true = interrupt thread nếu đang sleep/wait
            }
        });
    }

    private class DownloadTask extends AsyncTask<String, Integer, List<String>> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            textView.setTextColor(Color.BLUE);
            textView.setText(textView.getText() + "\n Bắt đầu tác vụ....");
        }

        @Override
        protected List<String> doInBackground(String... tasks) {
            int count = tasks.length;
            List<String> taskList = new ArrayList<>(count);

            for (int i = 0; i < count; i++) {
                String currentTask = tasks[i];
                taskList.add(currentTask);

                try {
                    Thread.sleep(1000); // Giả lập tác vụ tốn thời gian
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                // Cập nhật tiến độ (gọi onProgressUpdate)
                publishProgress((int) (((i + 1) / (float) count) * 100));

                // KIỂM TRA CANCEL - QUAN TRỌNG
                if (isCancelled()) {
                    break; // Thoát vòng lặp sớm
                }
            }
            return taskList;
        }

        @Override
        protected void onCancelled() {
            super.onCancelled();
            textView.setTextColor(Color.RED);
            textView.setText(textView.getText() + "\n Tác vụ đã bị hủy..");
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            super.onProgressUpdate(progress);
            textView.setText(textView.getText() + "\n Hoàn thành: " + progress[0] + "%");
        }

        @Override
        protected void onPostExecute(List<String> result) {
            super.onPostExecute(result);
            // Chỉ chạy nếu KHÔNG bị cancel
            if (!isCancelled()) {
                textView.setText(textView.getText() + "\n\n Hoàn tất....");
                for (int i = 0; i < result.size(); i++) {
                    textView.setText(textView.getText() + "\n" + result.get(i));
                }
            }
        }
    }
}

Bước 4: Cấu hình AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Chạy ứng dụng

Kết nối thiết bị, nhấn Run. Nhấn "Chạy AsyncTask" để bắt đầu, nhấn "Hủy" để dừng giữa chừng.

Cách dừng luồng AsyncTask trong Android Cách dừng luồng AsyncTask trong Android

Các điểm then chốt để hủy AsyncTask đúng cách

  1. Gọi cancel(true): Tham số true nghĩa là interrupt() sẽ được gọi trên thread nền (hữu ích nếu thread đang sleep(), wait(), I/O blocking)
  2. Kiểm tra isCancelled() trong doInBackground(): Phải kiểm tra định kỳ và thoát sớm (break/return)
  3. Override onCancelled(): Xử lý UI khi bị hủy (đổi màu, thông báo)
  4. Kiểm tra isCancelled() trong onPostExecute(): Tránh cập nhật UI không cần thiết sau khi đã hủy

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

Kotlin Coroutines (Structured Concurrency)

// Trong ViewModel hoặc Activity với lifecycleScope
val job = lifecycleScope.launch {
    try {
        for (i in 1..5) {
            delay(1000) // Không block thread
            _progress.value = i * 20
            // Tự động hủy khi scope bị hủy (ví dụ: onDestroy)
        }
        _result.value = "Hoàn tất"
    } catch (e: CancellationException) {
        _status.value = "Đã hủy"
        throw e // Quan trọng: ném lại để coroutine kết thúc đúng
    }
}

// Hủy: job.cancel()

ExecutorService + Future

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(() -> {
    for (int i = 0; i < 5; i++) {
        if (Thread.currentThread().isInterrupted()) return; // Kiểm tra interrupt
        Thread.sleep(1000);
        runOnUiThread(() -> updateProgress(i * 20));
    }
});

// Hủy:
future.cancel(true); // interrupt = true
executor.shutdown();

WorkManager (cho tác vụ bền vững, chạy ngầm)

// Hủy work theo ID hoặc tag
WorkManager.getInstance(context).cancelWorkById(workId);
// Hoặc
WorkManager.getInstance(context).cancelAllWorkByTag("download_tag");