1. 우리가 작성한 코드에 대해서만 위빙이 가능

 AOP에서 위빙하는 방식은 시점에 따라 컴파일타임 위빙과 런타임 위빙으로 나눌 수 있습니다. 

안드로이드 Dalvik VM(.class 를 변형한 .dex 포맷을 사용)은 아직까지 런타임 바이트코드 생성을 지원하지 않습니다. 따라서 안드로이드에서는 런타임 위빙을 사용할 수 없고, 컴파일타임 위빙만 가능합니다(AspectJ는 컴파일 타임 위빙을 지원합니다).

우리가 작성한 소스코드에 대해서만 인터셉트할 수 있으며, 안드로이드 프레임워크나 그외 라이브러리에 대한 어드바이스는 작동하지 않습니다.


예들들어 Activity 클래스의 onCreate() 메소드에 대한 execution() 포인트컷은 작동하지만, 반면에 call() 포인트컷은 작동하지 않습니다. 왜냐면 onCreate() 메소드를 호출하는 클래스는 수정불가한 안드로이드 프레임워크에 속하기 때문입니다.


그리고 before나 after 같은 어드바이스를 통해 우리가 작성한 Activity 클래스의 예들들어 onResume() 라이프사이클 메소드에 로직을 추가하기  위해서는 반드시 해당 액티비티 클래스에 onResumE() 메소드 "코드"가 존재해야합니다. 비록 액티비티가 상속받은 android.app.Activity 클래스에 이미 onResume()  메소드가 존재하지만, 런타임이 아닌 컴파일 타임에 위빙이 수행되므로 어드바이스가 적용되기 바란다면 onResume() 메소드를 오버라이드 해주어야 합니다.



2. thisJointPoint 를 사용하기 위한 이클립스 설정

어드바이스 안에 thisJointPoint 키워드를 사용하는 코드가 존재하면 컴파일은 되지만 실제로 런타임에 아래와 같이 AspectJ 클래스를 못찾는다는 에러가 발생합니다.


Caused by: java.lang.NoClassDefFoundError: org.aspectj.runtime.reflect.Factory


이는 AJDT 플러그인에 의해 빌드패스에 추가된 'AspectJ Runtime Library' 라이브러리(aspectjrt.jar) 파일이 최종 dex 파일에 포함되지 않아 발생하는 문제입니다. 프로젝트 빌드패스 설정창의 "Order and Export" 탭에서 "AspectJ Runtime Library" 항목을 체크해주면 해결됩니다.


그러나 여전히 Ant 빌드(build.xml)를 통해 빌드를 시도하면 NoClassDefFoundError 에러가 발생합니다. build.xml을 수정해줄 수도 있지만 그냥 aspectjrt.jar를 libs 폴더에 넣어두면 간단히 해결됩니다. libs에 존재하는 jar 파일은 "Android Dependencies " 형태로 빌드패스에 자동으로 추가되므로 AJDT에 의해 추가된 'AspectJ Runtime Library'는 더이상 필요없습니다.


결론적으로  안드로이드 라이브러리('Android 2.3.x' 같은) 변수와 'Android Dependencies' 라이브러리 변수를 제외한 모든 외부 라이브러리 변수는 제거하고 필요한 모든 외부 jar 파일은 libs 폴더에 넣어두는 것이 좋습니다.


Posted by 에코지오
,

안드로이드에서 AspectJ를 사용하기 위해 이클립스 및 Ant빌드 환경을 구성하는 방법을 설명합니다.


1. AspectJ 설치

AspectJ 홈페이지(http://www.eclipse.org/aspectj/)에서 AspectJ 컴파일러를 다운로드 받아 설치합니다.

저는 개발버전인 AspectJ 1.7을 받아서 D:/Compiler/aspectj1.7 경로에 설치했습니다.

설치는 다운받은 jar 파일을 실행하면 됩니다. 


> java -jar aspectj-1.7.0.M1.jar


2. 이클립스 AJDT 플러그인 설치

이클립스 AspectJ 개발 플러그인인 AJDT를 설치합니다. http://www.eclipse.org/ajdt/downloads/ 

(이클립스 메뉴에서 Help > Install New Software... 를 선택한 후 설치 주소에 업데이트 주소 입력)

저는 개발버전인 AJDT 2.2.x를 설치했습니다. http://download.eclipse.org/tools/ajdt/37/dev/update


3. 클래스패스에 aspectrt.jar 파일 추가

aspectrt-1.7.jar 파일을 안드로이드 프로젝트의 libs 폴더에 넣어줍니다. ADT(저의 경우는 r17을 쓰고 있습니다)가 libs 폴더의 모든 jar 파일들을 자동으로 'Android Dependencies' 라이브러리에 추가해줍니다.


4. 프로젝트에 AspectJ 특성 추가

프로젝트 오른 클릭 >  Configures > Convert to AspectJ Project 를 선택하여 AspectJ 특성을 추가합니다. 그러면 프로젝트 빌더에 AspectJ Builder가 추가되고 Java Builder는 제거되며, 빌드패스에 'AspectJ Runtime Library'가 자동으로 추가됩니다.

그러나 Java Builder가 제거되면 이클립스 Problems 뷰 및 Tasks 뷰에 기존에 보였던 컴파일 에러라든가 TODO 목록이 안보이게 되므로 .project 파일에   아래와 같이 다시 Java Builder를 강제로 추가해줍니다.


  <buildSpec>

    <buildCommand>

      <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>

      <arguments>

      </arguments>

    </buildCommand>

    <buildCommand>

      <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>

      <arguments>

      </arguments>

    </buildCommand>

    <buildCommand>

      <name>org.eclipse.wst.common.project.facet.core.builder</name>

      <arguments>

      </arguments>

    </buildCommand>

    <buildCommand>

      <name>org.eclipse.jdt.core.javabuilder</name>

      <arguments>

      </arguments>

    </buildCommand>

    <buildCommand>

      <name>org.eclipse.ajdt.core.ajbuilder</name>

      <arguments>

      </arguments>

    </buildCommand>

    <buildCommand>

      <name>com.android.ide.eclipse.adt.ApkBuilder</name>

      <arguments>

      </arguments>

    </buildCommand>

  </buildSpec>


그리고  'AspectJ Runtime Library' 라이브러리 변수는 제거해줍니다.


5. aspect 작성

예를들어 src/my/app/aop/MyFirstAspect.aj 파일을 만들고 포인트컷과 어드바이스 등을 코딩해줍니다.


AspectJ 코딩 방법은 다음사이트를 참고합니다.


http://dev.anyframejava.org/anyframe/doc/core/3.2.1/corefw/guide/aop-based-aspectj.html

http://blog.daum.net/oraclejava/15858189



개발시점에서 aspect 소스 컴파일은 AJDT로 충분하지만 릴리스용 빌드생성 또는 자동빌드를 위해서는 Ant 빌드(build.xml)을 이용하는 것이 편리합니다. (이하 Android SDK r17 버전 기준 설명입니다)


AspectJ 사용을 위한 Ant 빌드 설정

(1) local.properties 파일에 아래내용을 추가합니다.


# AspectJ 컴파일러 홈

aspectj.home=D:\\Compiler\\aspectj1.7


(2) project.properties 파일에 아래내용을 추가합니다.


# AspectJ 소스 리스트 파일 경로

aspectj.src.list=aspect-list.txt


(3) custom_rules.xml 파일을 아래의 내용으로 새로 작성합니다.(기존에 파일이 존재한다면 내용을 추가)


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

<project name="custom_rules" default="-post-compile">


  <!-- AspectJ 컴파일 추가 -->

  <taskdef resource="org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties">

   <classpath>

     <pathelement location="${aspectj.home}/lib/aspectjtools.jar" />

   </classpath>

  </taskdef>

  <!-- iajc 타스크 설명 : http://www.eclipse.org/aspectj/doc/released/devguide/antTasks.html -->

  <!-- sourceRoots, inpath, aspectpath에 대한 설명 : http://www.jroller.com/tmjee/entry/iajc_usage -->

  <target name="-post-compile">

    <echo message="Weaving aspects to .class files before dex converts .class files to .dex file" />

   <!-- aspectpathref : 라이브러리에 속한 애스펙트도 적용할 경우 설정 -->

    <iajc destDir="${out.classes.absolute.dir}"

          bootclasspathref="android.target.classpath"

          classpathref="project.libraries.jars"

          classpath="${aspectj.home}/lib/aspectjrt.jar"

          sourceroots="${source.absolute.dir}"

          inpath="${out.classes.absolute.dir}"

          aspectpathref="project.libraries.jars"

          Xlintwarnings="true"

          showWeaveInfo="true"

          encoding="${java.encoding}"

          source="${java.source}"

          target="${java.target}">

    </iajc>

  </target>


</project>


만약 프로젝트 유형이 안드로이드 '라이브러리' 프로젝트라면 아래와 같이 -post-compile 타겟을 정의합니다.


    <echo message="Weaving aspects to .class..." />

    <iajc destDir="${out.classes.absolute.dir}"

          bootclasspathref="android.target.classpath"

          classpathref="project.libraries.jars"

          classpath="${aspectj.home}/lib/aspectjrt.jar"

          Xlintwarnings="true"

          showWeaveInfo="true"

          encoding="${java.encoding}"

          source="${java.source}"

          target="${java.target}">


      <sourceroots>

        <pathelement location="${source.absolute.dir}" />

        <pathelement location="${gen.absolute.dir}" />

      </sourceroots>


      <!--

       inpath="${out.classes.absolute.dir}"

       => 이 옵션을 적용하면 앱에서 라이브러리의 애스펙트도 적용하도록 

            설정한 경우 bad WeaverState.Kind: -115 에러 발생함

      -->

    </iajc>


    <!-- 라이브러리 프로젝트인 경우 -compile 타겟에서 생성된 classes.jar를 위빙된 클래스로 교체 -->

    <if condition="${project.is.library}">

      <then>

        <echo>Overwrite library output jar file with weaved classes...</echo>

        <jar destfile="${out.library.jar.file}" update="false">

          <fileset dir="${out.classes.absolute.dir}"

                   includes="**/*.class"

                   excludes="${manifest.package.path}/R.class ${manifest.package.path}/R$*.class ${manifest.package.path}/Manifest.class ${manifest.package.path}/Manifest$*.class ${manifest.package.path}/BuildConfig.class" />

          <fileset dir="${source.absolute.dir}"

                   excludes="**/*.java ${android.package.excludes}" />

        </jar>

      </then>

    </if>




(4) proguard-project.txt에 다음 내용을 추가합니다.


# aspect 클래스 및 aspect가 적용되는 클래스에서 AspectJ 라이브러리를 참조할 수 없다는 경고 제거

# (can't find referenced class)

-dontwarn org.aspectj.**


# 패키지 변경 금지(주석해제시 런타임에 java.lang.NoSuchMethodError 에러 발생)

#-repackageclasses ''

#-allowaccessmodification


# AspectJ 클래스 보존

-keep class org.aspectj.**


# Aspect 클래스 및 멤버 보존

-keep @org.aspectj.lang.annotation.Aspect class * { *; }

-keepclasseswithmembers class * {

    public static *** aspectOf();

}


# around 어드바이스가 적용되는 target 클래스에서 around 어드바이스 메소드를

# 참조할 수 없다는 경고 제거(can't find referenced method) : aspect 클래스를 지정

-dontwarn my.app.aop.**

(또는 -dontwarn my.app.**.*Aspect 식으로 설정)


(5) aspect 소스 리스트 파일 작성

aspectj.src.list 속성으로 정의한 aspect-list.txt 파일을 생성하고, aspect 소스 리스트를 한줄에 하나씩 나열합니다.

경로는 프로젝트 루트에 대한 상대경로입니다.


src/my/app/aop/MyFirstAspect.aj

src/my/app/aop/MySecondAspect.aj

...


Posted by 에코지오
,

안드로이드에 AOP를 적용할 수 있는지 구글 형님께 알아보니 가능하다는 답변이 나옵니다.

=> http://blog.punegtug.org/2010/11/adding-aspect-to-android.html


1. 그러나 안드로이드는 런타임 바이트코드 생성을 지원하지 않기 때문에 컴파일타임 위빙만 가능합니다.

=> http://stackoverflow.com/questions/3759232/aspect-oriented-programming-in-android

2. 구글 Guice는 메소드 인터셉터 같은 일부 AOP 기능을 제공하는데

  이는 런타임 바이트코드 생성 방법을 사용하므로 안드로이드에서는 사용할 수 없습니다.

=> http://code.google.com/p/google-guice/wiki/OptionalAOP

3. 어쨋든 안드로이드에서도 컴파일 타임 위빙을 통한 AOP는 가능하기 때문에

  AspectJ 라이브러리를 이용해 AOP 프로그래밍을 할 수 있습니다.

=> http://deansserver.co.uk/~dean/2011/07/18/aspect-oriented-android-development-tool-integration/
=> http://code.google.com/p/android-aspectj/


4. 그럼 안드로이드에서 AOP를 어디에 활용할수 있을까요?

(1) 공통 에러 처리

- 발생된 Exception을 잡아서 에러로그를 서버로 보내 앱 유지보수에 참고할 수 있습니다.

  (물론 에러정보를 서버로 전송할지 사용자에게 물어야겠죠).

- Exception 발생시 안드로이드는 안드로이드 고유의 에러 창과 함께 앱프로세스가 죽어버리는데,

  이렇게 하기보다는 좀더 우아한 메시지와 함께 에러창을 꾸밀 수 있습니다.

- 또한 앱프로세스가 종료되지 않게 하거나 에러가 발생한 Activity만 종료하게 할 수 있을 것입니다.

  (앱을 종료할지 말지는 에러정책에 따라 달라질 것입니다)


(2) 개발을 위한 로깅

- 개발시 편의를 위해 디버그용 로그를 모든 Activity의 라이프사이클 메소드에 추가할 수 있습니다.

- Activity의 UI 렌더링 속도를 측정하기 위해 onCreate() 실행 전과 onResume() 실행 후의 경과시간을 계산할 수 있습니다.


(3) 아키텍처 위반 체크

- 예를 들어 레이어링 아키텍처를 갖도록 설계된 앱에서 아키텍처를 깨는 호출이 존재하는 경우 아예 컴파일이 안되게 처리할 수 있습니다.


(4) Activity 라이프사이클 메소드에 기능 추가

- 예들들어(이건 정말 그저 예입니다) 로그인이 포함된 앱에서 앱 자체의 세션 타임아웃을 체크하여 세션이 만료된 경우 로그인 Activity로 이동하게 할 수 있습니다. AOP를 이용하여 로그인 Activity를 제외한 다른 모든 Activity의 onCreate()나 onResume() 메소드에 세션타임아웃 체크 로직을 추가하면 됩니다.  단 이 경우 반드시 해당 메소드 "코드"가 액티비티에 존재해야합니다(왜냐면 안드로이드에서는 컴파일타임 위빙만 가능하기 때문)

- 또다른 예로 카톡같은 화면잠금을 생각할 수 있습니다. 모든 Activity의 onResume()과 onPause()에 화면잠금 로직을 쉽게 추가할 수 있습니다.


(5) ... ...



Posted by 에코지오
,