AndroidAnnotations는 어떤 객체에 대한 주입(injection)이 완료된 이후에 코드를 실행할 수 있는 @AfterInject 어노테이션을 제공합니다. 

그리고  GuiceyFruitMycilaGuice 같은 라이브러리도 인젝션에 대한 콜백을 받을 수 있는 JSR250의 @PreDestroy, @PostConstruct 어노테이션을 구현하여 제공합니다.

그러나 대표적인 안드로이드용 Injection 프레임워크인 RoboGuice는 객체가 생성되고 필드에 대한 주입이 모두 끝난 이후 지정된 메소드(ex. 초기화 메소드)를 실행하는 깔끔한 방법을 제공하지 않습니다. 


다행히 방법이 전혀 없는 것은 아닙니다. 2가지 방법이 존재합니다.


1. InjectionListener를 이용

유연한 방법은 아니지만 Guice의 InjectionListener를 구현하여 주입이후에 지정된 메소드를 실행하게 할 수 있습니다.

(아래 코드에서는 MyInitClass 객체에 대한 모든 주입이 끝나고 나서 init 메소드를 실행합니다)


bindListener(Matchers.subclassesOf(MyInitClass.class), new TypeListener() {

    @Override

    public <I> void hear(final TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {

        typeEncounter.register(new InjectionListener<I>() {

            @Override

            public void afterInjection(Object i) {

                MyInitClass m = (MyInitClass) i;

                m.init();

            }

        });

    }

});



2. @Inject를 이용

Guice는 필드에 대한 모든 주입을 끝내고 나면 @Inject가 붙은 메소드를 실행한다고 합니다. 


    @Inject

    protected void init() {

       ... ...

    }


이 방법은 약간의 트릭에 해당하지만 매우 간단하기 때문에 저는 이 방법을 사용합니다. 자세한 내용은 다음 링크를 참고하세요.


http://stackoverflow.com/questions/2093344/guice-call-init-method-after-instantinating-an-object



Posted by 에코지오
,

RoboGuice 사용시에 클래스 타입만으로는 주입받을 모듈을 구분하기 어려운 경우가 있습니다. 

예들들어 시간이 오래걸리는 작업을 처리하는 동안 사용자에게 진행중임을 알려주는 다이얼로그가 필요해서 아래처럼 Dialog를 주입받는 코드를 작성했습니다.


@Inject

protected Dialog progressDialog;


위 코드에서 우리가 원하는 것은 작업이 진행중임을 알려주는 Dialog를 주입시키는 것입니다. 그러나 다른 모듈에서는 다른 모양의 Dialog가 주입되어야 하는데 저 Dialog 타입만으로는 그것을 구분할 수 없습니다.


이런 상황에서는 추가적인 정보를 제공하는 어노테이션을 이용하면 됩니다. 위 예제의 경우 먼저 @TaskProgressDialog와 같은 어노테이션을 만들어 추가해줍니다.  


@Inject

@TaskProgressDialog

protected Dialog progressDialog;


그리고 Guice 모듈설정에서 AnnotatedBindingBuilder.annotatedWith() 메소드를 통해 해당 어노테이션이 붙은 놈에 적절한 모듈을 주입합니다.


protected void configure() {

  bind(Dialog.class).annotatedWith(TaskProgressDialog.class).toProvider(TaskProgrerssDialogProvider.class);

  ... ...

}


이렇게 하면 Dialog 타입이면서 @TaskProgressDialog 어노테이션이 붙어 있는 놈한테만 TaskProgressDialogProvider가 생성한 Dialog 객체가 주입될 것입니다.


* 참고로, Guice는 문자열을 통해 어노테이션을 구분할 수 있는 @Named 어노테이션을 제공합니다. 그러니까 필요시마다 TaskProgressDialog 같은 별도의 어노테이션 클래스를 만들 필요가 없다는 얘기입니다. 단, 문자열이 이용되므로 보조적인 수단으로만 사용을 권장합니다.


public class RealBillingService implements BillingService {

  @Inject

  public RealBillingService(@Named("Checkout") CreditCardProcessor processor,

      TransactionLog transactionLog) {

    ...

  }


Guice 모듈설정에서는 Names.named() 를 이용합니다.


bind(CreditCardProcessor.class).annotatedWith(Names.named("Checkout")).to(CheckoutCreditCardProcessor.class);






Posted by 에코지오
,

RoboGuice 사용환경에서 AspectJ의 aspect 클래스에 모듈을 injection하는 방법입니다.



방법1. static 방식으로 injection


(1) 애스펙트 클래스에서 static 멤버변수로 선언

주입할 모듈을 static 멤버변수로 선언합니다.


 @Inject protected static ContextScopedProvider<T> tProvider; // T는 @ContextSingleton이어야함

 @Inject protected static ExceptionHandler exceptionHandler;//Singleton


(2) Guice 모듈 설정에서 static injection 처리

configure() 메소드에서 requestStaticInjection 메소드를 통해 주입합니다.


requestStaticInjection(ExceptionHandlingAspect.class);


그러나 이 방법은 권장하지 않습니다. Guice에서도 static injection은 deprecated될 것이라고 합니다.

http://code.google.com/p/google-guice/wiki/AvoidStaticState



방법 2. aspect 객체를 구하여 injection


aspect는 AspectJ에 의해 인스턴스화되며 우리가 직접 생성할 수 없습니다. 대부분의 경우 aspect는 싱글턴 객체이며, AspectJ는 싱글턴 aspect 객체를 참조할 수 있는 Aspects.aspectOf() 메소드를 제공합니다.


(1) 주입할 모듈을 애스펙트 클래스에 보통의 멤버변수로 선언


 @Inject protected ExceptionHandler exceptionHandler;


(2) Guice 모듈 설정에서 requestInjection을 통해 injection 처리


requestInjection(org.aspectj.lang.Aspects.aspectOf(ExceptionHandlingAspect.class));


참고:

Posted by 에코지오
,

AndroidAnnotations 라이브러리와 비교하여 RoboGuice의 단점으로 지적되고 있는 사항을 해결하기 위한 방안입니다.



런타임시에 리플렉션(reflection) 사용에 따른 성능하락

- 스마트폰 하드웨어 성능이 점점 강해지고 있으므로 앞으로 큰 문제는 안될 것임

  또한 Dalvik VM의 리플렉션 성능이 점점 좋아지고 있음

- 게임처럼 대부분의 기능이 스마트폰 내에서 처리되는 앱이라면 문제될 수 있음

  그러나 주로 네트워크를 통해 서버측에서 결과를 받아 단순히 표출하는 클라이언트 앱의 경우에는 

  응답시간의 대부분은 네트워크 소요시간이 차지하며, 폰 로컬의 리플렉션 처리가 차지하는 시간은 매우 작을 것으로 판단됨. 

  따라서 클라이언트 앱에서 RoboGuice의 런타임 리플렉션은 큰 문제가 아님.



앱 용량이 많이 커지는 문제

- RoboGuice를 사용하기 위해 필요한 guice-3.0.jar 파일과 roboguice-2.0.jar 파일의 크기를 합치면 거의 820KB임.

- 반면, androidannotations-2.5.jar 파일은 53KB에 불과함

- 그러나 실제로 apk의 용량이 820KB만큼 커지는 것은 아니며 ProGuard를 적절히 설정하면 apk 파일 크기가 생각보다 많이 늘어나지 않음



RoboActivity 상속에 따른 Activity 클래스의 유연성 부족

- AndroidAnnotations에는 Activity에 @EActivity 어노테이션을 선언만하면 되기 때문에 Activity가 다른 액티비티를 상속받는 것이 가능함

- 그러나 RoboGuice에서는 RoboActivity를 상속받아야 하므로 다른 액티비티를 상속받을 수 없음

- 따라서 AOP를 통해서 다른 액티비티를 상속받지 않고도 기능을 추가할 수 있는 방법을 적용하는 것을 고려



유틸리티성 어노테이션 부족

- AndroidAnnotations는 @ViewById 같은 injection용 어노테이션 외에 @Click, @Rest, @Background, @UiThread 등의 다양한 유틸리티성 어노테이션을 제공함

- 반면 RoboGuice는 코드를 줄여주는 이러한 유틸리티가 별로 없음

- AndroidAnnotations 소스를 참고하여 유틸리티성 어노테이션을 처리하는 AOP 애스펙트를 개발하여 적용하면 됨(별로 어렵지 않음)

- @Click : (공통) 액티비티의 setContentView() 메소드에 after 어드바이스로 처리

- @Background, @UiThread : 메소드에 around 어드바이스로 처리

- @Rest : ... 고민중...






Posted by 에코지오
,

이클립스 프로젝트에 RoboGuice(RG) 2.0 설치하기

- 안드로이드프로젝트의 libs에 roboguice-2.0.jar, guice-3.0-no_aop.jar, javax.inject-1.jar 파일을 넣고 빌드패스에 추가

- RG 2.0에서는 RoboApplication 상속받은 Application을 작성해서 AndroidManifest.xml에 등록할 필요는 없음

  


Activity 작성

- RoboActivity 상속받은 Activity 작성

- @InjectView , @Inject 등의 어노테이션 사용



모듈 주입 커스터마이징

- AbstractModule 클래스를 상속받는 클래스 작성

- configure() 메소드에서 커스텀 바인딩 정의 : 자세한 구현은 Google Guice 문서 참조

- res/values/roboguice.xml에 아래처럼 컴스텀 모듈 등록


<?xml version="1.0" encoding="utf-8"?>

  <resources> 

    <string-array name="roboguice_modules">

    <item>com.mypackage.MyModule</item>

  </string-array>

</resources>




인스턴스 스코프

RG에 의해 생성 및 관리되어 다른 클래스에 주입되는 클래스의 인스턴스 스코프(생명주기) 설정


1. Singleton 스코프

- 앱에서 하나의 인스턴스만 존재

- 클래스에 @Singleton 어노테이션 선언 : Guice에서 제공

- 어떤 클래스에서 이 객체를 요청하든 동일한 인스턴스가 제공됨. 그러므로 쓰레드세이프하게 작성해야 함.

- Application 컨텍스트는 직접 주입 받을 수있음(특정 Activity, Service 등 current context랑 관계없는 global context)

- current context(액티비티 컨텍스트)를 주입받으려면 Provider<Context>를 사용해야 함

- 싱글턴 스코프를 가진 기본제공 모듈 : Application, ViewListener, Resources 등?

=> 그러나 RG에서 제공하는 대부분의 모듈은 context를 필요로 하므로 ContextSingleton 스코프를 갖는다.

- current context가 필요없거나 또는 Application 컨텍스트만 필요한 클래스에 적합


2. ContextSingleton 스코프

- current context(액티비티 컨텍스트)에 대해서 하나의 인스턴스만 존재

- 클래승 @ContextSingleton 어노테이션 선언 : RG에서 제공하는 스코프

- current context가 필요한 클래스는 ContextSingleton으로 선언

- 동일 액티비티 객체에 대해서 동일 인스턴스 제공(모든 액티비티에 대해서 동일 인스턴스 제공은 아님)

- Application, Context, Activity, Service 등 @Singleton/@ContextSingleton 클래스를 직접 주입받을 수 있음

- 컨텍스트싱글턴 스코프를 가진 기본제공 모듈 : Context, Activity, Service, AssetManager, ContentResolver,

   LayoutInflator 및 기타 시스템서비스 모듈들

- current context가 필요한 클래스에 적합


3. 프로토타입 스코프

- 요청할 때마다 새로운 인스턴스 생성

- 클래스에 별도의 스코프 어노테이션 선언 없음. (프로토타입이란 용어는 제가 스프링에서 따온 것임)

- 주입될 때마다 또는 provider.get() 할 때마다 새 인스턴스가 생성됨

- 스코프가 설정돼있지 않고 public 기본생성자가 존재하는 모든 클래스가 가능

- Handler처럼 Provider에서 get() 요청시마다 새로운 인스턴스를 리턴하는 클래스

- context가 필요없고 상태를 가진 클래스에 적합


스코프 지정 방법

 (1) 구현체 클래스에 @Singleton 붙임

 (2) @Provides 붙은 메소드에 @Singleton 붙임

 (3) bind().xxx().in(Singleton.class);


주요 기본제공 클래스의 스코프

http://code.google.com/p/roboguice/wiki/ProvidedInjections



어떤 컨텍스트를 주입받을 것인가 

- http://code.google.com/p/roboguice/wiki/InjectingContexts

- current context가 필요한게 아니라면 명시적으로 Application을 주입받아라

- Activity가 필요하다면 Activity를 주입받아라

- 잘 모르겠으면 Context를 주입받아라



Inject 되기 위한 생성자 조건

- 기본 생성자가 존재하거나 

- Inject가능한 파라미터로만 구성되고 @Inject 선언된 생성자가 존재


  @Inject

  SomeClass(Context context, MyComponent component) {}

  // 여기서 context와 component는 inject 가능해야함...



일반 POJO 클래스에 다른 객체들을 주입하는 방법

1. POJO 생성을 RG가 처리하는 경우 

- POJO 내부의 @Inject로 선언된 멤버들은 자동 주입됨

- POJO 클래스가 RoboActivity를 상속받는 액티비티의 @Inject 멤버변수로 선언돼있는 경우 등


2. POJO 생성을 RG가 처리하지 않는 경우(개발자 또는 라이브러리에서 직접 생성하는 경우)

- POJO 생성자 등에서 RoboGuice.injectMembers(context, this)를 이용하여 수동으로 주입함



명시적인 injector 사용을 피하는 방법(생성자 등 초기화 메소드를 핸들링할 수 없는 경우)

- 클래스 또는 Provider, ContextScopedProvider를 static하게 inject 받음

- http://code.google.com/p/roboguice/wiki/HowToInjectXYZ

 

 @Inject protected static ContextScopedProvider<T> tProvider; // T는 @ContextSingleton이어야함

 @Inject protected static ExceptionHandler;//singleton


=> 단, MyModule에서 requestStaticInjection(POJO.class)을 해주어야 함

=> 그러나 RG 2.0에서는 static injection을 비권장 ( http://code.google.com/p/roboguice/wiki/UpgradingTo20 )

=> static injection은 context에 액세스할 수 없는 클래스 및 static 메소드를 가진 헬퍼나 유틸 클래스에 적합할 듯.

(http://www.rosscode.com/blog/index.php?title=android-development-part-3-wiring-up-roboguice&more=1&c=1&tb=1&pb=1 )



Provider란 무엇인가

- 인스턴스 생성이 복잡한 경우 인스턴스를 만드는 방법을 제공

- 예를들어 new MyPojo()로 객체 생성후 myPojo.init() 등의 추가 작업한 인스턴스를 리턴

- MyPojo를 주입받는 모듈은 초기화 완료된 MyPojo 인스턴스를 받게 됨

- Module에서 @Provides 어노테이션을 쓰는 메소드를 작성해도 됨(Provider 인터페이스 구현안해도 됨)

- http://code.google.com/p/google-guice/wiki/ProvidesMethods




명시적/묵시적 바인딩

- 명시적 바인딩 : Module에서 bind().to(), bind().toProvider()로 바인딩하는 것

- 묵시적 바인딩 : 

   (1) 기본생성자나 inject가능한 생성자를 가진 구현 클래스가 존재하는 경우

   (2) @ImplementedBy를 통해 기본 구현체를 지정한 경우

   (3) @ProvidedBy를 통해 구현체 인스턴스를 생성해줄 Provider를 지정한 경우



AOP 애스펙트에도 RG의 inject가 가능한가

- 가능함. Aspects.aspectOf()를 통해 aspect 싱글턴을 구하여 Guice 모듈에서 강제 inject시켜줌.



Injection 이후에 지정된 메소드 호출

- Guice의 TypeListener를 이용하거나 GuiceyFruit 또는 MyciaGuice의 @PostConstruct를 사용

- http://stackoverflow.com/questions/2093344/guice-call-init-method-after-instantinating-an-object

- http://code.google.com/p/mycila/wiki/MycilaGuice

- http://code.google.com/p/guiceyfruit/




* 참고

RoboGuice 2.0 설정 방법 : http://code.google.com/p/roboguice/wiki/UpgradingTo20


Posted by 에코지오
,

1. RoboGuice 2.0

- RoboActivity의 onContentChanged() 메소드에서 View 주입(inject)


    @Override

    public void onContentChanged() {

        super.onContentChanged();

        RoboGuice.getInjector(this).injectViewMembers(this); //View 주입

        eventManager.fire(new OnContentChangedEvent());

    }



2. AndroidAnnotations 2.5

- 언더바 붙은 Activity(Java Annotation Processing에 의해 생성)의 setContentView() 메소드에서 View 주입 및 @Click 처리


    @Override

    public void setContentView(int layoutResID) {

        super.setContentView(layoutResID);

        afterSetContentView_();

    }



    private void afterSetContentView_() {

        //View 주입 코드

        mSaveBtn = ((Button) findViewById(id.save_btn)); 

        // ... ...


        // @Click 처리 코드

        {

            View view = findViewById(id.save_btn);

            if (view!= null) {

                view.setOnClickListener(new OnClickListener() {



                    public void onClick(View view) {

                        save();

                    }


                }

                );

            }

        }

        //... ...


        init();

        //... ...

    }


Posted by 에코지오
,

RoboGuice 2.x 사용 환경에서 ProGuard 설정에 추가해줄 내용입니다.

(참고 : http://code.google.com/p/roboguice/wiki/ProGuard)


# res/values/roboguice.xml 파일에 풀네임이 지정되는 클래스 보존

-keep class * extends com.google.inject.AbstractModule


# 참조할 수 없다는 경고 제거(can't find referenced method)

-dontwarn roboguice.**


# roboguice,guice 클래스 및 멤버 보존(필요에 따라 좀더 세밀하게 조정할 것)

-keep class roboguice.event.EventThread { *; }

#-keep public class roboguice.**

#-keep public class com.google.inject.**


# @Inject 붙은 생성자/필드/메소드 및 public 기본생성자 보존

-keepclassmembers class * {

    @com.google.inject.Inject <init>(...);

    @com.google.inject.Inject *;

    public <init>();

}


# There's no way to keep all @Observes methods, so use the On*Event convention to identify event handlers

-keepclassmembers class * {

    void *(**On*Event);

}

Posted by 에코지오
,

안드로이드 코딩시 도움이 되는 대표적 오픈소스 프레임워크/라이브러리인 RoboGuice와 AndroidAnnotations에 대한 비교 메모입니다.


1. RoboGuice (RG)

- Injection 대상 : View, Resource, 시스템서비스, POJO 등

- 향상된 유틸리티 클래스 제공 : RoboAsyncTask, Ln 등

- 액티비티 생명주기 Event 전달을 통한 모듈화 메커니즘 제공 : 액티비티에 대한 강한 커플링을 제거.

- Activity는 RoboActivity를 상속받아야 함

- Activity가 아닌 클래스에서도 안드로이드 컴포넌트 inject 가능

- 구글 Guice에 의존 : 강력하고 충실한 inject 기능 제공

- 런타임시에 inject 및 어노테이션을 처리하므로 성능하락 존재

- 용량 큼 : 590KB



2. AndroidAnnotations (AA)

- Injection 대상 : View, Resource, 시스템서비스, POJO(제한적) 등

- 다양한 유틸리티성 어노테이션 제공. inject외에 다양한 작업을 어노테이션을 통해 처리

 : 쓰레드작업(UiThread, Background,..), 이벤트처리(Click, ItemClick,...), REST통신 등

- 컴파일 타임에 inject 및 어노테이션 처리한 자식클래스를 생성 : 런타임 성능하락 없음

- 특정 Activity 상속 필요없음

- java annotation processing 설정 필요

- 새 액티비티 클래스 작성후 Android Manifest.xml 수정이 필요 => 액티비티 클래스명 끝에 언더바를 추가해줘야함

 : 예를들어 ".xxx.MyActivity" -> ".xxx.MyActivity_" 

- 명시적으로 액티비티 사용시에 언더바 붙은 액티비티를 사용해야 함

- 용량 작음 : 53KB



* 참고자료

http://blog.springsource.org/2011/08/26/clean-code-with-android/

http://blog.softwaregeeks.org/archives/647

Posted by 에코지오
,