AsyncTask의 대체
안드로이드 개발에서 비동기 처리는 사용자 경험을 해치지 않고 백그라운드에서 시간이 오래 걸리는 작업(네트워크 통신, 데이터베이스 접근 등)을 처리하는 데 필수적입니다. 과거에는 AsyncTask가 이러한 비동기 처리를 위한 손쉬운 방법으로 널리 사용되었습니다. 하지만 AsyncTask는 몇 가지 중요한 설계상의 단점(예: 메모리 누수 위험, 유연성 부족, 복잡한 스레드 관리의 어려움) 때문에 더 이상 권장되지 않습니다.
https://developer.android.com/reference/android/os/AsyncTask
AsyncTask | API reference | Android Developers
developer.android.com
따라서 안드로이드는 Kotlin Coroutines와 함께 RxJava 비동기 프로그래밍의 대안으로 말하고 있습니다.
간단한 예제를 통해 구조를 파악해 보겠습니다
의존성 추가
dependencies {
implementation libs.rxjava
implementation libs.rxandroid
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp"
tools:context=".MainActivity">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" /> <!-- 초기에는 숨김 -->
<TextView
android:id="@+id/textViewResult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="여기에 결과가 표시됩니다."
android:textSize="18sp"
android:layout_marginTop="16dp"
tools:text="데이터 로딩 완료!" />
<Button
android:id="@+id/buttonLoadData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="데이터 로드 시작"
android:layout_marginTop="24dp" />
</LinearLayout>
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class MainActivity extends AppCompatActivity {
// UI 요소들에 대한 참조 변수
private TextView textViewResult;
private ProgressBar progressBar;
private Button buttonLoadData;
String TAG = "MainActivity";
// RxJava 구독을 관리하기 위한 CompositeDisposable 객체.
// Activity가 종료될 때 모든 구독을 해제하여 메모리 누수를 방지합니다.
private CompositeDisposable disposables = new CompositeDisposable();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// activity_main.xml 레이아웃을 현재 액티비티의 콘텐츠 뷰로 설정합니다.
setContentView(R.layout.activity_main);
// XML 레이아웃에서 UI 요소들의 참조를 가져옵니다.
textViewResult = findViewById(R.id.textViewResult);
progressBar = findViewById(R.id.progressBar);
buttonLoadData = findViewById(R.id.buttonLoadData);
// "데이터 로드 시작" 버튼에 클릭 리스너를 설정합니다.
buttonLoadData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 버튼 클릭 시 비동기 작업을 시작하는 메서드를 호출합니다.
performAsyncTaskWithRxJava();
}
});
}
/**
* RxJava를 사용하여 비동기 작업을 수행하고 결과를 UI에 표시하는 메서드입니다.
*/
private void performAsyncTaskWithRxJava() {
// 1. UI 업데이트: 작업 시작 전에 로딩 상태를 표시합니다.
showLoadingState();
// 2. Observable 생성: 비동기적으로 수행될 작업을 정의합니다.
// Observable.create()는 직접 Observable을 생성할 때 사용됩니다.
Observable<String> dataObservable = Observable.create(emitter -> {
// emitter: Observable이 데이터를 발행하거나 완료, 오류를 알리는 인터페이스
try {
// ====== 백그라운드 스레드에서 실행될 핵심 로직 ======
// 실제 네트워크 요청, 데이터베이스 접근, 복잡한 계산 등이 여기에 들어갑니다.
// 이 예제에서는 3초간 대기하여 비동기 작업이 수행되는 것을 시뮬레이션합니다.
System.out.println("RxJava 작업 시작 - Thread: " + Thread.currentThread().getName());
Thread.sleep(3000); // 3초 동안 작업 수행 시뮬레이션
String result = "네트워크에서 데이터를 성공적으로 로드했습니다!";
emitter.onNext(result); // 작업 결과를 발행합니다. (Observer의 onNext 콜백으로 전달됩니다.)
emitter.onComplete(); // 작업이 성공적으로 완료되었음을 알립니다. (Observer의 onComplete 콜백으로 전달됩니다.)
// ==================================================
} catch (InterruptedException e) {
// 작업 중 InterruptedException (예: 쓰레드 중단)이 발생하면 오류를 발행합니다.
emitter.onError(e); // Observer의 onError 콜백으로 전달됩니다.
} catch (Exception e) {
// 기타 예상치 못한 예외가 발생하면 오류를 발행합니다.
emitter.onError(e); // Observer의 onError 콜백으로 전달됩니다.
}
});
// 3. RxJava 연산자 체이닝: 스케줄러 설정 및 구독
dataObservable
// subscribeOn(): Observable의 작업(create 내부 로직)이 실행될 스레드를 지정합니다.
// Schedulers.io()는 I/O 바운드 작업(네트워크, 파일 입출력 등)에 적합한 스레드 풀을 사용합니다.
.subscribeOn(Schedulers.io())
// observeOn(): 발행되는 데이터(onNext)가 전달될 스레드를 지정합니다.
// AndroidSchedulers.mainThread()는 Android의 UI 스레드를 나타냅니다.
// UI 업데이트는 반드시 메인 스레드에서 이루어져야 하므로 여기서 사용합니다.
.observeOn(AndroidSchedulers.mainThread())
// subscribe(): Observable을 구독하고 데이터를 수신합니다. Observer 객체를 인자로 받습니다.
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
// 구독이 성공적으로 시작되었을 때 호출됩니다.
// 이 메서드는 옵저버가 연결된 직후에 한 번만 호출됩니다.
System.out.println("RxJava 구독 시작됨 - Thread: " + Thread.currentThread().getName());
// Disposable 객체를 CompositeDisposable에 추가하여 구독을 관리합니다.
// 이렇게 하면 나중에 disposables.dispose() 호출 시 모든 구독이 한 번에 취소됩니다.
disposables.add(d);
}
@Override
public void onNext(String s) {
// Observable이 onNext(data)를 호출하여 데이터를 발행할 때마다 호출됩니다.
// observeOn(AndroidSchedulers.mainThread()) 때문에 이 메서드는 항상 메인 스레드에서 실행됩니다.
System.out.println("RxJava 데이터 수신 - Thread: " + Thread.currentThread().getName());
// UI 스레드에서 TextView의 텍스트를 업데이트합니다.
textViewResult.setText(s);
}
@Override
public void onError(Throwable e) {
// Observable이 onError(exception)를 호출하여 오류를 발행했을 때 호출됩니다.
// 이 메서드도 메인 스레드에서 실행됩니다.
System.err.println("RxJava 오류 발생 - Thread: " + Thread.currentThread().getName() + ": " + e.getMessage());
// 오류 상태를 UI에 표시하는 메서드를 호출합니다.
handleErrorState("데이터 로딩 실패: " + e.getMessage());
}
@Override
public void onComplete() {
// Observable이 onComplete()를 호출하여 작업이 성공적으로 완료되었음을 알릴 때 호출됩니다.
// 이 메서드도 메인 스레드에서 실행됩니다.
System.out.println("RxJava 작업 완료됨 - Thread: " + Thread.currentThread().getName());
// 모든 작업이 완료되었음을 알리고 UI를 완료 상태로 변경하는 메서드를 호출합니다.
showCompletedState();
}
});
}
/**
* 비동기 작업이 진행 중일 때 UI를 업데이트하는 메서드입니다.
* ProgressBar를 보여주고, 결과 TextView는 숨기며, 버튼을 비활성화합니다.
*/
private void showLoadingState() {
Log.d(TAG, "showLoadingState: 프로그레스바를 활성화합니다.");
progressBar.setVisibility(View.VISIBLE); // ProgressBar를 보이게 합니다.
textViewResult.setVisibility(View.GONE); // 결과 TextView는 숨깁니다.
buttonLoadData.setEnabled(false); // 버튼을 비활성화하여 중복 클릭을 방지합니다.
}
/**
* 비동기 작업이 성공적으로 완료되었을 때 UI를 업데이트하는 메서드입니다.
* ProgressBar를 숨기고, 결과 TextView를 다시 보여주며, 버튼을 활성화합니다.
*/
private void showCompletedState() {
Log.d(TAG, "showCompletedState: 작업이 완료되어 프로그레스바를 숨깁니다.");
progressBar.setVisibility(View.GONE); // ProgressBar를 숨깁니다.
textViewResult.setVisibility(View.VISIBLE); // 결과 TextView를 다시 보이게 합니다.
buttonLoadData.setEnabled(true); // 버튼을 다시 활성화합니다.
}
/**
* 비동기 작업 중 오류가 발생했을 때 UI를 업데이트하는 메서드입니다.
* ProgressBar를 숨기고, 오류 메시지를 TextView에 표시하며, Toast 메시지를 보여주고, 버튼을 활성화합니다.
* @param errorMessage 표시할 오류 메시지
*/
private void handleErrorState(String errorMessage) {
progressBar.setVisibility(View.GONE); // ProgressBar를 숨깁니다.
textViewResult.setVisibility(View.VISIBLE); // 결과 TextView를 보이게 합니다.
textViewResult.setText(errorMessage); // TextView에 오류 메시지를 설정합니다.
buttonLoadData.setEnabled(true); // 버튼을 다시 활성화합니다.
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); // 사용자에게 오류를 알립니다.
}
/**
* Activity의 생명주기 메서드 중 하나로, Activity가 소멸될 때 호출됩니다.
* 여기서 RxJava 구독을 해제하는 것이 매우 중요합니다.
* 그렇지 않으면 액티비티가 파괴된 후에도 백그라운드 작업이 계속 실행되어 메모리 누수가 발생할 수 있습니다.
*/
@Override
protected void onDestroy() {
super.onDestroy(); // 반드시 super 메서드를 먼저 호출해야 합니다.
// CompositeDisposable이 null이 아니고 아직 해제되지 않았다면,
// 모든 구독을 해제합니다.
if (disposables != null && !disposables.isDisposed()) {
disposables.dispose(); // 모든 구독 취소
System.out.println("RxJava 구독 해제됨");
}
}
}
시연영상
'Android' 카테고리의 다른 글
[Java][Android] BuildConfig을 사용하여 안드로이드 앱에서 특정 값을 전역으로 사용하기 / 모든 클래스 사용 / (1) | 2025.08.02 |
---|---|
[Java][Android] Shared Element Transition 사용하여 화면이 커지면서 전환 / 부드럽게 (8) | 2025.07.21 |
[Java][Android] 안드로이드 자바 버튼 연속, 연타 방지 방법 (5) | 2025.06.02 |
[Java][Android] SharedPreferences 데이터 암호화 하기! (1) | 2025.05.26 |
[Java][Android] 화면 회전, 메모리 부족 onSaveInstanceState()로 데이터 지키기 (2) | 2025.05.19 |