Rxjava를 이용한 안드로이드 개발
편리한 비동기 처리
고재성
2018-03-09
Overview
브랜디는 현재 2.0 기반 Android 버전입니다. Main Thread와 Sub Thread 사이의 ANR를 방지하려고 Volley, Otto Bus Library를 사용해서 백엔드 서비스(back-end Service)를 연동하고 있습니다.
이제 3.0 개발로 더 좋은 백엔드 서비스 기능을 만들려고 합니다. (기반 작업은 이미 완료했습니다.) 다만 3년 동안 브랜디 앱을 개발하면서 느꼈던 고통과 피로를 ‘제발’ 줄여보고 싶어서 브랜디 3.0에서는 Retrofit2 와 RxJava, Lambda 표현식을 사용하기로 했습니다.
RxJava(Reactive programming)는 가장 추천하고 싶은 것 중 하나입니다. 우리는 함수형 리액티브(반응형) 프로그램이라는 표현으로 자주 마주치곤 하는데요. 주로 옵저버 패턴(Observer pattern)을 대체하기 위해 사용합니다. 단순히 데이터를 넘기고 마무리하는 건 옵저버 패턴으로도 충분하지만 대부분의 문제는 이벤트들을 묶어서 사용할 때 생깁니다.1) RxJava는 이벤트에 대한 조건 처리나 실패 처리, 리소스 정리에 대비해 사용합니다. 기존 방식의 명령형 리액티브 접근 방식을 사용하면 복잡함이 지속적으로 증가하는 반면, 함수형 리액티브 프로그래밍은 효율을 크게 높일 수 있습니다. 몇 가지 예제와 함께 살펴보겠습니다.
Android에 직접 사용해보기
새로운 프로젝트를 생성한 후, 아래와 같이 build.gradle 파일을 수정해봅시다. (JDK 1.8 설치 필수)
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "kr.co.brandi.myapplication"
minSdkVersion 21
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
//추가된 부분 1
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//추가된 부분2
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxjava:2.1.3'
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:design:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
이제 람다 표현식과 RxJava를 사용할 준비가 되었습니다.
Flowable.just("Hello World").subscribe(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
Log.v(tag, s);
}
});
Flowable.just("Hello World !").subscribe(s -> Log.v(tag, s));
간단한 생성자와 결과를 출력하는 부분입니다. 두 번째 subscribe는 람다 표현식으로 인터페이스를 생성하지 않더라도 첫 부분과 동일하게 결과물을 얻을 수 있습니다.2) 이제 RxJava에서 간단한 필터링으로 간편하게 데이터를 가공하는 능력을 확인해보겠습니다. 아래 코드는 기본적인 List 의 값을 출력하는 부분입니다.
List<Integer> valueList = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
for (int data : valueList) {
String result = "value " + data;
Log.v(tag, result);
}
Flowable.fromIterable(valueList)
.map(new Function<Integer, String>() {
@Override
public String apply(Integer data) throws Exception {
return "value : " + data;
}
})
//.map(data -> "value : " + data)
.subscribe(data -> Log.v(tag, data));
위의 코드에 조건을 추가해 ’짝수’만 출력하겠습니다.
for (int data : valueList) {
if ((data % 2) == 0) {
String result = "value " + data;
Log.v(tag, result);
}
}
Flowable.fromIterable(valueList)
//.filter(data -> {
// return (data % 2) == 0;
//})
.filter(data -> (data % 2) == 0)
.map(data -> "value : " + data)
.subscribe(data -> Log.v(tag, data));
위와 같이 데이터 가공은 순차적으로 진행되고, 여러 함수로 간단하게 처리할 수 있습니다. 원하는 데이터 가공을 위해 filter, map 등의 함수들을 순차적으로 이어 붙일 수도 있습니다.
위에서 보여드린 RxJava는 간단한 예시이기 때문에 RxJava 의 기능을 좀 더 보여드리겠습니다.
String[] data1 = {Shape.HEXAGON, Shape.OCTAGON, Shape.RECTANGLE};
String[] data2 = {Shape.TRIANGLE, Shape.DIAMOND, Shape.PENTAGON};
Flowable<String> source =
Flowable.combineLatest(
Flowable.fromArray(data1)
.zipWith(Flowable.interval(100L, TimeUnit.MILLISECONDS), (shape, notUsed) -> Shape.getId(shape)),
Flowable.fromArray(data2)
.zipWith(Flowable.interval(150L, 200L, TimeUnit.MILLISECONDS), (shape, notUsed) -> Shape.getSuffix(shape)),
(id, suffix) -> id + suffix);
source.subscribe(s -> Log.d(getThreadName(), s));
CombineLatest() 함수를 이용해 두 개의 스트림을 하나로 처리하는 방법을 보여 드렸습니다. 각각의 스트림은 interval 함수를 시간 간격으로 data1과 data2 배열의 개수만큼 반복하여 처리하는 로직입니다. 서로 다른 두 스트림은 마지막 데이터를 가지고 있으며 새로운 데이터가 나올 때마다 하나의 스트림으로 출력됩니다.
Conclusion
만약 RxJava를 이용하지 않고 두 개의 TimerTask를 이용해서 코딩했다면 결과는 같았을지도 모릅니다. 이제 RxJava를 알기 때문에 다시는 TimerTask를 이용해서 코딩할 일은 없을 겁니다. 알면 알수록 다양한 기능을 갖추고 있는 RxJava! 이제 브랜디 상용화 버전에 사용할 수 있게 다시 개발의 숲으로 들어가겠습니다. 그럼 이만.
1)함수나 네트워크 호출의 비동기 응답
2)Java 8 람다 표현식 자세히 살펴보기, 2018.03.09.
3)RxJava on Android