안드로이드에서 쓰레드 사용기법에 대해서 잘 정리된 자료를 발견했습니다.


안드로이드에는 비동기 작업을 위한 여러 API 들이 제공되는데요, 각각의 차이점과 어떤 경우에 사용하면 좋은지에 대해서 깔끔하게 정리가 되어있네요.



Loader는 이 자료를 보면서 처음 알게되네요.


자료 출처 : http://www.slideshare.net/andersgoransson/efficient-android-threading  


신고
Posted by 에코지오

댓글을 달아 주세요

지난 번에는 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와 더불어 안드로이드 코딩을 굉장히 심플하게 만들어주는 AndroidAnnotations(AA) 오픈소스 라이브러리는 @Click 이라는 아주 유용한 어노테이션을 제공합니다. 

뷰의 ID가 설정된 어노테이션을 메소드에 선언해주면 설정된 뷰에 click 이벤트가 발생할 때 해당 메소드가 실행됩니다. 

기존에는  view.setOnClickListener(new OnClickListener() { ...}) 와 같은 OnClickListener 익명 클래스로 도배된 지저분한 코딩이 필요했었죠.


@Click(R.id.myButton)  

void myButtonClicked() { 

 ... ... // R.id.myButton을 누르면 이 메소드가 호출됩니다.

}  


@Click

void anotherButton(View clickedView) {

 ... ... // R.id.anotherButton 버튼을 누르면 이 메소드가 호출됩니다.

}


@Click({R.id.myButton1, R.id.myButton2})  

void myButtonsClicked() { 

 ... ... // R.id.myButton1 또는 R.id.myButton2를 누르면 이 메소드가 호출됩니다.

}  


AA를 사용하지 않는(사용하기 싫은?) 환경을 위해 비슷한 기능을 제공하는 우리만의 @Click을 만들어 보겠습니다. 

먼저 Click 어노테이션을 작성합니다. value는 클릭 이벤트가 발생하는 뷰의 id 값입니다.


@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Click {

    int[] value() default -1;

}


@Click 어노테이션을 선언하는 메소드는 아래의 제약사항을 준수해야 합니다(AA의 @Click도 마찬가지입니다).

- 리턴 타입은 void만 가능

- 파라미터는 없거나 View 타입의 1개만 가능

- Exception을 던지는 것이 가능하지만 메소드 caller한테는 throw되지 않음 (AA에서는 아예 checked Exception도 못던집니다).


그리고 공통 액티비티 클래스에서 android.app.Activity의 onContentChanged() 메소드를 오버라이딩합니다.


    /**

     * 어노테이션에 설정된 뷰에 대해 클릭 이벤트 발생시 해당 메소드가 실행되도록 리스너 등록.

     *

     */

    @Override

    protected void onContentChanged() {

        super.onContentChanged();

        processClick(this);

    }


    public static void processClick(final Activity activity) {

        Class clazz = activity.getClass();


        for (final Method m : clazz.getDeclaredMethods()) {

            Click click = m.getAnnotation(Click.class);//m.isAnnotationPresent(Click.class)

            if (click == null)

                continue;


            Class<?>[] params = m.getParameterTypes();

            final boolean hasViewParameter = (params.length > 0);


            int[] ids = click.value();

            for (int id : ids) {

                View view = activity.findViewById(id);

                if (view != null) {

                    view.setOnClickListener(new OnClickListener() {

                        public void onClick(View view) {

                            try {

                                if (hasViewParameter) {

                                    m.invoke(activity, view);

                                } else {

                                    m.invoke(activity);

                                }

                            } catch (IllegalAccessException e) {

                                throw new RuntimeException(e);

                            } catch (InvocationTargetException e) {

                                throw new RuntimeException(e);

                            }

                        }

                    });

                }

            }


        }

    }


다 됐습니다. 공통 액티비티를 상속받은 액티비티에서 AA의 그것과 동일한 기능을 수행하는 @Click 어노테이션을 사용할 수 있습니다. 간단하죠?


※ 단, AndroidAnnotations의 @Click과 차이점이 몇가지 있습니다.

- AA에서는 setContentView()에 @Click 처리코드가 작성되지만, 위에서는 onContentChanged()에서 @Click을 처리하도록 했습니다. 

- AA의 @Click은 뷰의 id가 주어지지 않은 경우 메소드 이름으로 뷰 id를 자동으로 찾지만 위 코드는 그러한 기능은 제공하지 않습니다.

- AA에서는 컴파일타임에 Java Annotation Processing에 의해 @Click 처리 코드가 생성되지만 위 코드는 런타임에 @Click이 붙은 메소드를 이벤트리스너를 등록합니다.



신고
Posted by 에코지오

댓글을 달아 주세요

  1. 양희영 2015.04.14 10:57 신고  댓글주소  수정/삭제  댓글쓰기

    오 좋은정보 감사합니다



티스토리 툴바