출처: http://qiita.com/konifar/items/d98c78facbaae63badca
Android 어플리케이션으로 메소드 수가 64k (65536)을 초과하면 빌드시 or 설치할 때 오류가 발생할 수 있습니다.
라이브러리를 포함한 전체 코드에서 계산되므로 다양한 사용하고 상당히 빨리 상한 초과하게됩니다.
상당히 めんどくさかっ 때문에 대처법을 정리합니다.
1. 빌드시 오류가 발생할 수 있다.
오류가 발생 빌드조차하지 못할 수 있습니다.
1. gradle에서 jumboMode 옵션을 활성화하려면
이런 오류가 발생했을 때는 jumboMode를 사용하면 해결할 수 있을지도 모릅니다.
com.android.dex.DexException : Can not merge new index 65576 into a non-jumbo instruction!
build.gradle에 다음을 추가하여 테스트 해 봅시다.
build.gradle
android {
dexOptions {
jumboMode true
}
}
2. proguard를 사용
이런 오류가 나왔을 때는 열심히 메소드를 줄이거 Proguard를 사용하여 빌드하는 방법 밖에없는 것 같습니다.
메소드의 수를 줄이는 것은 힘든, 그래서 Proguard를 사용하도록했습니다.
java.lang.IllegalArgumentException : method ID not in [0, 0xffff : 65536
1. app / build.gradle에 설명을 추가
runProguard 옵션을 true로하여 proguardFiles을 지정합니다.
build.gradle
android {
buildTypes {
release {
runProguard true
proguardFiles getDefaultProguardFile ( 'proguard-android.txt') 'proguard-rules.pro'
}
}
}
2. proguard-rules.pro를 작성
proguard 파일은 응용 프로그램에 따라 생각하기 때문에 생략하지만,
예를 들어 gson을 사용하는 경우 이런 식으로 쓰지 않는다 작동하지 않거나합니다.
proguard-rules.pro
- keepattributes Signature
# Gson specific classes
- keep class sun.misc.Unsafe { * ;}
- keep class com.google.gson.stream. ** { * ;}
proguard는 조사 움직이면서 열심히 만들어 갈 수 밖에 없을 것입니다.
2. 설치시 오류가 발생할
빌드가 성공 apk를 만들 수도 Android2에서 설치 오류가 발생할 수 있습니다.
pkg : /data/local/tmp/com.konifar
Failure [INSTALL_FAILED_DEXOPT]
Facebook 엔지니어 의 노트에 자세한 원인이 써 있습니다.
During standard installation, a program called "dexopt"runs to prepare your app for the specific phone it 's being installed on. Dexopt uses a fixed-size buffer (called the "LinearAlloc"buffer) to store information about all of the methods in your app . Recent versions of Android use an 8 or 16 MB buffer, but Froyo and Gingerbread (versions 2.2 and 2.3) only have 5 MB. Because older versions of Android have a relatively small buffer, our large number of methods was exceeding the buffer size and causing dexopt to crash.
아무래도 보통의 설치시에는 「dexopt '라는 프로그램이 실행되는 것 같고, 그 처리는 모든 메소드의 정보를 일정 크기의 공간에 모아 넣는 것 같습니다. Android4 이상의 최신 버전이라고 8MB, 16MB의 공간이 확보되어 있는데, Android2.3 다음의 OS에서는 5MB 밖에없고, 그에 맞지 않는만큼 많은 메소드 수다와 dexopt 충돌하여 설치 오류가 될 것이라는 것입니다.
1. 메소드의 수 확인
실제로 얼마나 메소드 수인지를 확인합니다. dex-method-count 라는 도구를 사용하면
쉽게 apk의 메소드 수를 확인할 수 있습니다.
$ git clone https://github.com/mihaip/dex-method-counts.git
$ ./gradlew assemble # 빌드
$ ./dex-method-counts path / to / app.apk # or .zip or .dex or directory
이런 식으로 출력됩니다. 우선 출력 해 보면 절약 할 수있는 곳이 발견 될지도 모릅니다.
Read in 60366 method IDs.
<root> : 60366
: 8
android : 10815
accessibilityservice : 6
accounts : 8
animation : 2
app : 351
bluetooth : 2
content : 303
... (생략) ...
com : 39448
... ( 약어) ...
google : 18513
ads : 165
mediation : 134
admob : 24
customevent : 40
jsadapter : 38
2. 불필요한 라이브러리를 exclude하기
build.gradle 종속성에 낭비가 본래 불필요한 라이브러리를 include 버리고있는 것이 있습니다.
자신의 경험인데, support-v4-13.0.0
와 support-v4-13.1.0
가 모두 가져 있던 수 있고,
build.gradle의 compile 부분에 exclude 옵션을 붙여 support 라이브러리를 넣지 같이 처리했습니다.
build.gradle
compile ( 'com.aviary.android.feather.sdk : aviary-sdk : 3.4.3.351') {
exclude module : 'support-v4'
}
3. google play services 불필요한 패키지를 지우는
2014/12/15 (월) 추가
최신 google-play-services6.5 은 모듈 단위로 사용할 수있는 것 때문에 문제없는 것입니다.
GoogleAnalytics과 GoogleMap 등을 이용할 수 라이브러리 com.google.android.gms : play-services
이지만,
사실 사용하는 기능은 하나 나 둘 개의 것이 많은 것이 아닐까요. com.google
는 18513 개나 메소드가 있으므로
이를 절약하는 것만으로 꽤 메소드의 수가 줄어 듭니다.
1. strip_play_services.gradle을 만들자
strip_play_services.gradle 라는 gradle task가 공개되어 있기 때문에, 그것을 사용합니다.
app / strip_play_services.gradle
를 배치합니다.
strip_play_services.gradle
// taken from https://gist.github.com/dmarcato/d7c91b94214acd936e42
def toCamelCase (String string) {
String result = ""
string.findAll ( "[^ \\ W +") {String word ->
result + = word.capitalize ()
}
return result
}
afterEvaluate {project ->
configuration runtimeConfiguration = project.configurations.getByName ( 'compile')
ResolutionResult resolution = runtimeConfiguration.incoming.resolutionResult
// Forces resolve of configuration
ModuleVersionIdentifier module = resolution.getAllComponents () .find {
it.moduleVersion.name.equals ( "play-services")
} .moduleVersion
String prepareTaskName = "prepare $ {toCamelCase ("$ {module.group} $ {module.name} $ {module.version} ") } Library "
task prepareTask = project.tasks.findByName (prepareTaskName)
File playServiceRootFolder = prepareTask.explodedDir
// Add the stripping to the existing task that extracts the AAR containing the original classes.jar
prepareTask.doLast {
// First create a copy of the GMS classes.jar
copy {
from (file (new file (playServiceRootFolder "classes.jar")))
into (file (playServiceRootFolder))
rename {fileName ->
fileName = "classes_orig.jar"
}
}
// Then create a new .jar file containing everything from the first one except the stripped packages
tasks.create (name : "stripPlayServices"+ module.version, type : Jar) {
destinationDir = playServiceRootFolder
archiveName = "classes.jar"
from (zipTree (new File ( playServiceRootFolder "classes_orig.jar"))) {
exclude "com / google / android / gms / actions / **"
exclude "com / google / android / gms / appindexing / **"
exclude "com / google / android / gms / appstate / ** "
exclude"com / google / android / gms / analytics / ** "
exclude"com / google / android / gms / auth / ** "
exclude"com / google / android / gms / cast / ** "
exclude"com / google / android / gms / drive / ** "
exclude"com / google / android / gms / fitness / ** "
exclude"com / google / android / gms / games / ** "
exclude"com / google / android / gms / identity / ** "
exclude"com / google / android / gms / panorama / ** "
exclude"com / google / android / gms / plus / ** "
exclude"com / google / android / gms / security / ** "
exclude"com / google / android / gms / tagmanager / ** "
exclude"com / google / android / gms / wallet / ** "
exclude"com / google / android / gms / wearable / ** "
// exclude"com / google / ads / ** "
// exclude"com / google / android / gms / ads / ** "
// exclude"com / google / android / gms / gcm / ** "
// exclude "com / google / android / gms / location / **"
// exclude "com / google / android / gms / maps / **"
}
} .execute ()
delete file (new File (playServiceRootFolder "classes_orig.jar "))
}
}
2. app / build.gradle에 한 줄 추가
build.gradle에 apply from
추가합니다.
build.gradle
apply from : 'strip_play_services.gradle'
3. proguard-rules.pro에 한 줄 추가
Proguard를 사용하는 경우에는 proguard-rules.pro에 한 줄 추가합니다.
proguard-rules.pro
- dontwarn com.google.android.gms. **
4. rebuild하기
rebuild하고 apk를 다시 만듭니다. build하기 전에 clean 해 두는 것이 좋다고 생각합니다.
dex-method-count 를 사용하여 메소드의 수를 보면 얼마나 메소드 수를 절약 할 수 있었는지를 확인할 수 있습니다.
이상. 실수 나 추가사항이 있으면 지적 부탁 드립니다!