[Android] AsyncTask를 RxJava로 대체하기

박상군·2022년 5월 17일
1

Android

목록 보기
8/9
post-thumbnail

에이 그래도 RxJava보단 코루틴이지

회사에서 사용하던 코드를 살펴보니 아직까지 deprecated된 AsyncTask를 사용하는 기능들이 많이 남아있어 이 부분을 리팩토링 해야겠다! 라고 생각했습니다.

개인 프로젝트에선 주로 코루틴을 사용했기 때문에 코루틴으로 변환하면 편했겠지만 늘 먹던 것만 먹을 순 없으니..

AsyncTask의 문제점

  • 제대로 된 의존성 관리가 안되어서 테스트를 하기가 매우 어렵다
  • IO 관련 로직을 UI 레이어에서 처리해야 한다.
  • 메인 스레드 외에서는 시작을 할 수 없다.
  • 메모리 누수가 일어나기 쉽다.

AsyncTask에는 위와 같은 많은 문제점들이 있기 때문에 요즘은 보통 코루틴이나 RxJava를 사용합니다.

RxJava의 장점

  • ReactiveX는 관찰가능한 절차를 통해 비동기, 이벤트 기반 프로그램을 구성하기 위한 라이브러리
  • Observer Pattern을 확장하며, Sequence를 조합할 수 있는 연산자를 지원한다.
  • low-level Thread, 동기화, Thread 안전성, non-blocking I/O에 관한 우려를 줄인다.

공식사이트에서 설명하는 ReactiveX의 설명은 위와 같습니다.

허나 단점도 존재하는데

  • 전통적인 스레드 기반의 프로그래밍을 사용하는 자바와 접근 방식이 다르기 때문에 러닝 커브가 가파르다.
  • 여러 스레드를 사용하는 경우 예상치 못한 문제가 발생할 수도 있고 디버깅하기도 어렵다.

구현 방법

1. 의존성 추가

	implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    implementation 'io.reactivex.rxjava3:rxjava:3.0.7'

2. 객체 구현

RxJava에는 관찰할 수 있는 여러 데이터 스트림이 존재하는데 몇 가지를 알아보자면

  • Observable : 기본적으로 여러 개의 데이터를 발행할 수 있습니다.
  • Single : Observable에서 변형된 형태로 한 개의 데이터만 발행할 수 있습니다.
  • Maybe : 최대 하나의 데이터를 발행할 수 있습니다. 또한 데이터 없이도 발행이 가능합니다.
  • Completable: Completable은 위와 다르게 데이터 발행의 완료/에러 신호만 보내는 특수한 형태 입니다.

예제로 살펴보면 다음은 서버에서 Apk파일을 다운로드 받아 디렉토리에 저장하는 기능을 RxJava로 구현한 것입니다.

먼저 Apk를 받아 디렉토리에 저장하고 저장된 주소를 발행해주는 execute함수를 구현 했습니다.
데이터는 한번만 받기 때문에 Single타입으로 리턴 받겠습니다.

public Single<String> execute(){ // 
        String downloadURL = sDownLoadUrl;
        boolean downloadStat = false;

        Handler handler = new Handler(Looper.getMainLooper());
        handler.postDelayed(this::showProgressDialog, 0); // Apk를 다운로드 받는 동안 Progress를 띄움 

        try {
            URL url = new URL(downloadURL);
            downloadStat = makeConnection(url); // Apk를 다운로드 받고 결과를 Boolean 형태로 리턴 해주는 함수

        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        if (downloadStat){
            return Single.create(e -> {
                e.onSuccess(filePath); // 다운로드가 완료되면 Single 타입으로 Apk가 저장된 주소인 filePath를 발행
                progressDialog.dismiss(); // 다운로드가 종료되면 Progress를 종료
            });
        } else {
            return Single.create(e -> {
                e.onError(new Throwable()); // 다운로드 실패시 에러처리
                progressDialog.dismiss(); // 다운로드가 종료되면 Progress를 종료
            });
        }
    }

이제 위에서 구현한 함수를 사용하려면

new DownloadAPKTask()
        .execute() // 위에서 구현한 execute함수
        .subscribeOn(Schedulers.io()) // io스케줄러에서 처리
        .observeOn(AndroidSchedulers.mainThread()) // 받은 데이터를 처리할 스케줄러, 받은 데이터로BackGround 작업을 하고 싶다면 io스케줄러를 넣어주면 된다.
        .subscribe((filePath, t2) -> {
         if (t2 == null) {
          // 다운로드한 apk 주소가 filePath로 넘어옴
          Toast.makeText(this, "받은 주소는 "+filePath,Toast.LENGTH_SHORT).show(); 여기서 Ui 처리
          } else {
            t2.printStackTrace();
         }
});

위와 같은 식으로 구현이 가능합니다.

Single말고 Completable에 대한 예제를 한 가지 보면

public void execute(){
        Completable.create(e -> {
            // TODO: 비동기 처리 코드 구현  
            e.onComplete();
        }).subscribeOn(Schedulers.io())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(() -> System.out.println("Completable 완료!"));
    }

Completable의 예제를 보면 subscribe() 함수안에 람다식으로 ()가 선언 되어 있습니다. Observerble이나 Single과는 다르게 성공, 실패 여부만 알려주기 때문인데 일반 함수로 비교해서 보자면 Single은 리턴 값이 있는 함수이고 Completable은 void 함수라고 보면 될 것 같다.


마무리

RxJava를 처음 접했을 땐 기존의 자바에서 사용하는 비동기 방식과는 너무 달랐기 때문에
이게 무슨 소리지..라는 생각이 들었고, 배우는데도 많은 시간이 들었던 것 같습니다. 지금도 아직 완벽하게 이해하는지도 잘 모르겠지만 분명한 건 배워두면 정말 좋은 기술이라고 생각이 듭니다!

0개의 댓글