Gradle

Gradle

최근 Android Studio를 통해 개발을 진행하게 되면서, 자연스럽게 Gradle을 사용하게 되었다. .gradle 확장자를 가진 파일을 통해 빌드 설정을 자유롭게 조정할 수 있다. 이 글에서는 Gradle을 이용해 배포 APK를 생성하는 방법과 함께 ProGuard를 사용법을 공유하겠다.

Gradle 환경에서 배포 APK 생성하기

배포 APK에는 서명이 되어 있어야 하는데, 이를 위해서는 keystore 파일과 그 암호, 키 별칭, 키 암호가 필요하다. 디버그 APK에도 서명을 하지만, 알려진 keystore 암호와 키 별칭, 키 암호를 사용한다. 배포 APK의 서명을 위해 프로젝트의 build.gradle 파일에 다음 코드를 추가하면 된다.

android {
    // ...

    signingConfigs {
        release {
            storeFile file("YOUR_KEYSTORE_PATH")
            storePassword "YOUR_KEYSTORE_PASSWORD"
            keyAlias "YOUR_KEY_ALIAS"
            keyPassword "YOUR_KEY_PASSWORD"
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

이때 buildTypes 아래의 debug 항목은 굳이 명시하지 않아도 기본적으로 생성되며, 이 buildType디버그 keystore와 키를 사용하도록 설정되어 있다.

다만 이때 build.gradle 파일에 keystore 암호와 키 암호가 평문으로 들어가게 되는데, 소스를 공개하고 있는 등의 이유로 이를 피하고 싶다면 각각의 항목을 쉘 프롬프트에서 입력받을 수 있다.

signingConfigs {
    release {
        storeFile file(console.readLine("\n\$ Enter keystore path: "))
        storePassword new String(console.readPassword("\n\$ Enter keystore password: "))
        keyAlias console.readLine("\n\$ Enter key alias: ")
        keyPassword new String(console.readPassword("\n\$ Enter key password: "))
    }
}

그러나 이는 IDE를 통해 디버그 APK를 생성할 때 크래시를 내며, 이는 그때 코드의 consolenull이라서 발생하는 오류다. 이를 해결한 최종 코드는 다음과 같다.

signingConfigs {
    release {
        final Console console = System.console();
        if (console != null) {
            storeFile file(console.readLine("\n\$ Enter keystore path: "))
            storePassword new String(console.readPassword("\n\$ Enter keystore password: "))
            keyAlias console.readLine("\n\$ Enter key alias: ")
            keyPassword new String(console.readPassword("\n\$ Enter key password: "))
        }
    }
}

디버그 APK와 배포 APK의 패키지 이름이 같으면 APK의 서명이 서로 달라 개발과 디버깅에 어려움이 있다. 이를 해결하기 위해서 buildTypes 아래에 debug 항목을 선언하여 디버그 APK의 패키지 이름을 바꿀 수 있고, 추가로 버전명도 바꿀 수 있다.

buildTypes {
    debug {
        packageNameSuffix '.debug'
        versionNameSuffix '-debug'
    }
}

이제 터미널에서 다음 명령을 실행하면 디버그 APK와 배포 APK를 각각 얻을 수 있다. 물론 디버그 APK는 IDE로도 생성할 수 있다.

$ ./gradlew assembleDebug
$ ./gradlew assembleRelease

Gradle은 캐멀케이스 단축키를 지원해서 aR에 해당하는 다른 명령이 없는 한 assembleRelease 대신 aR을 사용할 수 있다.

ProGuard 사용하기

배포 APK를 생성할 때 ProGuard를 사용할 수도 있는데, build.gradle에 다음 코드를 추가하면 된다.

buildTypes {
    release {
        runProguard true
        proguardFile getDefaultProguardFile('proguard-android.txt')
    }
}

getDefaultProguardFile()SDK에 위치한 해당 이름의 파일을 가져와 적용한다. proguard-android.txtproguard-android-optimize.txt가 있으며 앞의 것은 최적화를 수행하지 않고, 뒤의 것은 최적화를 수행한다.

추가적인 다른 proguardFile을 더 적용하고 싶다면 proguardFiles를 사용하면 된다.

buildTypes {
    release {
        runProguard true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
    }
}

다른 라이브러리 프로젝트를 가져다 사용하고 있을 경우, ProGuard 사용에 있어 주의해야 한다.

ActionBarSherlock

ActionBarSherlock의 경우 ProGuard를 사용할 때, 다음 규칙을 추가하라고 명시하고 있다.

-keep class android.support.v4.app.** { *; }
-keep interface android.support.v4.app.** { *; }
-keep class com.actionbarsherlock.** { *; }
-keep interface com.actionbarsherlock.** { *; }

-keepattributes *Annotation*

Crashlytics

Crashlytics는 이미 ProGuard를 사용한 라이브러리들을 다시 ProGuard가 검사할 필요 없게 해서 빌드 시간을 줄이는 팁을 제공하고 있다.

-libraryjars libs
-keep class com.crashlytics.** { *; }

Android Support Library는 이미 소스가 공개되어 있기 때문에 코드 난독화가 필요하지 않다.

-libraryjars libs
-keep class android.support.v4.app.** { *; }
-keep interface android.support.v4.app.** { *; }

ProGuard를 이용해 코드 난독화 작업을 거치게 되면, 소스 파일의 줄 번호가 바뀌게 되어 Crashlytics의 스택 트레이스에서 정보를 얻기 어려울 수 있다. 소스 파일의 줄 번호 정보를 유지하려면 다음 문장을 추가한다.

-keepattributes SourceFile,LineNumberTable

다만 이 코드 때문에 난독화가 덜 되는 것 같다는 생각이 든다면, 파일 이름을 모두 "SourceFile" 문자열로 바꿀 수도 있다.

-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable

Google Play Services SDK

Google Play Services SDK 또한 필요한 클래스가 사라지는 것을 방지하기 위한 ProGuard 규칙을 제공하고 있다.

-keep class * extends java.util.ListResourceBundle {
    protected Object[][] getContents();
}

-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
    public static final *** NULL;
}

-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
    @com.google.android.gms.common.annotation.KeepName *;
}

-keepnames class * implements android.os.Parcelable {
    public static final ** CREATOR;
}

참고 목록