IT_Programming/Android_Java

Android에서 메소드 수가 65536을 넘었을 때의 대처 방법

JJun ™ 2015. 4. 9. 13:36



 출처: 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 를 사용하여 메소드의 수를 보면 얼마나 메소드 수를 절약 할 수 있었는지를 확인할 수 있습니다.

이상. 실수 나 추가사항이 있으면 지적 부탁 드립니다!