지난 번에는 AndroidAnnotations(AA) 오픈소스 라이브러리의 @Click 어노테이션을 흉내내봤습니다. 이번에는 @Background@UiThread 어노테이션을 흉내내보죠.


먼저 @Background와 @UiThread 어노테이션 클래스를 작성합니다.


(1) Background.java

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;


/**

 * <pre>

 * 메소드를 백그라운드 쓰레드에서 실행해주는 어노테이션.

 *

 * ※ 메소드 제약사항

 * - 메소드 리턴타입은 void만 가능

 * - Exception을 던지는 것이 가능하지만 에러정책에 따라 메소드 caller한테는 throw되지 않을 수 있음

 *

 * ※ NOTE

 * - 이 어노테이션은 작업진행상태 스핀다이얼로그 표시, 작업취소 처리, 작업 콜백메소드 등의 기능을 지원하지 않는다.

 * </pre>

 */

@Documented

@Retention(RetentionPolicy.CLASS)

@Target(ElementType.METHOD)

public @interface Background {

}


(2) UiThread.java

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;


/**

 * <pre>

 * 메소드를 UI 쓰레드에서 실행해주는 어노테이션.

 *

 * ※ delay

 * - delay 파라미터 설정시 해당 밀리초 이후에 실행된다. 기본값은 0이다.

 *

 * ※ 메소드 제약사항

 * - 메소드 리턴타입은 void만 가능

 * - Exception을 던지는 것이 가능하지만 에러정책에 따라 메소드 caller한테는 throw되지 않을 수 있음

 * </pre>

 */

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface UiThread {

    long delay() default 0;

}


자 이제 위 어노테이션이 붙은 메소드를 백그라운드 쓰레드나 UI쓰레드에서 실행시키기 위해서 AOP(AsepctJ)의 힘을 빌려봅니다.


import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;


/**

 * <pre>

 * {@link Background}, {@link UiThread} 어노테이션이 붙은 메소드가

 * 백그라운드 및 UI쓰레드에서 실행되도록 애스펙트로 처리한다.

 * </pre>

 */

@Aspect

public class BackgroundAnnotationAspect {


    private Handler uiHandler = new Handler(Looper.getMainLooper());


    private Executor threadExecutor = Executors.newCachedThreadPool();


    @Around("execution(@com.mycompany.async.Background * *.*(..))")

    public void proceedInBackground(final ProceedingJoinPoint pjp) {

        threadExecutor.execute(new Runnable() {

            @Override

            public void run() {

                try {

                    pjp.proceed();

                } catch (final Throwable e) {

                    // 에러정책에 따라 적절히 에러 처리

                }

            }

        });

    }


    @Around("@annotation(uithread) && execution(@com.mycompany.async.UiThread * *.*(..))")

    public void proceedInUiThread(final ProceedingJoinPoint pjp, UiThread uithread) {

        runInUiThread(new Runnable() {

            @Override

            public void run() {

                try {

                    pjp.proceed();

                } catch (Throwable e) {

                    // 에러정책에 따라 적절히 에러 처리

                }

            }

        }, uithread.delay());

    }



    /**

     * {@link Runnable}을 UI쓰레드에서 실행한다.

     *

     * @param run

     * @param delayMillis 지연시간. 밀리초

     */

    private void runInUiThread(Runnable run, long delayMillis) {

        if (delayMillis <= 0) {

            // 현재 쓰레드가 UI 쓰레드인 경우 그냥 run 실행.

            if (Looper.getMainLooper().getThread() == Thread.currentThread()) {

                run.run();

                return;

            }


            uiHandler.post(run);

            return;

        }


        uiHandler.postDelayed(run, delayMillis);

    }

}


다 됐습니다. 아래처럼 쓰면 됩니다. (꼭 액티비티 내의 메소드에 국한되지 않고 아무 클래스에서든 사용가능합니다)


public class TestAsyncActivity extends Activity {

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

        Button test = ....;
        test.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                doBackground();
            }
        });
    }

    @Background

    private void doBackground() {

        URL url = new URL("http://mycompany.com/xxx");

        URLConnection conn = url.openConnection();

        byte[] contents = FileCopyUtils.copyToByteArray(conn.getInputStream());

        doUiThread(new String(contents));

    }


    @UiThread

    private void doUiThread(String contents) {

        Toast.makeText(getApplicationContext(), contents, Toast.LENGTH_LONG).show();

    }


유용하게 쓰시길 바랍니다.



신고
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 에코지오

댓글을 달아 주세요

* agimatec-validation  http://code.google.com/p/agimatec-validation/

JSR 303: bean-validation 스펙을 구현한 라이브러리. 아래는 agimatec-validation 사용예제.

javax.validation.Validator customerValidator = new ClassValidator(Customer.class);
Set<InvalidConstraint<Customer>> violations = customerValidator.validate(customer);
public class Customer{
    @NotEmpty(groups = "last")
    private String firstName;
    @NotEmpty(groups = "first")
    private String lastName;
    @Length(max = 30, groups = "last")
    private String company;
    @Valid
    private List<Address> addresses;
   
    ....
}

그 밖에 다음 글도 참고할 것.

- Bean Validation Sneak Peek http://in.relation.to/Bloggers/BeanValidationSneakPeekPartI
- 기선님 Spring MVC Validation 정리(스프링 validator와 valang)
- 기선님 Really easy field validation 사용하기(이건 css를 통해 브라우저단에서 입력값을 검증하는 것)
- 스트러츠2에서 어노테이션방식 입력값검증 http://struts.apache.org/2.x/docs/validation-annotation.html
- 하이버네이트 Validator http://www.hibernate.org/hib_docs/validator/reference/en/html/validator-defineconstraints.html
- springmodules https://springmodules.dev.java.net/docs/reference/0.8/html/validation.html#d0e9699
신고
Posted by 에코지오

댓글을 달아 주세요



티스토리 툴바