Showing posts with label AndroidAnnotations. Show all posts
Showing posts with label AndroidAnnotations. Show all posts

Saturday, October 10, 2015

[Androidannotations 사용하기] 3. How It Works ?!

이번 포스팅에서는 더 많은 annotation들을 사용하기 전에, Android Annotations가 어떻게 동작하는지 가볍게 보고 가도록하자.

개요

AndroidAnnotations는 매우 간단하게 동작한다!
표준 Java Annotation Processing Tool을 사용하여, 소스 코드를 생성하는 여분의 컴파일 단계를 추가한다.

여기서 생성되는 소스 코드가 뭘까??
예를 들어 각각의 @EActivity annotated activity가 있다고 해보자. 이 Activity의 서브 클래스는 같은 이름 끝에 추가 접미어 '_'가 붙어서 생성된다.

* 지난 포스팅 참조 - [Androidannotations 사용하기] 1. Activity에 적용하기

예를 들어, 아래의 class는

package com.some.company;
@EActivity
public class MyActivity extends Activity {
  // ...
}

같은 패키지의 다른 소스 폴더에 다음과 같이 generate 된다.

package com.some.company;
public final class MyActivity_ extends MyActivity {
  // ...
}

이 서브 클래스는 super를 부르는 것을 위임하기 전에 몇 가지 메소드들(ex. onCreate(), ...)을 overrinding하여 사용자의 Activity에 동작을 추가한다.

이게 바로 AndroidManifest.xml파일에서 Activity 이름 뒤에 반드시 '_' 접미어를 붙여야 하는 이유이다~~! 바로 요로케~~^0^


그래서 지난 포스팅처럼 Activity를 시작할 때 generate된 클래스를 호출해야 하는 것이다~~!

참고 논문 및 사이트

1. androidannotations wiki 중 "HowItWorks" https://github.com/excilys/androidannotations/wiki/HowItWorks#starting-an-annotated-activity

[Androidannotations 사용하기] 2. Annotated activity 시작하기

이번 포스팅은 아주 간단하다~_~ annotate 된 Activity를 시작하는 방법이다.

annotated activity 시작하기

앞의 포스팅의 덧을 참고한다면 아주 쉽게 이해하고 할 수 있다. 본 포스팅을 건너뛰어도 좋다:D

안드로이드에서 Activity를 시작할 때 아래와 같이 해왔을 것이다!

startActivity(this, MyListActivity.class);

그러나 AndroidAnnotations를 사용한다면 반드시 MyListActivity_가 호출되어 시작되어야 한다!

startActivity(this, MyListActivity_.class);

Intent Builder

  • Since AndroidAnnotations 2.4
  • AndroidAnnotations 2.4버전 부터는 생성 된 Activity를 시작할 수 있도록 static helper를 제공한다.

    // Starting the activity
    MyListActivity_.intent(context).start();
    
    // Building an intent from the activity
    Intent intent = MyListActivity_.intent(context).get();
    
    // You can provide flags
    MyListActivity_.intent(context).flags(FLAG_ACTIVITY_CLEAR_TOP).start();
    
    // You can even provide extras defined with @Extra in the activity
    MyListActivity_.intent(context).myDateExtra(someDate).start();
    
  • Since AndroidAnnotations 2.7
  • Since AndroidAnnotations 2.7버전 부터는 startActivityForResult()도 동일하게 사용할 수 있다 ^_^

    MyListActivity_.intent(context).startForResult(REQUEST_CODE);
    

    결과 코드 및 추가 값을 얻기 위해 @OnActivityResult annotation을 사용할 수 있다~

    @OnActivityResult(REQUEST_CODE)
    void onResult(int resultCode) {
    }
    
  • Since AndroidAnnotations 3.3
  • AndroidAnnotations 3.3버전 부터는 intent builder를 이용해서 options을 Bundle parameter로 넘길 수 있당~!

    MyListActivity_.intent(context).withOptions(bundle).start();
    
  • Since AndroidAnnotations 4.0
  • AndroidAnnotations 4.0버전 부터는 Activity transition animations를 the fluent intent builder를 사용하여 추가 할 수 있다.

    MyListActivity_.intent(context).start().withAnimation(enterAnimRes, exitAnimRes));
    

    다음 포스팅에서는 Android Annotations의 동작 원리를 다시 살펴본 후, 사용하기를 계속 진행하도록 하겠다 ^_^

    참고 논문 및 사이트

    1. androidannotations wiki 중 "HowItWorks" https://github.com/excilys/androidannotations/wiki/HowItWorks#starting-an-annotated-activity

    [Androidannotations 사용하기] 1. Activity에 적용하기

    본 포스팅은 Androidannotations wiki를 기반으로 작성되었습니다 :D ~

    1. Android Annotations 환경 설정하기

    환경 설정은 앞의 포스팅을 참고해주세요^_^~!

    * AndroidAnnotations Intellij(or Android Studio 1.3) Setting 편 - http://juranosaurus.blogspot.kr/2015/08/android-annotation-setting.html

    2. Android Annotations 시작하기

    앞의 포스팅을 참고하여 환경 세팅이 모두 되었다면 이제 Android Annotations를 즐기면 된당 ^0^~

    • 1. 새로운 Activity 생성한다! (또는 이미 존재하는 Activity를 사용해도 된다~!)
    • 2. Activity에 @EActivity, @ViewById, @Click 을 사용한다! - 아래의 예제를 보고 따라해보자^_^ :
    import android.app.Activity;
    import android.widget.EditText;
    import android.widget.TextView;
    
    import org.androidannotations.annotations.Click;
    import org.androidannotations.annotations.EActivity;
    import org.androidannotations.annotations.ViewById;
    
    @EActivity(R.layout.main)
    public class MyActivity extends Activity {
    
        @ViewById(R.id.myInput)
        EditText myInput;
    
        @ViewById(R.id.myTextView)
        TextView textView;
    
        @Click
        void myButton() {
             String name = myInput.getText().toString();
             textView.setText("Hello "+name);
        }
    }
    

    main.xml은 평소 작성하던대로 작성하면 된다~! 아래는 정말 기본적인 예제이다~!

    
    
    
        
    
                
    
        
        
    
    
    • 3. 파일을 저장한다. (컴파일을 하고 우리가 만든 Activity의 이름 뒤에 '_'가 붙은 이름의 서브 클래스를 generate 한다. "MyActivity"의 generate된 서브클래스 이름은 "MyActivity_")
    • 4. Manifest에 MyActivity 대신에 MyActivity_를 등록한다. 이렇게~! :
    
    

    Android Annotations Activity에 적용하기 끄읏~!

    덧,

    AndroidManifest.xml 파일

    AndroidAnnotations가 Annotation이 적용된 각각의 Activity마다 서브클래스를 generate 하기 때문에 우리는 항상 Android Manifest에 액티비티이름 뒤에 '_' 접미사를 붙여서 등록해야한다!

    같은 패키지, 같은 이름에 그냥 _ 접미사만 붙이면 된다. AndroidManifest.xml에 등록하는거 깜빡해도 AndroidAnnotations가 알려주니 걱정말자~ ^_^

    AndroidManifest.xml 파일 찾기

    AndroidAnnotations는 generate 된 소스 폴더로부터 반복적으로 이동하여 AndroidManifest.xml 파일을 찾는다.

    AndroidAnnotations 2.7 부터는 프로젝트 구조에 적합하지 않는 경우 프로세서에 androidManifestFile 옵션을 제공함으로써 AndroidManifest.xml의 절대 경로를 지정할 수 있다!

    • javac - 옵션 추가: -AandroidManifestFile=/path/to/AndroidManifest.xml
    • Eclipse - "Properties > Java Compiler > Annotation Processing"으로 가서 Processor options에서 add
    • 다른 빌드 시스템, IDE - customization page를 살펴보길

    연관된 포스팅

    [AndroidAnnotations_EnhancedComponent] - @EActivity - http://juranosaurus.blogspot.kr/2015/08/androidannotationsenhancedcomponent.html

    위의 포스팅을 참고하여 Enhance activities에 대해서 더 알아보자 ^0^

    참고 논문 및 사이트

    1. androidannotations wiki 중 "FirstActivity" - https://github.com/excilys/androidannotations/wiki/FirstActivity

    [Android Databass] SQLiteOpenHelper, ContentProvider, OrmLite, AndroidAnnotations @OrmLite - 2

    Android에서 Sqlite를 사용하는 것에 있어서 두번째 포스팅입니다. 기본적인 OrmLite Library사용과 AndoridAnnotations를 적용하여 OrmLitef를 사용하는 방법에 대하여 포스팅하도록 하겠습니다.

    3. OrmLite

    ORM 이라는 의미는 Object Realational Mapping의 약자입니다. OrmLite는 많은 표준 Library의 복잡성과 오버헤드를 피하면서 SqlDatabase를 접근 할 수 있게 하는 경량 Library입니다. 이것은 다양한 JDBC를 지원할뿐만아니라, Sqlite와 함께사용하는 Android OS Database API의 native call 또한 지원한다.(Homepage참고) Orm 에 Java 표준 기술은 Java Persistence API로 JPA가 요즘 많이 사용되고 있다고 한다. Spring과 같은 웹 프로젝트에서 이런 Java Library를 사용하는 것과 같이 Android 계열에서 Sqlite 사용시 Orm 을 사용할 경우 더 편하게 사용할 수있으니 해당 Library는 앞으로 봤을때 사용성이 높은 Library라고 생각합니다.

    Github Repository 에 가면 아래의 작성한 소스코드 전체를 확인할 수 있습니다. OrmLite에 대한 상세한 설명은 더 파악한 후에 포스팅하도록 하겠습니다. 이 포스팅에서는 사용법 위주로 설명하도록 하겠습니다.

    가장 먼저 gradle에 ormlite에 대한 dependency를 추가해주어야 합니다.

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:23.0.1'
        ...
    
        compile 'com.j256.ormlite:ormlite-android:4.46'
        ...
    }
    

    저는 OrmLiteDBHelper라고 class 이름을 설정하고 해당 class는 OrmLiteSqliteOpenHelper를 상속받아야 합니다. 또한 상속받게 되면 아래와 같이 생성자와 onCreate, onUpgrade에 대해서 implement를 반드시 해줘야 한다. 그리고 SQLiteOpenHelper 에서 해줬던것 처럼 데이터베이스 이름과 버전을 설정해줘야한다. 저는 이 부분에 대해서 Constant Class를 따로 두고 받아오도록 구현하였습니다.

    package com.juranoaa.sqlite.common;
    
    public class Constant {
        public static final class SQLite {
            public static final Integer ORM_DB_VERSION = 1;
            public static final String ORM_DB_NAME = "ORMTest.db";
        }
    }
    
    package com.juranoaa.sqlite.ormlite;
    
    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;
    
    import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
    import com.j256.ormlite.support.ConnectionSource;
    import com.juranoaa.sqlite.common.Constant;
    
    public class OrmLiteDBHelper extends OrmLiteSqliteOpenHelper{
    
        public OrmLiteDBHelper(Context context) {
            super(context, Constant.SQLite.ORM_DB_NAME, null, Constant.SQLite.ORM_DB_VERSION);
        }
    
        @Override
        public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
    
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {
    
        }
    }
    

    그리고 onCreate와 onUpgrade method에 아래와 같이 구현해야 합니다. onCreate에는 Database생성, onUpgrade에는 Database 삭제후 생성과 같은 함수 내용을 작성해야 합니다. 이 부분을 아래와 같이 작성해줍니다. 이때 사용되는 것은 " TableUtils " 에 정의 되어있고 데이터베이스를 생성해주는 쿼리문을 작성해줍니다.

    package com.juranoaa.sqlite.ormlite;
    
    public class OrmLiteDBHelper extends OrmLiteSqliteOpenHelper{
    
    //...
    
    @Override
    public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
        try {
            TableUtils.createTable(connectionSource, House.class);
        } catch (SQLException e) {
            Log.e(TAG, e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }
    // ...
    
    }
    

    여기서 House.class 와 같은 경우는 아래와 같이 정의가 필요합니다. 이렇게 작성해주게 되면 자동으로 CREATE 문을 생성하여 Sqlite Database에 입력해줍니다. 자세한 내용은 Database를 참고 하시기 바랍니다.

    package com.juranoaa.sqlite.ormlite;
    
    public class House {
    
        // auto increment
        @DatabaseField(generatedId = true)
        Integer id;
    
        @DatabaseField
        String name;
    
        public House() {
            // must exist
        }
    }
    

    그리고 두가지 method를 정의하여 사용할 수 있습니다. getDao(class)의 경우에는 SQLExcpetion이 throw 되는 method이고 getRuntimeExceptionDao(class)은 Exception 에 대해서 RuntimeException으로 처리하여 사용하는 method가 제공됩니다. 이에 대한 구현은 아래와 같이 표현됩니다.

    package com.juranoaa.sqlite.ormlite;
    // ...
    
    public class OrmLiteDBHelper extends OrmLiteSqliteOpenHelper{
        // ...    
        private RuntimeExceptionDao<House, Integer> houseRuntimeExceptionDao = null;
        // ...
        
        public RuntimeExceptionDao<House, Integer> getHouseDataDao() {
            if (houseRuntimeExceptionDao == null) {
                houseRuntimeExceptionDao = getRuntimeExceptionDao(House.class);
            }
            return houseRuntimeExceptionDao;
        }
        // ...
    }
    

    이렇게 구성하면 OrmLite를 사용할 수 있게됩니다. 또한 해당 Library는 apt를 통해 compile 시점에 작동하는 library가 아닌 runtime시에 작동하는 library입니다. 그리고 Activity에서 아래 코드와 같이 사용하면 사용할 수 있습니다.

    
    @EActivity(R.layout.main)
    public class AASQLiteActivity extends OrmLiteBaseActivity<OrmLiteDBHelper> {
    
        @ViewById
        TextView textView;
    
        @AfterViews
        void afterViews(){
            RuntimeExceptionDao<House, Integer> houseDao = getHelper().getHouseDataDao();
            List<House> houseList = houseDao.queryForAll();
            textView.setText(houseList.toString());
    
            for(House house : houseList){
                House updateHouse = new House(house.getId(), house.getName()+"_update");
                houseDao.update(updateHouse);
            }
            List<House> updateHouseList = houseDao.queryForAll();
            textView.setText(textView.getText()+updateHouseList);
        }
    }
    
    

    Activity는 OrmLiteBaseActivity를 상속받고 Generic 부분은 앞에서 작성한 클래스 이름이 작성되게 됩니다. (OrmLiteDBHelper) 그리고 getHeleper()를 통하여 OrmLiteDBHelper에서 작성한 getHouseDataDao method를 호출하여 사용하면 된다.

    해당 Dao에 대해서 create, update, delete, queryForAll 과 같은 method을 이용하여 SQLite 데이터 베이스에 CRUD 작업을 할 수 있습니다. 이렇게 사용할 경우 상속받을 Activity에 type과 method를 작성을 해야하는데 이런 불편함을 조금 더 개선한게 AndroidAnnotations에 @OrmLite입니다.

    4. AndroidAnnotations @OrmLite @EProvider

    가장먼저 AndroidAnnotations의 @OrmLite를 사용하기 위해서 Gradle에 추가정보를 입력해야 합니다. 해당 부분은 4.0-SNAPSHOT 기준입니다. 현재 Release 버전인 3.3.2를 사용한다면 기존 apt만 사용해도 사용할 수 있습니다.

    def AAVersion = "3.3.2"
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:23.0.1'
        apt "org.androidannotations:androidannotations:$AAVersion"
        compile "org.androidannotations:androidannotations-api:$AAVersion"
        compile 'com.j256.ormlite:ormlite-android:4.46'
    }
    
    def AAVersion = "4.0-SNAPSHOT"
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:23.0.1'
        apt "org.androidannotations:androidannotations:$AAVersion"
        compile "org.androidannotations:androidannotations-api:$AAVersion"
    
        compile 'com.j256.ormlite:ormlite-android:4.46'
        apt "org.androidannotations:ormlite:$AAVersion"
        compile "org.androidannotations:ormlite-api:$AAVersion"
    }
    

    @OrmLite 사용에 있어서 기존에 앞에 사용하던 Class는 그대로 유지를 하여 사용하면 됩니다. 그리고 객체를 반환했을때 사용할 수 있도록 하는 객체하나가 필요합니다.

    package com.juranoaa.sqlite.ormlite;
    
    import com.j256.ormlite.dao.Dao;
    import com.j256.ormlite.dao.RuntimeExceptionDao;
    
    public class HouseRuntimeExceptionDao extends RuntimeExceptionDao<House,Integer>{
    
        public HouseRuntimeExceptionDao(Dao<House, Integer> dao) {
            super(dao);
        }
    }
    

    위의 객체를 정의하고 Activity에서 아래와 같이 정의하고 사용하면 @OrmLite Annotation을 사용할 수 있습니다.

    @EActivity(R.layout.main)
    public class AASQLiteActivity extends Activity{
        ...
        @OrmLiteDao(helper = OrmLiteDBHelper.class)
        HouseRuntimeExceptionDao houseDao;
        ...
    }
    

    위의 코드는 Github Repository에서 전체 코드를 확인할 수 있습니다.

    이상으로 Android 에서 Database를 접근하는 방법에 대해서 두개의 포스팅에서 검토해보았습니다. ContentProvider의 경우 Android Docs를 보게되면 Application간에 데이터를 공유해야 되는 경우에만 필요하다고 작성되어있습니다. 따라서 CRUD에 대한 쿼리 작성이 불편한 점으로 인해 하나의 Application에서만 사용하는 DB를 ContentProvider로 쓰는것은 올바르지 않다고 생각합니다. 그래서 제 생각에는 OrmLite를 이용하면 조금 더 이런 코드에 대한 리팩토링을 할 수있다고 생각합니다. 이상 끝.

    Friday, October 9, 2015

    [Android Databass] SQLiteOpenHelper, ContentProvider, OrmLite, AndroidAnnotations @OrmLite - 1

    이 블로그는 4개의 Documentation 을 참고하여 작성되었습니다. [ SQLiteOpenHelper Docs, ContentProvider Docs, OrmLite Docs, AndroidAnnotations @EProvider ]

    팀원 중 한명이 AndroidAnnotations 를 기반으로한 주소록 어플을 빠르게 만들고 있는데, 그러던 중 SQLiteOpenHelper 클래스를 사용하여 쓰는 것을 볼 수 있었다. 초기 개발시에는 해당 클래스를 기반으로 개발하고 ContentProvider를 쓰고 @EProvider를 써본다고 해서.. 갑자기 3가지의 차이점이 궁금하게 되었다. 그래서 Android Docs에 가서 해당 내용에 대해서 분석해보았다.

    1. SQLiteOpenHelper

    먼저 SQLiteOpenHelper를 설명하자면 클래스 이름 그대로 Android 에서 내장 되어 사용되는 SQLite를 사용하는데 도와주는 Helper역할을 하는 클래스이다. Database Creation, Version Management, 즉, 데이터베이스 생성과 버전관리를 담당하는 부분이다. 해당 Class를 상속받는 클래스를 생성하면 아래의 코드와 같이 onCreate(SQLiteDatbase)onUpgrade(SQLiteDabase, int int) 2개의 method 구현이 반드시 이뤄져야 하며 생성자 또한 존재해야한다.

    package com.juranoaa.sqlite;
    
    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    
    public class BaseSQLiteOpenHelper extends SQLiteOpenHelper{
    
        public BaseSQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
    
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    
        }
    }
    

    1.1. Constructor(Context, String, SQLiteDabase.CursorFactory, int) 생성자

    해당 생성자의 경우에 중요한 부분이 있습니다. 역할은 당연히 database에 대한 create, open, manage하기 위한 부부이지만 해당 되는 부분은 getWritableDatabase(), getReadableDatabase() method를 부르기 전까지 데이터베이스 자체는 생성되지 않는 다는 점입니다. 앞으로 나올 부분에서도 해당 부분에 대한 언급을 계속하게 되기때문에 "데이터베이스 자체가 생성되지 않는다"라는 것은 반드시 숙지하고 넘어가시기 바랍니다.

    1.2. onCreate(SQLiteDatabase), onUpgrede(SQLiteDatabae)

    method 명과 같이 데이터 베이스를 생성하거나, 데이터베이스를 업그레이드 하는 역할을 하는 method입니다. 만약 Sqlite로 id, title, content를 가진 게사판을 구현한다고 하면 다음과 같이 작성됩니다.

    public class Constant {
    
        public static final class SQLite {
            public static final Integer DB_VERSION = 1;
            public static final String DB_NAME = "AATest.db";
    
            public static final String TABLE_NAME = "board";
            public static final String DB_CREATE =  "CREATE TABLE " + TABLE_NAME + " ("+
                                                    " id INTEGER PRIMARY KEY AUTOINCREMENT "+
                                                    " title VARCHAR NOT NULL"+
                                                    " content VARCHAR NOT NULL"+
                                                    ")";
            public static final String DB_DROP =    "DROP TABLE IF EXIST " + TABLE_NAME ;
        }
    }
    
    import android.content.Context;
    import android.database.sqlite.SQLiteDatabase;
    import android.database.sqlite.SQLiteOpenHelper;
    
    public class BaseSQLiteOpenHelper extends SQLiteOpenHelper{
    
        public BaseSQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(Constant.SQLite.DB_CREATE);
        }
    
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            db.execSQL(Constant.SQLite.DB_DROP);
            onCreate(db);
        }
    }
    

    1.3. getReadableDatabase(), getWritableDatabase()

    두 개의 method는 특징이 있습니다. SQLiteOpenHelper의 경우 onCreate, onOpen, onUpgrade method 들이 생성자 시점에 불리는 것이 아니라 해당 method들이 호출될때 불리게 됩니다. 이렇게 구현이 되는 이유는 생성 또는 업데이트 과정에서 시간이 올래걸리게 된다면 Application이 호출하는데 늦게 활성화가 되기 때문입니다. 따라서 해당 method가 Application main thread에서 불리게 하는것은 되도록 지양해야합니다.

    2. ContentProvider

    ContentProvider는 AndroidApplication에서 application에서 제공하는 가장 중요한 부분 중 하나 입니다.Data는 Encapsulation(은닉화)되어있으며, ContentResolver를 이용하여 인터페이스간 통신을 제공해줍니다. 따라서 ContentProvider는 Application간에 데이터를 주고 받고자할 때 필요한 부분입니다. 만약에 Application간에 Data share가 발생하지 않는다면 굳이 contentProvider를 사용하는 것이아닌 SQLiteDatabase를 직접적으로 사용하면 됩니다.

    (이 부분이 Docs에 쓰여져있긴 하지만 굳이 Database에 Directly 접근할 필요가 있을까? 라는 의문이 드네요).

    그리고 아래와 같이 ContentProvider를 사용하게 되면 6개의 method가 반드시 작성되어 있어야만합니다.

    public class BaseContentProvider extends ContentProvider{
        @Override
        public boolean onCreate() {
            return false;
        }
    
        @Nullable
        @Override
        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            return null;
        }
    
        @Nullable
        @Override
        public String getType(Uri uri) {
            return null;
        }
    
        @Nullable
        @Override
        public Uri insert(Uri uri, ContentValues values) {
            return null;
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            return 0;
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
            return 0;
        }
    }
    

    2.1. Constructor() 생성자

    저는 이 생성자가 가장 중요한 부분이라고 생각합니다. ContentProvider는 Application 실행시점에 application main thread를 사용하여 실행합니다. 따라서 아까 위에서 말했듯이 SQLiteOpenHelper를 통해 구현한 클래스에 대한 실행이 생성자 부분에서 실행되도록 하면 안됩니다. 해당 부분이 실행되게 되면 application이 늦게 시작이 될 수 있기 때문에 이부분이 가장 중요한 부분이라고 생각합니다.

    2.2. onCreate()

    Docs에 존재하는 이 method에 대한 설명 또한 시간이 오래걸리는 작업, Application 시작시 느려지는 것은 수행하지 않도록 해야 한다고 권장하고 있습니다. 따라서 SQLiteOpenHelper를 이용해서 Database의 생성부분을 관리하도록 짜는 것이 중요합니다. 또한 onCreate() method 내부에서 getReadableDatabase()getWriteabaleDatabase() method를 부른다면 Database 생성을 main thread에서 수행하는것과 마찬가지가 되기 때문에 이부분을 가장 중요시하게 다뤄야 합니다.

    ContentProvider를 작성하면 다음과 같다.

    Constant.class

    public class Constant {
    
        public static final class SQLite {
            public static final Integer DB_VERSION = 1;
            public static final String DB_NAME = "AATest.db";
        }
    }
    

    Board.class

    public class Board {
    
        private Integer id;
    
        private String title;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public Board() {}
    
        public Board(Integer id, String title) {
            this.id = id;
            this.title = title;
        }
    
        @Override
        public String toString() {
            return "Board{" +
                    "id=" + id +
                    ", title='" + title + '\'' +
                    '}';
        }
    }
    

    BoardProvider.class

    public class BoardProvider extends ContentProvider {
        private static final String TAG = BoardProvider.class.getSimpleName();
    
        public static final String TABLE_NAME_BOARD = "dbBoard";
        private static final String AUTHORITY = "com.juranoaa.sqlite";
    
        public static final Uri CONTENT_URI_BOARD = Uri.parse("content://" + AUTHORITY + "/" + TABLE_NAME_BOARD);
    
        public static final String COL_ID = "_id";
        public static final String COL_TITLE = "title";
    
        /** create db string */
        public static final String DATABASE_CREATE_BOARD =
                "CREATE TABLE " + TABLE_NAME_BOARD +
                " (" + COL_ID + " INTEGER PRIMARY KEY,"
                + COL_TITLE + " TEXT );";
    
        private static BoardDataBaseHelper mDbHelperBoard;
        private SQLiteDatabase mDbBoard;
    
        @Override
        public boolean onCreate() {
            mDbHelperBoard = new BoardDataBaseHelper(getContext());
            return true;
        }
    
        @Override
        public Cursor query(Uri uri, String[] projection, String selection,
                            String[] selectionArgs, String sortOrder) {
            mDbBoard = mDbHelperBoard.getReadableDatabase();
            Cursor cursor = mDbBoard.query(TABLE_NAME_BOARD, projection, selection,
                    selectionArgs, null, null, sortOrder);
            return cursor;
        }
    
        @Override
        public String getType(Uri uri) {
            return null;
        }
    
        @Override
        public Uri insert(Uri uri, ContentValues values) {
            mDbBoard = mDbHelperBoard.getWritableDatabase();
            mDbBoard.insertOrThrow(TABLE_NAME_BOARD, null, values);
            return null;
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            mDbBoard = mDbHelperBoard.getWritableDatabase();
            int count = mDbBoard.delete(TABLE_NAME_BOARD, selection, selectionArgs);
            return count;
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection,
                          String[] selectionArgs) {
            mDbBoard = mDbHelperBoard.getWritableDatabase();
            int count = mDbBoard.update(TABLE_NAME_BOARD, values, selection, selectionArgs);
            return count;
        }
    
        private static class BoardDataBaseHelper extends SQLiteOpenHelper {
    
            private static final String TAG = BoardDataBaseHelper.class.getSimpleName();
    
            public BoardDataBaseHelper(Context context){
                super(context, Constant.SQLite.DB_NAME, null, Constant.SQLite.DB_VERSION);
            }
    
            @Override
            public void onCreate(SQLiteDatabase db) {
                db.execSQL(DATABASE_CREATE_BOARD);
    
            }
    
            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                Log.w(TAG, "invoked! " + oldVersion + " -> " + newVersion + ", it'll destroy old data");
                db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME_BOARD);
                onCreate(db);
    
            }
        }
    }
    

    BoardDao.class

    public class BoardDao {
    
        public static final String COLS_BOARD_ARR[] = {
                BoardProvider.COL_ID,
                BoardProvider.COL_TITLE
        };
    
        private BoardDao() {} 
    
        /**
         * Insert new Board to DB.
         */
        public static void insertNewBoard(Context ctx, Board board) {
            ContentValues Values = new ContentValues();
            ContentResolver cr = ctx.getContentResolver();
    
            Values.put(BoardProvider.COL_TITLE, board.getTitle());
    
            cr.insert(BoardProvider.CONTENT_URI_BOARD, Values);
        }
    
        public static List getAllBoards(Context ctx) {
            List boards = new ArrayList();
    
            ContentResolver cr = ctx.getContentResolver();
            Cursor c = cr.query(BoardProvider.CONTENT_URI_BOARD,
                    COLS_BOARD_ARR, null, null, null);
    
            if (c != null && c.getCount() > 0) {
                c.moveToFirst();
                while (c.isAfterLast() == false) {
                    boards.add(new Board(
                            c.getInt(c.getColumnIndex(BoardProvider.COL_ID)),
                            c.getString(c.getColumnIndex(BoardProvider.COL_TITLE))));
                    //LogUtil.w("map.put(" + uid + "," + persona_name+ ")");
                    c.moveToNext();
                }
            }
            c.close();
    
            return boards;
        }
    
        public static Board getBoardById(Context ctx, int id) {
            Board board = null;
    
            ContentResolver cr = ctx.getContentResolver();
            Cursor c = cr.query(BoardProvider.CONTENT_URI_BOARD,
                    COLS_BOARD_ARR, BoardProvider.COL_ID + "=?",
                    new String[]{String.valueOf(id)}, null);
            if (c != null && c.getCount() > 0) {
                c.moveToFirst();
                while (c.isAfterLast() == false) {
                    board = new Board(
                            c.getInt(c.getColumnIndex(BoardProvider.COL_ID)),
                            c.getString(c.getColumnIndex(BoardProvider.COL_TITLE)));
                    c.moveToNext();
                }
            }
            c.close();
    
            return board;
        }
    }
    

    AndroidManifest.xml

    
    
    
        
    
            
            
        
    
    
    

    Github 링크는 다음과 같습니다. https://github.com/JuranoSaurus/AndroidAnnotationsSampleProject/tree/sqlite.1.0

    -------------------------------------------

    다음 포스팅에서 OrmLite와 AndroidAnntation EProvide를 이용한 DB 구성에 대한 포스팅을 하도록 하겠습니다. 끝...

    참고 논문 및 사이트

    1. [Android/안드로이드] Content Provider ( 콘텐트 프로바이더 ) 에 대한 모든 것. http://aroundck.tistory.com/236

    Tuesday, October 6, 2015

    Gradle 기반으로 Android Annotation Apt Processor 만들기.

    이 포스팅은 Android Annotation Processing Setup using Gradle - Jens Diller을 기반으로 작성하였습니다.

    Annotation Processing은 다른 포스팅에서도 설명했듯이 Compile Time에 java에서 annotation 에 대한 스캔과 처리를 하는 것 입니다. 물론 AndroidAnnotations Framework를 사용하면 편리한 기능을 많이 사용할 수 있지만 개발자라면 누구나 이것이 어떻게 동작하고 만들어지는 방법에 대해서 이해를 할 것이라고 생각합니다. 그래서 Android Project에서 Annotation Processor를 빌드하는 방법을 설명하고자 합니다.

    1. Android Project 만들기

    "Start a new Android Studio project" 을 선택하여 새로운 프로젝트를 생성합니다.

    저는 Application Name : Sample , Company Domain : jurano.com , Package name : com.jurano.annotation으로 프로젝트를 진행하도록 하겠습니다. 이 부분은 사용자가 지정하기 나름이니 알아서 하시면 됩니다.

    알맞은 Minimum SDK Version을 선택한 뒤 Next

    일단 우리에게는 Activity가 필요없으니 "Add No Activity" 선택 하고 Finish를 선택해줍니다. 그러면 프로젝트 생성끝!

    2. 새로운 모듈 추가하기.

    Annotation Processor를 적용하려면 두개의 Module이 더 필요합니다. 생성할 모듈은 2개로 Annotation API를 정의하는 Java Library와 Annotation Processor 를 정의하는 라이브러리로 나누어지게 됩니다.File → New → New Module... 을 가면 아래와 같은 화면이 뜨게됩니다.

    여기서 Java Library를 선택해줍니다.

    선택한 뒤에 Library name : api, Java package name : com.jurano.annotation , Java class name : FullScreen 으로 정의한뒤 프로젝트를 생성시켜줍니다.그럼 API에 대한 Java library 생성이 완료된것이며 위와 똑같은 방식으로 Annotation Processor 프로젝트를 생성하여 줍니다.

    Library name : compiler, Java package name : com.jurano.annotation , Java class name : AnnotationProcessor


    3. Gradle Build 설정하기

    Annotation Processor를 사용하기 위해서는 총 4가지의 build.gradle 파일을 수정해야 합니다. 앞으로 상위 build, api, compiler, app 순으로 설정해야 할 정보들에서 설명하도록 하겠습니다.

    3.1. Project: Sample build.gradle (부모 Gradle 파일)

    상위 build 파일에서 먼저 android-apt 프로세스 사용을 위한 빌드 정보를 입력해주어야 합니다. 기존에 생성되는 코드는 아래와 같이 구성 됩니다. 첫 줄에 작성되 있는 것과 같이 해당 gradle 파일은 Top-level 빌드 파일입니다. 따라서 아래에 있는 build 파일들에 대한 repostiory 는 jcenter이며, 위에 작성되어 있는 buildscritp 부부은 android를 build할때 사용되는 build tool에 대한 version을 나타내게 됩니다.

    // Top-level build file where you can add configuration options common to all sub-projects/modules.
    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:1.3.0'
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    allprojects {
        repositories {
            jcenter()
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    위에 android-apt에 대한 모듈을 dependency에 추가시켜야 하위 gradle 파일에서 해당되는 프로세서를 사용할 수 있습니다. 따라서 아래의 라인을 dependecy에 추가시켜주어야 합니다.

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:1.3.0'
            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' // add
        }
    }
    

    현재 1.8 버전까지 나와 있으며 해당버전 사용에 있어 문제가 없기 때문에 최신버전을 이용하였습니다. But 1.7 버전 사용시 android processor가 정상적으로 실행되지 않았습니다. 이유는 잘 모르겠으나 1.7 버전은 사용하지 않는것이 좋습니다.(2틀날림...) 해당 프로젝트의 상위 모듈은 위의 한 줄만 추가하면 모두 끝났습니다.

    3.2. Api library

    api library의 경우에는 java이고 다른 library를 물리지 않고 사용자 정의 Annotation을 정의하는 부분이기 때문에 다른 사항은 필요하지 않습니다. 아래 코드는 아까 java library 생성시 생성된 코드로 다른 것을 추가하거나 제거할 필요는 없습니다.

    apply plugin: 'java'
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
    }
    

    하지만 위에 있는 포스팅에 "One more thing: If you're using Java 8 as your SDK, you need to ensure and tell the compiler to build using Java 7 or you will get the following error during pre-dexing:" 다음과 같은 문구가 존재합니다. 결국 Java 1.8을 사용하고 있다면 1.7을 사용하기 위해서 1.7하도록 하라는 것인데 이 부분에 있어 저는 1.7을 사용하기 때문에 추가하지 않았습니다.

    sourceCompatibility = JavaVersion.VERSION_1_7  
    targetCompatibility = JavaVersion.VERSION_1_7  
    

    3.3. Compiler library

    Compiler library의 경우에는 추가사항이 존재합니다. 먼저 기존 build정보는 아래와 같습니다.

    apply plugin: 'java'
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
    }
    

    위의 정보에서 내가 생성한 library인 api에 대한 사용권한을 추가해야합니다. 따라서 아래의 코드를 추가해줍니다. 아래의 dependency를 추가함으로써 compiler에서 api에서 생성한 annotation에 대한 사용이 가능하게 됩니다. 따라서 무조건 필요한 부분입니다.

    apply plugin: 'java'
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile project (':api') // add
    }
    

    3.4. App Sample

    마지막으로 App에 대한 build정보입니다. 해당 build 파일이 수정할 부분이 가장 많습니다. 먼저 아래는 기본 소스 코드 입니다.

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 23
        buildToolsVersion "23.0.1"
    
        defaultConfig {
            applicationId "com.jurano.annotation"
            minSdkVersion 22
            targetSdkVersion 23
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:23.0.0'
    }
    

    가장먼저 추가해야 하는 것은 android-apt를 사용할 것이라는 정의 입니다. 2번째 라인에 추가되어 있는 부분으로 이를 사용함을 정의합니다. 그리고 dependency 에 내가 사용하는 내 로컬 library인 api와 compiler에 대한 빌드 정보를 아래와 같이 추가해줍니다. 컴파일러 library의 경우에는 apt 를 이용하여 빌드되게 아래 코드와 같이 구성해야 합니다.

    apply plugin: 'com.android.application'
    apply plugin: 'com.neenbedankt.android-apt'
    
    android {
        compileSdkVersion 23
        buildToolsVersion "23.0.1"
        ...
    
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:23.0.0'
        compile project(':api')
        apt project(':compiler')
    }
        
    

    위와 같이 작성하면 모든 build 파일에 대한 설정을 완료하였습니다. Annntation Processor에 대한 동작원리를 이해한다면 충분히 따라오실 수 있으셨을 겁니다.

    4. Java Class 생성하기

    이제 사용자가 정의한 새로운 Anntation과 그것에 대한 사용법을 보도록 하겠습니다. 이것도 동일하게 api → compiler → app 방식으로 진행하도록 하겠습니다.

    4.1. API 클래스 구현

    먼저 저희가 장으로 생성한 Class는 아래와같이 구성이 되어있습니다. 하지만 저희는 @interface가 필요 함으로 과감하게 바꿔줍니다. 또한 api라는 package안에 annotation을 만들도록 하겠습니다.

    package com.jurano.annotation;
    
    public class FullScreen {
    }
    
    package com.jurano.annotation.api;
    
    public @interface FullScreen {
    }
    

    또한 타켓이나 용도 지정이 가능해지는데 이부분에 있어서는 이 포스팅을 참고해주시기 바랍니다. [What is java annotation 03] - Creating your Own Annotations?


    4.2. Annotation Processor 정의

    이제 Annotation에 대한 정의는 위와 같이 하면 끝난것입니다. 이제 다음으로 Annotation Processor를 정의하러 Compiler 모듈로 이동합니다. 기존에 있는 소스코드를 package를 추가하여 아래와 같이 이동시켰습니다. 또한 Annotation Processor를 사용하기 위해서 시작이 되는 class는 AbstractProcessor class를 상속받아야 함으로 변경된 코드는 이를 반영하였습니다.

    package com.jurano.annotation;
    
    public class AnnotationProcessor {
    }
    
    package com.jurano.annotation.compiler;
    
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.RoundEnvironment;
    import javax.lang.model.element.TypeElement;
    
    public class AnnotationProcessor extends AbstractProcessor{
        @Override
        public boolean process(Set set, RoundEnvironment roundEnvironment) {
            return false;
        }
    }
    
    

    또한 AnnotationProcessor를 실행시키기 위해서는 몇가지 작업이 필요합니다. 따라서 "compiler/src/main/resources/META-INF/services"에 폴더를 생성해주고 "javax.annotation.processing.Processor"파일을 생성해주어야 합니다. 구조가 만들어지면 다음과 같은 프로젝트 형식이 되게 됩니다.

    해당 파일의 내부에는 Annotation Processor가 처음 실행되는 class의 이름을 작성해줍니다. 저는 Annotation Processor의 시작점은 AnnotationProcessor로 할것이기 때문에 아래와 같이 작성해주었습니.

    com.jurano.annotation.compiler.AnnotationProcessor
    

    위와같이 작성해주고 함수 내부는 클래스를 생성하는 함수를 작성해야 하는데 함수를 작성하는 모듈에 대한 내용은 다른 포스팅을 통하여 설명하겠습니다. 여기서는 간단하게 Annotation이 있을 경우에 특정경로에 파일이 작성되도록 하는 것으로 대체 하겠습니다. 코드는 아래와 같습니다.

    package com.jurano.annotation.compiler;
    
    import com.jurano.annotation.api.FullScreen;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.Collections;
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.RoundEnvironment;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.TypeElement;
    
    public class AnnotationProcessor extends AbstractProcessor{
    
        @Override
        public Set getSupportedAnnotationTypes() {
            return Collections.singleton(FullScreen.class.getCanonicalName());
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        @Override
        public boolean process(Set set, RoundEnvironment roundEnvironment) {
            for(Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(FullScreen.class)){
                TypeElement annotatedClass = (TypeElement) annotatedElement;
                // TODO Code generate
                String annotatedClassName = annotatedClass.getSimpleName().toString();
    
                try {
                    FileOutputStream output = new FileOutputStream("E://log.txt");
                    output.write(annotatedClassName.getBytes());
                    output.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
    
            return false;
        }
    }
    
    

    4.3. Android Application에서 빌드되는지 확인하기.

    마지막으로 Android Application에서 이것이 사용이 가능한 것인지 이제 구현하여 확인해보도록 하겠습니다. 만들어진 App에서 ConfirmFullScreen이라는 class 를 생성하고 Annotation을 위에다가 선언해 줍니다. 코드는 아래와 같습니다.

    package com.jurano.annotation;
    
    import com.jurano.annotation.api.FullScreen;
    
    @FullScreen
    public class ConfirmFullScreen {
    }
    

    빌드를 실행하게 되면 아래와 같이 파일에 텍스트가 작성되게 됩니다. 필요한 기능에 대해서 Java class를 generated 하도록 소스를 작성한다면 자주 반복되는 코드와 같은 기능을 손쉽게 작업을 할 수 있습니다.

    소스는 GradleAndroidAnnotationProcessor에서 확인할 수 있습니다. 다음 포스팅은 소스코드 생성에 대해서 다뤄보도록 하겠습니다. 끝

    5. 현재문제점

    Compiler 부분의 java process가 죽지않아서 매번 프로세스를 강제 종료하고 있습니다. 이부분은 해결해서 다시올리도록하겠습니다.. ㅠ 참고한 프로젝트에서도 compiler가 죽지 않는 문제가 있어서 이 부분은 확인이 필요한 부분입니다.

    Monday, September 21, 2015

    [Unit testing] 2. Play JUnit~!

    JUnit을 시작해보자 ^_^

    * 개발환경: OS X Yosemite / Android Studio 1.3.2

    1. 프로젝트 생성

    기본 옵션으로 Blank Activity 프로젝트를 생성한다.

    2. Unit Test 폴더 구조 생성

    Junit 테스트의 경우, src/test/java를 기본 테스트 폴더로 인식한다. 그렇기 때문에 폴더명을 수정해준다~! 마우스 우클릭 > Refactor > Rename > "androidTest"를 "test"로 수정

    * 테스트 폴더가 안보인다면 좌측 상단을 Project로 선택해주면 된다

    3. 빌드 스크립트 수정

    app의 build.gradle 파일을 열어서 dependency에 testCompile 구문을 추가해준다. 그 다음, Sync now 클릭!

    4. Test Artifact 설정

    좌측 하단 Build Variant 탭을 클릭한 후 Test Artifact를 Unit Tests로 설정해 준다. 그러면 아래와 같이 폴더 아이콘이 초록색으로 변경된다~!

    여기까지 성공했다면, 환경설정 끝!

    5. Unit Test 코드 작성

    정상적으로 작동하는지 알아보기 위해 간단히 Calculator 클래스를 만들어서 두 정수를 더하는 메소드를 테스트 해보자 ^_^! 아래와 같이 Calculator랑 CalculatorTest 클래스를 생성하고 코드를 써준다~~

    클래스 생성해주고

    테스트 코드를 작성해준다! ^_^!

    6. 테스트 하기~~

    테스트 java 폴더에서 우클릭하여 Run 'All Tests' 클릭하여 테스트 해준당!

    끄읏~~~~~

    덧.

    내 환경에서는 위와 같이 했을 때 다음과 같이 fail이 2개가 생겼다 ㅜ_ㅜ

    로그 내용
    java.lang.RuntimeException: Method setUp in android.test.ApplicationTestCase not mocked. See http://g.co/androidstudio/not-mocked for details. at android.test.ApplicationTestCase.setUp(ApplicationTestCase.java) at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:86) at org.junit.runners.Suite.runChild(Suite.java:128) at org.junit.runners.Suite.runChild(Suite.java:27) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
    java.lang.RuntimeException: Method setUp in android.test.ApplicationTestCase not mocked. See http://g.co/androidstudio/not-mocked for details. at android.test.ApplicationTestCase.setUp(ApplicationTestCase.java) at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:86) at org.junit.runners.Suite.runChild(Suite.java:128) at org.junit.runners.Suite.runChild(Suite.java:27) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

    이럴 경우 app의 build.gradle 스크립트에 다음과 같이 testOption을 추가해주면 된다.

    Sync now를 해주고 다시 Run'All Test'를 하면!

    All Tests Passed! 진짜 끝~~~~~~^_^

    다음 글에서는 robolectric에 대해 알아보자!

    참고 논문 및 사이트

    1. TDD/JUnit 조금 더 알기 http://www.slideshare.net/WonchangSong1/abouttddj-unit

    2. Android Studio에서 Junit 기반 단위 테스트(Unit Test) 환경 구축하기 http://xinics.tistory.com/101#recentTrackback

    3. Android Developer https://developer.android.com/training/testing/unit-testing/local-unit-tests.html

    4. Android Tools Project Site http://tools.android.com/tech-docs/unit-testing-support

    Saturday, September 19, 2015

    [AndroidAnnotations structure analysis] How to make custom annotation processor_01

    1. 개요

    AndroidAnnotations 프레임워크 분석을 위해 나만의 커스텀 annotation processor를 따라 만들어보자.


    2. 환경

    Android Application는 Android Studio를 사용하고, AndroidAnnotations 프레임워크는 IntelliJ를 사용한다. 4.0-SNAPSHOT의 develop 브랜치에 따라가는 것임을 밝힌다.
    앞으로 분석을 위해 만들 custom annotation processor는 동일한 구조를 본딸 것이다. 이에 앞서, 어떤 방식으로 이 annotation processor가 import될 수 있는지 보자.

    AndroidAnnotations의 최신 버전(develop 브랜치에서 개발중)인 4.0-SNAPSHOT이다. 어플리케이션에서 이 프레임워크를 사용하기 위해서는, 어플리케이션에 빌드 스크립트가 들어가 있어야 한다.
    mavenCentral에 올라간 것을 사용하는 것이 아니라, 직접 빌드한 결과물을 로컬에서 가져오기 위해 아래와 같이 mavenLocal을 선언하였다. (4.0-SNAPSHOT 버전은 아직 릴리즈되지 않았기 때문이다.)
    아래와 같이 annotation processor를 선언하고, 해당 annotation processor에 알맞는 api 라이브러리를 선언해준다.


    그러면 이제 다음과 같은 예쁜 소스코드를 사용할 준비가 완료된 것!


    3. AndroidAnnotations 프레임워크 구조 분석

    AndroidAnnotations의 프로젝트 구조는 다음과 같다.

    core는 EActivity 등 AndroidAnnotations의 필수요소들을 담고 있으며, 나머지 Rest 등 다른 요소들은 필요시 옵션으로 부착할 수 있게 되어있다. core를 보면 다음과 같이 annotation processor와 core가 사용하는 api로 구성되어 있다. (테스트는 다음에 다루자.)

    위 라이브러리들은 maven을 통해 다음과 같이 jar로 추출된다. 이 중 javadoc과 sources를 제외하고 필수 요소만으로도 사용할 수 있다.



    4. AndroidTannotations 프레임워크 및 테스트 어플리케이션 생성

    이제 나만의 custom annotations processor를 만들어보자. 내 이름은 김태희니까 태노테이션이라고 해보았다. 팀원들은 어이가 없는 모양이지만 그냥 이렇게 할거다.
    앞서 언급한 대로 Android Studio로 Android Application을 만들어보자.(저는 1.3.2) (남아있는 소스는 그냥 찌꺼기니 신경쓰지 않으셔도 좋다.)


    그리고 나서, IntelliJ로 Java Application을 만들어보자.(저는 15 EAP) (남아있는 소스는 그냥 찌꺼기니 신경쓰지 않으셔도 좋다2.)

    원래 있던 main()을 없애고, [Project Structure -> Artifacts -> +로 JAR를 추가]로 설정하여 실행하면 main()이 실행되는 게 아니라, 실행되지 않는 jar가 생성되도록 설정해주자. 그러면 이제 라이브러리로 사용할 프로젝트가 완성되었다. (원래는 maven 빌드로 만들지만 원리를 파악하기 위함이니까 그렇게까지는 하지 않습니다.) 여기까진 기본이져...

    이렇게 하면 준비 끝~ 이 아니고 그 전에! annotation processor를 만드는 방법을 알아야 한다. 다음 장에 계속...


    Thursday, September 17, 2015

    [Unit testing] 1. What is JUnit?

    1. 단위 테스트란? - What is Unit test?

    * 사전적 의미 ^_^

    단위
    1) 사물의 길이, 넓이, 무게 등을 수치로 나타낼 때, 기본이 되는 기준
    2) 하나의 집단 조직 등을 구성하는 기본적인 한덩어리.

    테스트
    1) 제품의 성능이나 상태 따위를 일정한 기준에 따라 검사함
    2) 일정한 기준에 따라 검사하다.

    * 위키 백과 ^_^

    유닛 테스트(unit test)는 컴퓨터 프로그래밍에서 소스 코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차다.
    즉, 모든 함수와 메소드에 대한 테스트 케이스(Test case)를 작성하는 절차를 말한다.
    이를 통해서 언제라도 코드 변경으로 인해 문제가 발생할 경우, 단시간 내에 이를 파악하고 바로 잡을 수 있도록 해준다.

    이미지출처: http://martinfowler.com/bliki/UnitTest.html

    * 장점 ^_^

    1. 프로그램의 결함을 찾기 쉽다
    단위 테스트의 목적은 프로그램의 각각의 부분이 정확하게 잘 동작하는지 확인하는 것.
    즉, 프로그램을 작은 단위로 쪼개서 각 단위가 정확하게 동작하는지 검사하고 이를 통해 문제 발생 시 정확하게 어느 부분이 잘못되었는지를 재빨리 확인할 수 있게 해준다!
    => 따라서 프로그램의 안정성이 높아진다.
    단위 테스트는 개발 시간을 증가 시키는 것처럼 보인다.
    하지만 잘 이용한다면, 개발 기간 중 대부분을 차지하는 디버깅 시간을 단축시킴으로써 여유로운 프로그래밍을 가능케 한다!

    2. 변경이 쉽다
    리팩토링은 알다시피 기능을 유지하므로, 리팩토링을 진행하고 나서 자신이 변경한 코드가 리팩토링 전 처럼 의도한 대로 작동하고 있음을 단위 테스트를 통해서 확실하게 알 수 있다!
    이를 회귀 테스트(Regression testing)라고 한다.
    어떻게 코드를 수정해도 문제가 되는 부분을 빠르게 확인 할 수 있고, 수정한 코드가 잘 동작하는지 바로 알 수 있기 때문에 리팩토링에 있어서 안정적으로, 그리고 맘편하게 코드 수정을 할 수 있도록 도와준다~!

    3. 통합이 간단하다
    단위 테스트는 쪼개어진 단위에서 그 자체의 불확실성을 제거해주므로 상향식(bottom-up) 테스트 방식에서 유용하다.
    먼저 프로그램의 각 부분을 검증하고 그 부분들은 합쳐서 다시 검증하는 통합 테스트에서 더욱 더 빛을 발한다.

    * 왜 단위테스트를 써야 하지?

    두 말할 것 없다!!! "신뢰!"
    단위 테스트는 프로그램의 쪼개진 단위마다 테스트 결과가 매우 명확하다.
    그렇기 때문에 개발 조직에서 개발자와 개발자간의 코드에 대한 신뢰를 형성한다.
    개발 조직과 테스트 팀간의 신뢰 또한 ㅎㅎ!
    그리고 우리의 목적인 오픈소스 - 다른 개발자와 코드로 소통을 해야한다.
    이 때, 다른 개발자에 대한 최소한의 신뢰를 갖기에 단위테스트가 매우 적절하다!
    단위 테스트라는 것을 했고, 그 결과가 명확하기 때문에 그 관계에서 "신뢰" 가 형성될 수 있다 ^_^!!!!

    2. JUnit이란?

    * 정의?

    Java에서 독립된 단위 테스트를 도와주는 framework! (편의성! 자동화!)

    * 어떻게?

    기대값과 결과값을 비교!
    위에서 설명한 바와 같이 단위 테스트란 잘게 쪼갠 소스 코드의 어떠한 모듈이 의도한 대로 정확히 작동하는지 검증하는 것이다.
    즉, 모든 함수와 메소드에 대한 테스트 케이스(Test case)를 작성하는 절차를 말한다.
    해당 모듈이 동작하길 기대하는 값과 실제로 동작 후 나온 결과 값을 비교하며 테스트를 진행한다.

    * 참고~ JUnit은 컴파일 시에 JAR로서 링크된다.
    JUnit 3.8과 그 이전 버전에서는 프레임워크의 junit.framework 패키지 하위에 있고, JUnit 4와 그 이후 버전에서는 org.junit 패키지 하위에 있다.

    다음 포스팅에서는 JUnit 사용법을 알아보자~

    참고 논문 및 사이트

    1. 시작하자 단위테스트 - http://www.slideshare.net/youngeunchoi12/springcamp2014-35347399

    2. 위키백과 - 유닛 테스트 - https://ko.wikipedia.org/wiki/유닛_테스트

    3. 왜 단위 테스트를 해야 하는가? - http://blog.powerumc.kr/265

    4. Wikipedia - JUnit - https://en.wikipedia.org/wiki/JUnit

    [AndroidAnnotations_Build] 5. AndroidAnnotations 4.0 SNASHOT maven build

    시작하기에 앞서 AndroidAnnotaions가 4.0 SNAPSHOT 버전에서 보면 3.3.2 버전과 매우 다르게 프로젝트 구조가 격변했음을 알 수 있었다. 따라서 해당 프로젝트를 빌드하기 위해 처음에 Eclipse인줄만 알고(원래 3.3.2 까지 eclipse에서 작업을 했었다)작업을 했지만 정상적으로 수행되지 않았고, 열심히 Contributer에게 Gitter로 건의한 결과 Intellij에서 개발하는 환경 구축 방법을 얻을 수 있었다. 수행 해본 결과 Build에 성공하였고 이에 따라 로컬에 jar파일도 생성되어서 이미 구성해둔 Project에 import도 가능하게 되었다.:) 아래는 AndroidAnnotations 에 대한 4.0 SNAPSHOT 빌드 방법과 프로젝트 전체 구성방식에 대한 설명이다.

    1. AndroidAnnotations 4.0 SNAPSHOT 빌드

    빌드 환경은 InstallingEnvironment 에서 확인 가능하며, Maven Build 방식을 가지고 있다. Maven 에 대한 설명은 Build Label의 4번을 참고해도 되고 더 많은 정보는 구글링을 통해 찾아보면 된다. 여기서 나온 환경과 같이 해당 프로젝트를 빌드 하기 위해서는 3가지 조건이 필요하다.

    빌드 환경 구성에 대한 내용에서는 Fork를 받으라고 되어있지만 해당 페이지는 Contributing에 관한 이야기이기 때문에 굳이 Fork를 받지 않아도 상관없습니다. 따라서 그냥 Clone을 내 저장소에 받으면됩니다. (저는 컨트리뷰팅이 목적임으로 Fork를 받고 Clone을 받았습니다. 아래 사진과 같이 SourceTree에서)

    다음은 Console 창을 활성화 시킨뒤 Clone을 한 Repostiory로 이동하여 mvn install 을 작성하여 줍니다. 해당 명령어를 작성하게 되면 빌드가 실패가 되거나 정상적으로 수행되거나 둘중하나가 되게됩니다. 빌드가 성공하게 되면 다음과 같은 화면이 뜨게 됩니다. (수행하게되면 Maven에 명시된 모든 build가 수행됨으로 unittest케이스에 대한 실행도 모두 되게 됩니다.)


    1.1. [BUG] JAVA_HOME 이 지정되있지 않은경우

    환경변수에 JAVA_HOME 가 지정이 되어있지 않은 경우 정상적으로 install 되지 않을 수 있습니다. 빌드 실패시 로그가 눈에 보이기 때문에, 명령 오류에 따른 수정을 하면되지만 혹시라도 모르는 사람이 있을수도있기 때문에 환경변수 추가에 대한 내용을 적어두었습니다. 환경변수에 JAVA_HOME을 추가하면 되고 보통 oracle에서 받은 java를 설치할경우 설치 Driectory는 다음과 같습니다. "C:\Program Files\Java"

    설치경로를 확인한뒤 환경변수에 다음과 같이 JAVA_HOME을 지정해주면 됩니다.

    Window
    ------
    변수 이름(N) : JAVA_HOME
    변수 값(V) : C:\Program Files\Java\jdk1.7.0_76
    

    1.2. [BUG] Android SDK 가 지정되있지 않은 경우

    Android SDK 또한 ANDROID_PATH에 대한 것이 Path에 지정되어 있지 않다면 빌드가 정상적으로 이루어지지 않는다. 따라서 이 역시 환경변수에 추가해주어야 한다.

    Window
    ------
    변수 이름(N) : ANDROID_HOME
    변수 값(V) : C:\Development\android_sdk
    
    변수 이름(N) : Path
    변수 값(V) : ...;%ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools;...
    
    
    Linux
    ------
    export ANDROID_HOME=/home/francis/Android/Sdk
    export PATH=${PATH}:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
    

    1.3. [BUG] Android SDK 16이 없는 경우

    Android SDK 16 platform을 설치하면 됩니다. SDK Manager 를 활성화 시키고 아래의 화면과 같은 부분이 install 되어 있으면 됩니다.

    2. AndroidAnnotations Maven Build 구조 분석

    생각했던것 보다 엄청난 Maven 상속 구조를 가지고 있었습니다. 실제로 이런 프로젝트 빌드와 배포를 수행해 보지 않았기 때문에 빌드에 성공한뒤 프로젝트에 대한 구성 내용에 대한 파악을 진행해 보았습니다.

    추후 업데이트 예정...

    [AndroidAnnotations_Build] 4. Maven build.

    Maven Build 환경에 대한 구조를 파악하기 전에 Maven 자체가 무엇이며 Maven으로 할 수 있는 것이 무엇인지 알아보았습니다.

    1. Maven 이란?

    Maven은 Java를 위한 Build Tool이자 프로젝트 전반의 LifeCycle 관리를 목적으로 하는 툴입니다. Maven을 설치하면 Console 명령어를 이용하여 사용할 수 있습니다. 하지만 Eclipse를 이용하면 Console 명령어가 아닌 m2e plugin을 활용하여 빌드를 사용할 수 있습니다. Maven에서 핵심 키워드는 pom.xml, phase , goal이며 아래에서 이에 대한 설명을 진행하도록 하겠습니다.

    2. pom.xml?

    POM은 Project Object Model의 약자로 Maven을 이용하는 Project의 root에 존재합니다. 해당 프로젝트에 대한 Dependency, Information 같은 것들을 작성하며 사용합니다. pom.xml에 대한 설명은 다음의 링크 (POM Reference) 에서 확인할 수 있습니다. 그럼 여기서 가장 많이 사용되는 label들에 대한 파악을 해보도록 하겠습니다.

    2.1. Maven Coordinates (GroupId,ArtifactId,Version,Packaging)

    
    
      4.0.0
     
      com.home
      my-project
      1.0
      war
    
    
    
    

    'groupId:artifactId:version'는 모두 pom.xml 파일에 있어야 하는 조건입니다. 'groupId, vesrion'의 경우 부모로 부터 선언된경우라면 필요하지 않습니다. 'groupId'는 조직이나 프로젝트의 일반적인 고유 값이며, 'artifactId'는 일반적으로 프로젝트로 알려져있는 이름입니다. 'version'은 이름을 짓는것에 마지막 규칙으로 artifact 저장소의 코드 버전을 나누는 것입니다.

    'packaging'은 프로젝트의 artifact type입니다. 기본으로 지정되있는 것은 jar이며, 새로운것을 지정하기 위해서는 'pom, jar, maven-plugin, ejb, war, ear, rar, par'이 있습니다. 이 정의는 build lifecycle 지정에 사용됩니다.

    3. phase

    말 그대로 단계를 뜻하며, Maven 환경에서의 LifeCycle 단계를 의미한다. LifeCycle은 3가지로 분류할 수 있다.

    3.1. Default LifeCycle Maven Phase

    1. validate 프로젝트가 정상적인지, 필요한 모든 정보가 있는지 검증하는 단계.
    2. compile 프로젝트의 소스코드를 컴파일 하는 단계.
    3. test unit test framework를 이용하여 테스트 소스코드 컴파일 하는 단계.
    4. package 컴파일된 소스를 배포하는 형태로 만드는 단계.
    5. integration-test통합 테스트를 할 수 있는 환경에 패키지를 배포하는 단계.
    6. verify package를 실행하고 검증해보는 단계
    7. install 패키지를 로컬 저장소에 설치하는 단계
    8. deploy 만들어진 패키지를 외부 저장소에 release 하는 단계

    3.2. Other LifeCycle

    1. clean Build전에 생성된 artifact를 청소하는 단계
    2. site 해당 프로젝트의 Document를 생성하는 단계

    4. Maven Install

    기본적으로 Eclipse나 Intellij 를 사용한다면 해당 IDE 에서 빌드 작업을 수행하기 때문에 따로 maven을 설치하지 않아도 실행되었다. 하지만mvn install 와 같은 CommandLine 명령어를 수행시키기 위해서는 maven을 install을 컴퓨터에 해두어야 한다. 먼저 Install을 하기 위해서는 Download Link 링크로 이동하여 다운로드 받으면 된다.

    윈도우 환경에서 진행하였기 때문에 zip 파일을 다운로드 받았다. Download 받은 뒤 Window 환경이었기 때문에 환경변수 아래와 같이 지정해 주었다.

    - MAVEN_PATH
    - C:\Development\maven\apache-maven-3.3.3\bin
    
    - Path
    - ...;%MAVEN_APTH%; ...
    

    위의 작업을 실행하면 Command Line에서의 mvn -v를 치면 아래와 같은 화면이 출력된다.

    참고 논문 및 사이트

    1. Maven Documentation - https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html

    2. 메이븐(Maven) 5분 개념 설명 http://qwertyju.egloos.com/5716209

    3. Maven 기초 사용법 http://javacan.tistory.com/129

    Sunday, September 13, 2015

    [AndroidAnnotations_Build] 3. AndroidAnnotations Build Option

    Gradle build script에서 android-apt script 내용 변경이 가능합니다. CustomizeAnnotationProcessing 페이지에서 공식 wiki 를 확인 가능합니다. 간략하게 특정 기능에 대한 어떤 Option이 있는지 확인해보도록 하겠습니다.

    1. androidManifestFile

    Default - 부모 폴더에서 부터 재귀적으로 AndroidManifest.xml을 찾습니다. 이 부분에 있어서 확인이 필요하다고 생각한 부분은 부모 폴더가 어디인지를 파악하지 못하였습니다.(이걸 찾아야할텐데..) 따라서 아래와 같이 작성해주어야만 찾습니다.

    apt {
        arguments {
            androidManifestFile variant.outputs[0].processResources.manifestFile
        }
    }
    

    위과 같이 작성해야만 되는데 위의 script내용물 또한 자세한 파악을 못하였습니다. 이 부분에 대해서는 파악을 하는데로 포스팅 업데이트를 하도록하겠습니다.

    2. resourePackageName

    Default의 경우 AndroidManifest.xml의 Application Package로 부터 추출되어집니다. 따라서 아래 코드의 package 부분이 추출되어집니다.

     
    

    따로 R class에 대한 변경이 필요하지 않을 경우에는 작성하지 않아도 됩니다. 작성이 필요하다면 아래 script와 같이 작성해주면됩니다.

    apt{
        arguments {
            resourcePackageName "your.package.name"
        }
    }
    

    3. logFile, logLevel, logAppenderConsole, logAppenderFile

    이 옵션의 경우 AndroidAnnotations에서 사용하는 커스텀 로그로 파일 생성시에 작성되는 로그입니다. 해당 되는 로그파일은 {outputFolder}/andrioannotations.log에 작성되어진다.

    logLevel은 trace, debug, info, warn, error 이고 default 값은 deubg이다. Log가 출력되는 곳의 기본은 "FileAppender(File)", "MessageAppeder(IDE)" 이다. logAppenderConsole의 경우는 true,false값이며 console에 log가 찍히도록 해준다. logAppenderFile의 경우에는 default는 true이며, false로 할경우 File에 작성되는 log를 제외해준다. 작성방법은 다음과 같다

    apt{
        arguments {
            logLevel 'INFO'
            logFile '/var/log/aa.log'
            logAppenderConsole false
            logAppenderFile false
        }
    }
    

    Saturday, September 12, 2015

    [AndroidAnnotations_Build] 2. Setting AndroidAnnotations Build (Use Gradle)

    1번 포스팅에 이어서 AndroidAnntations를 빌드하는 방법에 대한 포스팅을 하도록 하겠습니다. 동일하게 AndroidStudio를 사용하고 Gradle을 이용하였습니다. 빌드를 하기 위해서는 몇가지 세팅 방법이 필요합니다. 이 부분은 AndroidAnnotations Wiki Page를 참고하였습니다. 해당 페이지의 Gradle 페이지를 참고하였습니다.

    1. Annotations Processor 사용을 위한 Annotation processor 활성화 시키기

    먼저 AndroidAnnotations를 사용하기 위해서는 Android Studio의 Compile 시점의 Annotation Processor를 활성화 시켜야 합니다. " File → Other Settings → Default Settings... "으로 들어갑니다.

    다음 과정으로는 " Build, Execution, Deployment → Compiler → Annotation Processor "으로 들어갑니다. 들어가게되면 아래의 사진과 같이 Anntation Processor가 활성화 되지 않은것을 확인할 수 있습니다.

    여기서 " Enable annotation proceesing " Check Box를 아래 사진과 같이 활성화 시켜주면 됩니다.

    해당 작업을 "OK" 버튼을 눌러 활성화 시켜주면 Annotation Processor가 활성화 되게 됩니다.

    2. Gradle 빌드에 android-apt 설정 추가하기

    2.1 android-apt란?

    Android-apt Bitbucket 홈페이지 Readme를 통하여 확인할 수 있습니다. Android-apt 는 gradle plugin으로 Android studio 에서 annotation processor를 사용할 수 있도록 도와주는것입니다. 이것은 마지막 apk 추출이나 library에는 적용이 안되며 오직 compile 시에만 작동할 수있도록 도와주는것이며, annotations process를 통해 생성되는 코드에 대한 소스 경로를 결정해줍니다.

    2.2 Gradle 빌드에 android-apt 추가

    먼저 상위 build.gradle 파일에 아래와 같이 dependency를 추가해줍니다. ('// *' 이 추가된 라인입니다.)

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:1.3.0'
            classpath "com.neenbedankt.gradle.plugins:android-apt:1.7" // ** 
        }
    }
    
    allprojects {
        repositories {
            jcenter()
        }
    }
    
    

    다음은 Application의 depenecy의 gradle을 아래와 같이 depenecy와 정의를 추가해 줍니다.('// *' 이 추가된 라인입니다.)

    def AAVersion = "3.3.2" // *
    
    apply plugin: "com.android.application"
    apply plugin: "com.neenbedankt.android-apt" // *
    
    android {
        compileSdkVersion 23
        buildToolsVersion "23.0.0"
    
        defaultConfig {
            applicationId "com.juranoaa.myapplication"
            minSdkVersion 23
            targetSdkVersion 23
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
            }
        }
    }
    
    apt { // *
        arguments { // *
            androidManifestFile variant.outputs[0].processResources.manifestFile // *
        } // *
    } // *
    
    dependencies {
        compile fileTree(dir: "libs", include: ["*.jar"])
        apt "org.androidannotations:androidannotations:$AAVersion" // *
        compile "org.androidannotations:androidannotations-api:$AAVersion" // *
    }
    

    그리고 AndroidAnnotations를 사용한 Activity를 추가해줍니다.

    package com.juranoaa.myapplication;
    
    import android.app.Activity;
    
    import org.androidannotations.annotations.EActivity;
    import org.androidannotations.annotations.Fullscreen;
    
    @Fullscreen
    @EActivity(R.layout.activity_main)
    public class AAMainActivity extends Activity{
    }
    

    "Build → Rebuild Project"을 선택하게 되면 AndroidAnnotations Build 가 수행되게 됩니다.

    완료가 되면 다음과 같이 generated 폴더 하위에 다음과 같은 클래스 파일이 생성되게 됩니다.

    다음 포스팅에서는 AndroidAnnotations build시 사용되는 내용에 대해서 포스팅하도록 하겠습니다.

    참고 논문 및 사이트

    1. AndroidAnnotations Wiki Configuration https://github.com/excilys/androidannotations/wiki/Configuration