블로그

  • 에셋번들과 리소스에 대한 가이드

    이 글은 유니티 튜토리얼을 번역한 글 입니다. 원문은 여기에서 확인하실 수 있습니다.

    이 글은 총 5개의 챕터로 구성되며 아래와 같습니다.

    1. [번역] 에셋과 오브젝트, 그리고 직렬화
    2. [번역] RESOURCES 폴더
    3. [번역] 에셋번들 기초
    4. [번역] 에셋번들 사용 패턴

    이 시리즈는 유니티 엔진에서 에셋1과 리소스를 관리하는 것에 대한 깊이있는 이야기를 하고자 합니다. 유니티의 에셋과 직렬화 시스템에 대한 심도있는 소스 단계(source-level) 지식을 전문 개발자에게 제공하고자 합니다. 이 글에서는 유니티 에셋번들 시스템의 기술적인 뒷받침들과 현재 그것들을 적용시키기 위한 모범 사례들을 검토합니다.

    이는 총 4개의 챕터로 구성되어 있습니다.

    • [번역] 에셋과 오브젝트, 그리고 직렬화 -이 글에서는 유니티가 어떻게 에셋을 직렬화하고 에셋 간의 참조를 다루고 있는지에 대한 저수준의 상세한 내용을 다룹니다. 전체 가이드에서 사용되는 기술 용어를 정의하고 있는 챕터이므로, 독자들은 이 챕터부터 읽어볼 것을 강력히 추천합니다.
    • [번역] RESOURCES 폴더 – 이 글에서는 내장되어 있는 Resources API에 대해 이야기합니다.
    • [번역] 에셋번들 기초 – 이 글은 챕터 1에 기반하여, 에셋 번들이 어떻게 동작하는지와 에셋 번들을 로딩하는 부분, 에셋 번들에서 에셋을 로딩하는 부분에 대한 이야기를 다룹니다.
    • [번역] 에셋번들 사용 패턴 – 이 글은 에셋 번들의 실질적인 사용에 관련한 많은 주제를 다루고 있습니다. 이 글은 몇개의 섹션(section)으로 구성되어 있는데, 에셋을 에셋번들에 할당하는 것, 로드된 에셋을 관리하는 것, 개발자가 에셋 번들을 사용할 때에 직면하게 되는 많은 함정에 대해 다룹니다.


    주의 : 이 가이드에서 사용되는 오브젝트(Objects)와 에셋(Assets)은 유니티의 API 네이밍 관습(convention)과는 다릅니다.

    이 가이드에서는 데이터(data)를 오브젝트(Objects)라 말하고 이는 많은 유니티 API에서 에셋이라 불립니다. 예를들면 AssetBundle.LoadAsset 이나 Resources.UnloadUnusedAssets 처럼 말이죠.

    반면 이 가이드에서 파일(files)은 에셋(Assets)이라 말하고 이는 유니티 API에서는 거의 사용되지 않고 있습니다. 이것이 유니티 API에서 사용되는 경우에는 AssetDatabase나 BuildPipeline 같은 빌드 관련된 코드에서만 사용되고 있습니다. 이 경우에 유니티 API에서는 이를 파일(files)이라 지칭하고 있습니다.

  • Grdle 빌드변형(빌드 타입, 앱 서명 첨부하기, 제품 특성)

     Gradle을 도입한 목표중 하나는 단일 소스 코드로 목적에 맞는 다양한 APK 생성입니다. 모듈 내부에서 디버그, 릴리즈와 같은 빌드 타입별로 세부사항을 변경하거나, lite, full 버전 과 같이 기능 일부를 비활성화 할 수 있습니다.(feature 변경) 
     빌드 변형은 빌드타입과 제품특성을 합한 개념입니다. 어떤 모듈에 3가지 빌드타입과 4가지 제품 특성이 존재한다면 빌드 변형은 3*4=12가지 경우입니다. 이번 포스팅에서 빌드타입에 대해 살펴보도록 하겠습니다.

    빌드타입

    build type 에는 debug와 release가 존재합니다. 디버깅이 포함된 apk이냐 마켓에 배포할 apk냐에 따라 구분되어집니다. 
    debug build는 default대로 진행하면 되고, release 빌드때는 pro guard 를 비활성화 하였습니다.

     buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    다음과 같이  minifyEnabled true로 하면 프로가드가 활성화됩니다. 소스코드난독화하여 역컴파일을 방지하게 됩니다.
    프로젝트 폴더에들어가서 gradlew :app:assemble 명령을 치면 빌드가 완료되고나서 debug, release apk 가 생성됨을 확인하실 수 있습니다.
    또한, 그렇게 하기 귀찮으시면 gradle 콘솔(안드로이드 프로젝트)을 열어서 assemble 태스크를 실행하시면 되겠습니다. app모듈에 build 그룹에 있습니다.

    다음과 같이 assemble task를 실행시키고나면, debug용도와 release용도의 apk가 생성됨을 확인하실 수 있습니다.

    앱 서명 첨부하기

    release 용을 빌드할떄는 앱서명(siging)에 관한정보를 직접 지정하여 등록하셔야 합니다. signing을 할때 module build.gradle을 수정하는데, android 블록아래에 signingConfigs를 입력합니다. 물론 siginingConfigs는 buildTypes 블록보다 먼저 정의되어야 합니다.

    signingConfigs{
        release{
            storeFile file('app.keysave')
            storePassword 'keypass'
            keyAlias 'key'
            keyPassword 'mypassword'
        }
    }
    
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    그런데 문제는, 앱서명에 관한정보를 직접 썼다는게 보안상의 문제로 될수있습니다. 그러므로 담당자 서버의 환경변수에 별도로 지정하는게 좋겠습니다.

    signingConfigs{
        release{
            storeFile file('app.keysave')
            storePassword System.getenv("KEYSTORE_PASSWORD")
            keyAlias System.getenv("KEY_ALIAS")
            keyPassword System.getenv("KEY_PASSWORD")
        }
    }

    그리고 환경변수를 윈도우에서 설정해줍시다. 예로들어 KEYSTORE_PASSWORD라는 환경변수를 읽어오기 위해서는 ORG_GRADLE_PROJECT_KEYSTORE_PASSWORD 라고 환경변수 이름을 짓고, 변수값을 넣어서 생성하면 gradle에서 환경변수로 값을 얻어오실 수 있습니다.

    제품 특성

    빌드타입이 빌드의 속성을 변경하는것이라고 앞서 설명드렸습니다. 제품 특성은 리소스 교체 혹은 특정 feature를 활성화 또는 비활성화 시킬 수 있습니다. module build.gradle에 기술하시면 되겠습니다.

    제품 특성 생성 해보기

    제품 특성 생성하려면 android 블록에 productFlavor 블록을 추가합니다. lite와 full이라는 제품 특성 추가해보겠습니다.

    productFlavors{
        lite{
            applicationId 'com.lite.HelloWorld'
        }
        full
    }

    .

    제품 특성 확인

    추가된 제품 특성을 확인하려면 Android Studio IDE 좌측 하단에 세로방향으로 위치한 Build Variants 창을 활용합니다. app모듈에 build Varient를 선택해보시면, full,lite 조합 release, debug로 나오는것은 확인하실수있습니다. 2*2 =4가지경우가있네요. 원하시는 값을 선택후 build apk를 하였더니 아래그림처럼, app-gull-release apk가 생성됨을 확인할 수 있었습니다.

    제품 특성 활용

    제품특성을 간단히 적용하려면 AndroidManifest.xml 을 변경한후 재빌드 하면 되긴합니다. 엄청 번거롭죠. 그러나 제품특성을 활용하면 더욱 쉽고 간편하게 적용시킬 수 있습니다. 예를든다면 lite 버전의 로고와 app label을 변경합니다.  app 모듈의 src폴더 아래 lite 폴더를 만들고 AndroidManifest.xml 파일을 추가합니다. Project View로 진행하시면 됩니다.

    프로젝트뷰로 다음과 같이 lite폴더에 AndroidManifest.xml 파일을 추가하였습니다.
    그리고 Android 뷰로 돌아오면 lite버전의 AndroidManifest.xml 파일이 추가된것을 확인하실 수 있습니다. 추가된 파일에는 (lite)라고 추가됨을 확인하실 수 있습니다. 
    제품특성은 module build.gradle의 android.defaultConfig블록의 속성값을 공유하게 됩니다. defaultConfig블록은 간단히 요약하면 AndroidManifest.xml 의 내용 중 gradle로 재정의할 수 있는 속성들입니다. 또한 제품특성은 소스 코드를 재정의할 수 있습니다. 변경량은 최소화 하는게 좋습니다.

    제품 특성으로 특정기능 활성화 (Feature On)

     제품특성을 활용하면 전체 app,의 기능 중 일부를 활성화 또는 비활성화 할 수 있습니다. 예를들어 full 제품 특성에서는 현재 시간 표시 기능을 활설화 하고 demo제품 특성에서는 현재시간을 보여주지 않게 합니다. 모듈 build.gradle에서 BuildConfig변수를 활용하면 됩니다.

    productFlavors{
       full{
      buildConfigField "boolean", "SHOW_CURRENT_TIME", "true"
      }
       lite{
       buildConfigField "boolean", "SHOW_CURRENT_TIME", "false"
      }
    }

    SHOW_CURRENT_TIME이라는 boolean 변수를 추가했습니다. 내용을 입력하고 SYNC NOW버튼을 누르면 변수가 자동으로 생성되어 소스코드에서 참조할 수 있습니다.

     if(BuildConfig.SHOW_CURRENT_TIME){
    }

    실제확인하려면 app/build/generated/source/buildConfig/lite/debug/com/프로젝트 폴더에 BuildConfig클래스를 열어봅니다. applicationid등 빌드스크립트가 생성한 다양한 변수가 있는것을 확인하실 수 있습니다.

  • 안드로이드 Gradle Test

    Android application Test 방법

    크게 2가지가 있습니다. 안드로이트 테스트(Instrumentation Test)와 Local PC의 JVM을 활용하는 새로운 개념의 Local Unit Test 입니다. 

    참고) 로컬 유닛 테스트는 로컬 JVM을 활용하기 때문에 타깃 디바이스와 연결피 필요없습니다. 그리고 테스트 코드의 전체 실행속도가 향상되는 효과가 있습니다. 안드로이드 테스트에서는 APK를 생성하여 타깃 디바이스에 설치하고 실행하는 과정에서 시간 소모가 많은 편입니다.
    Gradle을 활용화여 안드로이드 테스트 코드와 로컬 유닛테스트코드를 실행하는 방법을 알아보겠습니다.

    로컬 유닛 테스트

    app module의 build.gradle을 보시겠습니다. dpendencies부분에 아래와 같이 testCompile이 웬만하면 디폴트로 지정되어있으실겁니다.

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:26.0.0-alpha1'
    }

    그리고 ExampleUnitTest 클래스를 보시면 아래와 같이 코드가 미리작성되어있는것을 확인하실 수 있습니다. Junit4에서는 테스트코드에 @Test라는 애너테이션을 부착합니다.

    public class ExampleUnitTest {
        @Test
        public void addition_isCorrect() throws Exception {
            assertEquals(4, 2 + 2);
        }
    }

    해당 unitTest 클래스 오른쪽 마우스로누르고, run “ExampleUnitTest” 선택시 정상적으로 테스트됩니다.
    로컬유닛테스트를 실행할때 호출되는 중요한 Gradle Task는 다음과 같습니다.

    :app:mockableAndroidJar UP-TO-DATE
    :app:compileDebugUnitTestSources UP-TO-DATE

    1. mock(app:mockableAndroidJar ) 테스크는 안드로이드의 android.jar 파일을 mock 인터페이스로 컴파일합니다. 로컬 유닛 테스트가 동작하려면 안드로이드 코드에 대한 mock 인터페이스가 필요합니다. mock 인터페이스 호출 시, 가짜객체를 주입하는 경우도 있습니다. 가짜 객체(mock object)를 이용한 테스트 기법은 구글문서를 참고하십시오.
    mock 테스트 실행결과는 /build/generated/mockable-android-26.jar(26은 버전명) 입니다. 
    2. complieDebugUnitTestSources 태스크입니다. 태스크 이름자체대로 디버그 모드로 로컬 유닛 테스트 코드를 컴파일합니다. 릴리즈 모드일때는 릴리즈 이름에 맞게 테스크가 실행되는것을 확인하실 수 있습니다. 
    또한, 콘솔에서도 테스트코드를 실행하실 수 있습니다. HTML로 결과가 출력되는데요.  프로젝트 폴더에서 cmd창을 연 후,
    > gradlew :app:testDebug 
    와같이 치신후 엔터를 누르고, app/build/reports/tests/debug 폴더에 테스트 결과를 확인하실 수 있습니다.

    안드로이드 테스트

    안드로이드 테스트코드는 Activity, Fragment, ButtonView, TextView 등 UI컴포넌트와, 유틸리티클래스와 같은 Java클래스 모두 테스트할 수 있습니다. 다만 에뮬레이터나, 타깃 디바이스에서 실행해야하는 제약이 있습니다 .
    저는 MainActivityTest라는 클래스를 만들고 아래와 같이 코드작성 후, 실시해보겠습니다.

    public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
        public MainActivityTest() {
            super(MainActivity.class);
        }
    
    
        public  void testHelloLabel(){
            Activity ac = getActivity();
    }

    app 모듈의경우 /app/src/androidTest/java 폴더에 위치하고있습니다. 실행하는 방법은 로컬 유닛테스트와 유사합니다.
    오른쪽마우스 누르고, run “MainActivityTest” 를 실행합니다.
    :app:compileDebugAndroidTestSources
    이번에 실행된 gradle test는 unitTest가 아닌, AndroidTest로 바뀐것을 확인하실 수 있습니다.
    콘솔에서도 역시 확인하실 수 있습니다.
    gradlew :app:connectedAndroidTest + 엔터 치시면됩니다.gradle 콘솔창에 뜨듯이 진행후, 완료구문을 확인하실 수 있습니다.

    Espresso 연동하기

    Espresso는 Android Testing Support Library에 편입되었습니다. 강력한 테스팅 도구이죠. 연동하는 방법을 살펴보시겠습니다.
    app모듈의 build.gradle의 내용 추가입니다.

    defaultconfig{
      testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    그다음 denependencies블록에 추가할 내용입니다.

    androidTestCompile 'com.android.support.test:runner:0.4.1'
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
    compile 'com.android.support:appcompat-v7:23.0.1'

    로컬 유닛 테스트의 제약사항

    로컬 유닛테스트 같은 경우 andoir.util.Log 클래스에 대한, mock 인터페이스가 제공되지안항 오류를 낼때가있습니다.
    이를 피하려면  모듈 build.gradle에 다음 내용을 추가해야합니다.

    android{
      testOptions{
        unitTests.returnDefaultValues = true   
      }
    }

    근본적으로 해결하려면 PowerMock과 같은 라이브러리를 활용하여 log 클래스의 static 메서드를 위한 가짜 객체를 주입하는 방법이 있습니다. PowerMock 관련 문서를 참조하시기 바랍니다.