[번역]RxJava를 이용한 Dagger 2 비동기 주입

10 Dec 2016

원본 - First version of this post was originally written on my dev-blog:[http://frogermcs.github.io/

몇 주 전 Producers를 사용한 Dagger2의 비동기 의존성 주입에 대한 글을 작성하였다. 백그라운드 스레드에서 객체 초기화를 수행하면 큰 장점을 하나 가진다 -리얼 타임에서 UI를 그릴 책임이 있는 메인 스레드를 차단하지 않는다(부드러운 인터페이스를 유지하기 위한 초당 60 프레임).

느린 초기화가 모든 사람들에게 이슈가 되는 것이 아님을 언급할 가치가 있다. 하지만 당신이 당신의 코드를 정말 신경더라도 init() 메소드나 생성자에서 외부 라이브러리가 디스크/네트워크 작업을 수행할 가능성은 항상 존재한다. 만약 그것에 대해 확신이 서지 않는다면 Android 성능 통계 라이브러리인 AndroidDevMetrics를 사용해 보라. 애플리케이션의 특정 화면을 표시하기 위해 필요한 시간과 (만약 Dagger 2를 사용한다면) 의존성 그래프에서 각 객체를 제공하는데 소모된 시간이 얼마인지를 알려준다.

Producers는 불행히도 Android를 위해 디자인되지 않았고, 몇가지 결점들이 존재한다:

마지막 두 가지에 대해서는 우리가 할 수 있는게 많지 않지만, 첫번째 것은 Android 프로젝트에서 Producers를 위태롭게 한다.

RxJava를 사용한 비동기 주입

다행히도 많은 Android 개발자는 애플리케이션에서 비동기 코드를 만들기 위해 RxJava(와 RxAndroid)를 사용한다. 이것을 사용하여 Dagger2 비동기 주입을 해보자.

비동기 @Singleton 주입

여기 우리의 묵직한 객체가 있다:

@Provides
@Singleton
HeavyExternalLibrary provideHeavyExternalLibrary() {
    HeavyExternalLibrary heavyExternalLibrary = new HeavyExternalLibrary();
    heavyExternalLibrary.init(); //This method takes about 500ms
    return heavyExternalLibrary;
}

이제 이 코드를 비동기적으로 호출할 Observable<HeavyExternalLibrary> 객체를 반환하는 추가 provide…() 메소드를 만든다:

@Singleton
@Provides
Observable<HeavyExternalLibrary> provideHeavyExternalLibraryObservable(final Lazy<HeavyExternalLibrary> heavyExternalLibraryLazy) {
    return Observable.create(new Observable.OnSubscribe<HeavyExternalLibrary>() {
        @Override
        public void call(Subscriber<? super HeavyExternalLibrary> subscriber) {
            subscriber.onNext(heavyExternalLibraryLazy.get());
        }
    }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}

이를 줄 단위로 분석해보자:

우리 Observable은 다른 어떤 객체처럼 그래프에 주입된다. 그러나 객체 heavyExternalLibrary 그 자체는 나중에 사용할 수 있습니다:

public class SplashActivity {

	@Inject
	Observable<HeavyExternalLibrary> heavyExternalLibraryObservable;

	//This will be injected asynchronously
	HeavyExternalLibrary heavyExternalLibrary;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate();
		//...
		heavyExternalLibraryObservable.subscribe(new SimpleObserver<HeavyExternalLibrary>() {
            @Override
            public void onNext(HeavyExternalLibrary heavyExternalLibrary) {
	            //Our dependency will be available from this moment
	            SplashActivity.this.heavyExternalLibrary = heavyExternalLibrary;
            }
        });
	}
}

비동기로 신규 인스턴스 주입

위 코드는 singleton 객체를 삽입하는 방법을 보여준다. 새로운 인스턴스를 비동기적으로 주입하려면 어떻게 해야 할까?

우리 객체가 더 이상 @Singleton이 아님을 확인하라:

@Provides
HeavyExternalLibrary provideHeavyExternalLibrary() {
    HeavyExternalLibrary heavyExternalLibrary = new HeavyExternalLibrary();
    heavyExternalLibrary.init(); //This method takes about 500ms
    return heavyExternalLibrary;
}

우리의 Observable<HeavyExternalLibrary>는 싱글톤일 수 있지만 이것의 subscribe()를 호출할 때 마다, onNext() 호출에서 HeavyExeteranlLibrary의 새로운 인스턴스를 얻을 것이다:

heavyExternalLibraryObservable.subscribe(new SimpleObserver<HeavyExternalLibrary>() {
    @Override
    public void onNext(HeavyExternalLibrary heavyExternalLibrary) {
        //New instance of HeavyExternalLibrary
    }
});

비동기 주입 완결

Dagger2에서 RxJava를 사용하여 비동기 주입을 수행할 수 있는 또 다른 방법이 있다. Observable로 전체 주입 절차를 간단히 마무리 할 수 있다. 우리의 주입이 다음 방식으로 수행된다고 가정 해 보자(코드는 GithubClient 예제 프로젝트에서 가져왔다):

public class SplashActivity extends BaseActivity {

    @Inject
    SplashActivityPresenter presenter;
    @Inject
    AnalyticsManager analyticsManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    //This method is called in super.onCreate() method
    @Override
    protected void setupActivityComponent() {
        final SplashActivityComponent splashActivityComponent = GithubClientApplication.get(SplashActivity.this)
                .getAppComponent()
                .plus(new SplashActivityModule(SplashActivity.this));
        splashActivityComponent.inject(SplashActivity.this);
    }
}

이를 비동기로 만들기 위해서는 Observable로 setupActivityComponent() 메소드를 래핑하기만 하면 된다.

@Override
protected void setupActivityComponent() {
    Observable.create(new Observable.OnSubscribe<Object>() {
        @Override
        public void call(Subscriber<? super Object> subscriber) {
            final SplashActivityComponent splashActivityComponent = GithubClientApplication.get(SplashActivity.this)
                    .getAppComponent()
                    .plus(new SplashActivityModule(SplashActivity.this));
            splashActivityComponent.inject(SplashActivity.this);
            subscriber.onCompleted();
        }
    })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new SimpleObserver<Object>() {
                @Override
                public void onCompleted() {
                    //Here is the moment when injection is done.
                    analyticsManager.logScreenView(getClass().getName());
                    presenter.callAnyMethod();
                }
            });
}

설명했듯이 @Inject 어노테이션된 모든 객체는 미래 어느 시점에 주입될 것이다. 대신에 주입 절치는 비동기식이고 메인 스레드에 큰 영향을 미치지 않는다.

물론 Observable 객체와 subscribeOn()에 대한 추가 스레드들을 생성하는 것은 완전히 무료가 아니다 —약간의 시간이 걸리 것이다. 이는 Producers 코드에서 생성된 영향과 거의 비슷하다.

Thanks for reading!