코드 축소 방법-dex의 65k 메서드 제한


91

많은 도서관 프로젝트에 의존하는 다소 큰 Android 앱이 있습니다. Android 컴파일러에는 .dex 파일 당 65536 개의 메서드 제한이 있으며이 수를 초과합니다.

메서드 제한에 도달하면 기본적으로 두 가지 경로를 선택할 수 있습니다 (적어도 내가 아는).

1) 코드 축소

2) 여러 dex 파일 빌드 ( 이 블로그 게시물 참조 )

나는 두 가지를 모두 살펴보고 내 방법 수가 너무 높아진 원인을 찾으려고 노력했습니다. Google Drive API는 12,000 개가 넘는 Guava 종속성으로 가장 큰 덩어리를 차지합니다. Drive API v2의 총 libs는 23,000 개가 넘습니다!

제 질문은 제가 어떻게해야한다고 생각하십니까? 내 앱의 기능에서 Google 드라이브 통합을 제거해야합니까? API를 줄이는 방법이 있습니까 (예, proguard를 사용합니다)? 여러 dex 경로로 가야합니까 (특히 타사 API를 다루는 것이 다소 고통스러워 보입니다)?


2
나는 당신의 앱을 좋아합니다. 모든 추가 라이브러리를 의사 apk형식으로 다운로드하는 것에 대해 생각해 보셨습니까 ? 나는 개인적으로보고 싶은 드라이브 통합
JBirdVegas

8
Facebook은 최근 Android 앱에서 거의 동일한 문제로 보이는 문제에 대한 해결 방법을 문서화했습니다. 유용 할 수 있습니다 : facebook.com/notes/facebook-engineering/…
Reuben Scratton

4
여러 dex 경로를 시작합니다. Google 드라이브에서 사용할 보조 dex 파일을 성공적으로 만들었습니다. 구아바가 필요한 사람이라면 누구에게나 기분이 나쁘다. : P 그것은 나를 위해 여전히 꽤 큰 문제입니다하지만
자레드 Rummler

4
방법을 어떻게 세나요?
Bri6ko 2014-06-02

1
여기에 몇 가지 추가 참고 사항이 있습니다. stackoverflow.com/questions/21490382(APK 에서 메서드 참조를 나열하는 유틸리티에 대한 링크 포함). 64K 제한은 몇 개의 댓글이 연결된 Facebook 문제와 관련이 없습니다.
fadden

답변:


69

Google이 마침내 dex 파일의 65K 메서드 제한을 초과하는 해결 방법 / 수정을 구현 한 것 같습니다.

65K 참조 제한 정보

Android 애플리케이션 (APK) 파일에는 Dalvik Executable (DEX) 파일 형식의 실행 가능한 바이트 코드 파일이 포함되며, 여기에는 앱을 실행하는 데 사용되는 컴파일 된 코드가 포함됩니다. Dalvik Executable 사양은 Android 프레임 워크 메서드, 라이브러리 메서드 및 자체 코드의 메서드를 포함하여 단일 DEX 파일에서 참조 할 수있는 총 메서드 수를 65,536 개로 제한합니다. 이 한도를 초과하려면 multidex 구성이라고하는 둘 이상의 DEX 파일을 생성하도록 앱 빌드 프로세스를 구성해야합니다.

Android 5.0 이전의 Multidex 지원

Android 5.0 이전의 플랫폼 버전은 앱 코드 실행을 위해 Dalvik 런타임을 사용합니다. 기본적으로 Dalvik은 앱을 APK 당 하나의 classes.dex 바이트 코드 파일로 제한합니다. 이 제한을 해결하기 위해 앱의 기본 DEX 파일의 일부가 된 multidex 지원 라이브러리 를 사용하여 추가 DEX 파일 및 포함 된 코드에 대한 액세스를 관리 할 수 ​​있습니다.

Android 5.0 이상에 대한 Multidex 지원

Android 5.0 이상은 기본적으로 애플리케이션 APK 파일에서 여러 dex 파일로드를 지원하는 ART라는 런타임을 사용합니다. ART는 응용 프로그램 설치시 classes (.. N) .dex 파일을 스캔하고 Android 장치에서 실행할 단일 .oat 파일로 컴파일하는 사전 컴파일을 수행합니다. Android 5.0 런타임에 대한 자세한 내용은 ART 소개를 참조하세요 .

참조 : 65,000 개 이상의 방법으로 앱 빌드


Multidex 지원 라이브러리

이 라이브러리는 여러 DEX (Dalvik Executable) 파일로 앱 빌드를 지원합니다. 65536 개 이상의 메서드를 참조하는 앱은 multidex 구성을 사용하는 데 필요합니다. multidex 사용에 대한 자세한 내용은 65,000 개 이상의 메서드를 사용하여 앱 빌드를 참조하세요 .

이 라이브러리는 Android 지원 라이브러리를 다운로드 한 후 / extras / android / support / multidex / 디렉토리에 있습니다. 라이브러리에는 사용자 인터페이스 리소스가 포함되어 있지 않습니다. 애플리케이션 프로젝트에 포함하려면 리소스없이 라이브러리 추가에 대한 지침을 따르세요 .

이 라이브러리의 Gradle 빌드 스크립트 종속성 식별자는 다음과 같습니다.

com.android.support:multidex:1.0.+이 종속성 표기법은 릴리스 버전 1.0.0 이상을 지정합니다.


Proguard를 적극적으로 사용하고 종속성을 검토하여 65K 메서드 제한에 도달하지 않도록해야합니다.


6
+1, 같은 사람이 답변을했는데 왜 사람들이 정답을 찬성하지 않나요?
Pacerier 2014

최소 API 레벨이 14가됩니다!
Vihaan Verma 2015

5
각 빌드에서 현재 메서드 수를 제공하기 위해 작은 Gradle 플러그인을 작성했습니다. 우리에게 도서관을 관리하는 도움이 되었습니까 - github.com/KeepSafe/dexcount-gradle-plugin
필립

53

당신은 사용하려면, 그의 multidex 지원 라이브러리를 사용할 수 있습니다 multidex

1) 종속성에 포함하십시오.

dependencies {
  ...
  compile 'com.android.support:multidex:1.0.0'
}

2) 앱에서 활성화 :

defaultConfig {
    ...
    minSdkVersion 14
    targetSdkVersion 21
    ....
    multiDexEnabled true
}

3) 앱에 대한 애플리케이션 클래스 가있는 경우 다음 과 같이 attachBaseContext 메서드 를 재정의합니다 .

package ....;
...
import android.support.multidex.MultiDex;

public class MyApplication extends Application {
  ....
   @Override
   protected void attachBaseContext(Context context) {
    super.attachBaseContext(context);
    MultiDex.install(this);
   }
}

4) 애플리케이션에 대한 애플리케이션 클래스 가없는 경우 android.support.multidex.MultiDexApplication 을 매니페스트 파일에 애플리케이션으로 등록 합니다. 이렇게 :

<application
    ...
    android:name="android.support.multidex.MultiDexApplication">
    ...
</application>

잘 작동합니다!


32

Play Services6.5 이상 도움말 : http://android-developers.blogspot.com/2014/12/google-play-services-and-dex-method.html

"Google Play 서비스 6.5 버전부터는 여러 개별 API 중에서 선택할 수 있으며 확인할 수 있습니다."

...

"이것은 모든 API에서 사용되는 '기본'라이브러리를 전 이적으로 포함 할 것입니다."

이것은 좋은 소식입니다. 예를 들어 간단한 게임의 경우 아마도 base, games그리고 아마도 drive.

"API 이름의 전체 목록은 다음과 같습니다. 자세한 내용은 Android 개발자 사이트에서 확인할 수 있습니다. :

  • com.google.android.gms : play-services-base : 6.5.87
  • com.google.android.gms : play-services-ads : 6.5.87
  • com.google.android.gms : play-services-appindexing : 6.5.87
  • com.google.android.gms : play-services-maps : 6.5.87
  • com.google.android.gms : play-services-location : 6.5.87
  • com.google.android.gms : play-services-fitness : 6.5.87
  • com.google.android.gms : play-services-panorama : 6.5.87
  • com.google.android.gms : play-services-drive : 6.5.87
  • com.google.android.gms : play-services-games : 6.5.87
  • com.google.android.gms : play-services-wallet : 6.5.87
  • com.google.android.gms : play-services-identity : 6.5.87
  • com.google.android.gms : play-services-cast : 6.5.87
  • com.google.android.gms : play-services-plus : 6.5.87
  • com.google.android.gms : play-services-appstate : 6.5.87
  • com.google.android.gms : play-services-wearable : 6.5.87
  • com.google.android.gms : play-services-all-wear : 6.5.87

Eclipse 프로젝트 내에서 수행하는 방법에 대한 정보가 있습니까?
Brian White

아직 해당 버전으로 업그레이드 할 위치가 아닙니다. 그러나 프로젝트가 Maven 기반이라면 Maven pom에서 해결해야합니다.
Csaba Toth

@ webo80 글쎄, 이것은 6.5.87 버전까지만 도움이됩니다. proguard가 사용하지 않는 기능을 제거한다는 Petey의 대답이 궁금합니다. 2rd party libs도 포함되는지 아니면 자신의 항목 만 포함되는지 궁금합니다. 나는 proguard에 대해 더 읽어야합니다.
Csaba Toth 2015 년

@BrianWhite 현재 유일한 해결책은 외부 도구로 .jar 파일을 제거하는 것 같습니다.
milosmns

1
@CsabaToth, 당신은 나를 구했습니다! 전체 'com.google.android.gms : play-services'대신 위 목록 중 몇 개만 추가했는데 그게 차이를 만들었습니다!
EZDsIt

9

6.5 이전 버전의 Google Play 서비스에서는 전체 API 패키지를 앱으로 컴파일해야했습니다. 경우에 따라 앱의 메서드 수 (프레임 워크 API, 라이브러리 메서드 및 자체 코드 포함)를 65,536 제한 미만으로 유지하기가 더 어려워졌습니다.

버전 6.5부터는 Google Play 서비스 API를 선택적으로 앱으로 컴파일 할 수 있습니다. 예를 들어 Google Fit 및 Android Wear API 만 포함하려면 build.gradle 파일에서 다음 줄을 바꿉니다.

compile 'com.google.android.gms:play-services:6.5.87'

다음 라인으로 :

compile 'com.google.android.gms:play-services-fitness:6.5.87'
compile 'com.google.android.gms:play-services-wearable:6.5.87'

자세한 내용은 여기 를 클릭 하세요.


일식에서하는 방법?
Hardik9850

7

사용되지 않은 메서드는 최종 빌드에 포함되지 않으므로 proguard를 사용하여 apk를 가볍게합니다. proguard 구성 파일에서 guava와 함께 proguard를 사용하기 위해 다음을 다시 확인하십시오 (이미 가지고 있다면 사과드립니다. 작성 당시에는 알려지지 않았습니다).

# Guava exclusions (http://code.google.com/p/guava-libraries/wiki/UsingProGuardWithGuava)
-dontwarn sun.misc.Unsafe
-dontwarn com.google.common.collect.MinMaxPriorityQueue
-keepclasseswithmembers public class * {
    public static void main(java.lang.String[]);
} 

# Guava depends on the annotation and inject packages for its annotations, keep them both
-keep public class javax.annotation.**
-keep public class javax.inject.**

또한 ActionbarSherlock을 사용하는 경우 v7 appcompat 지원 라이브러리로 전환하면 개인 경험을 기반으로 메서드 수를 많이 줄일 수 있습니다. 지침은 다음과 같습니다.


이 모습은 promissing 그러나 나는 가지고 Warning: butterknife.internal.ButterKnifeProcessor: can't find superclass or interface javax.annotation.processing.AbstractProcessor실행할 때./gradlew :myapp:proguardDevDebug
ericn

1
그러나 개발 중에는 proguard가 일반적으로 실행되지 않으므로 (마지막으로 Eclipse에서는 아님) 릴리스 빌드를 수행 할 때까지 축소의 이점을 누릴 수 없습니다.
Brian White

7

Jar Jar Links 를 사용 하여 Google Play 서비스 (16K 메서드!)와 같은 거대한 외부 라이브러리를 축소 할 수 있습니다 .

귀하의 경우 common internaldrive하위 패키지를 제외 하고 Google Play 서비스 항아리에서 모든 것을 추출 합니다.


4

Gradle을 사용하지 않는 Eclipse 사용자의 경우 Google Play 서비스 jar를 분해하고 원하는 부분으로 만 다시 빌드하는 도구가 있습니다.

dextorer의 strip_play_services.sh를 사용 합니다.

일부 내부 종속성이 있기 때문에 포함 할 서비스를 정확히 파악하기 어려울 수 있지만 필요한 항목이 누락 된 경우 작게 시작하여 구성에 추가 할 수 있습니다.


3

장기적으로 앱을 여러 dex로 나누는 것이 가장 좋은 방법이라고 생각합니다.


2
Gradle로이 작업을 수행하는 적절한 방법을 찾고 있습니다. 힌트가 있습니까?
Ivan Morgillo


2

빌드 프로세스를 매우 느리게 만드는 multidex를 사용하지 않는 경우. 다음을 수행 할 수 있습니다. 로 yahska 언급 사용하여 특정 구글 플레이 서비스 라이브러리. 대부분의 경우 이것 만 필요합니다.

compile 'com.google.android.gms:play-services-base:6.5.+'

다음은 사용 가능한 모든 패키지입니다. 선택적으로 API를 실행 파일로 컴파일합니다.

이것이 충분하지 않으면 gradle 스크립트를 사용할 수 있습니다. 이 코드를 'strip_play_services.gradle'파일에 넣으십시오.

def toCamelCase(String string) {
String result = ""
string.findAll("[^\\W]+") { String word ->
    result += word.capitalize()
}
return result
}

afterEvaluate { project ->
Configuration runtimeConfiguration = project.configurations.getByName('compile')
println runtimeConfiguration
ResolutionResult resolution = runtimeConfiguration.incoming.resolutionResult
// Forces resolve of configuration
ModuleVersionIdentifier module = resolution.getAllComponents().find {
    it.moduleVersion.name.equals("play-services")
}.moduleVersion


def playServicesLibName = toCamelCase("${module.group} ${module.name} ${module.version}")
String prepareTaskName = "prepare${playServicesLibName}Library"
File playServiceRootFolder = project.tasks.find { it.name.equals(prepareTaskName) }.explodedDir


def tmpDir = new File(project.buildDir, 'intermediates/tmp')
tmpDir.mkdirs()
def libFile = new File(tmpDir, "${playServicesLibName}.marker")

def strippedClassFileName = "${playServicesLibName}.jar"
def classesStrippedJar = new File(tmpDir, strippedClassFileName)

def packageToExclude = ["com/google/ads/**",
                        "com/google/android/gms/actions/**",
                        "com/google/android/gms/ads/**",
                        // "com/google/android/gms/analytics/**",
                        "com/google/android/gms/appindexing/**",
                        "com/google/android/gms/appstate/**",
                        "com/google/android/gms/auth/**",
                        "com/google/android/gms/cast/**",
                        "com/google/android/gms/drive/**",
                        "com/google/android/gms/fitness/**",
                        "com/google/android/gms/games/**",
                        "com/google/android/gms/gcm/**",
                        "com/google/android/gms/identity/**",
                        "com/google/android/gms/location/**",
                        "com/google/android/gms/maps/**",
                        "com/google/android/gms/panorama/**",
                        "com/google/android/gms/plus/**",
                        "com/google/android/gms/security/**",
                        "com/google/android/gms/tagmanager/**",
                        "com/google/android/gms/wallet/**",
                        "com/google/android/gms/wearable/**"]

Task stripPlayServices = project.tasks.create(name: 'stripPlayServices', group: "Strip") {
    inputs.files new File(playServiceRootFolder, "classes.jar")
    outputs.dir playServiceRootFolder
    description 'Strip useless packages from Google Play Services library to avoid reaching dex limit'

    doLast {
        def packageExcludesAsString = packageToExclude.join(",")
        if (libFile.exists()
                && libFile.text == packageExcludesAsString
                && classesStrippedJar.exists()) {
            println "Play services already stripped"
            copy {
                from(file(classesStrippedJar))
                into(file(playServiceRootFolder))
                rename { fileName ->
                    fileName = "classes.jar"
                }
            }
        } else {
            copy {
                from(file(new File(playServiceRootFolder, "classes.jar")))
                into(file(playServiceRootFolder))
                rename { fileName ->
                    fileName = "classes_orig.jar"
                }
            }
            tasks.create(name: "stripPlayServices" + module.version, type: Jar) {
                destinationDir = playServiceRootFolder
                archiveName = "classes.jar"
                from(zipTree(new File(playServiceRootFolder, "classes_orig.jar"))) {
                    exclude packageToExclude
                }
            }.execute()
            delete file(new File(playServiceRootFolder, "classes_orig.jar"))
            copy {
                from(file(new File(playServiceRootFolder, "classes.jar")))
                into(file(tmpDir))
                rename { fileName ->
                    fileName = strippedClassFileName
                }
            }
            libFile.text = packageExcludesAsString
        }
    }
}

project.tasks.findAll {
    it.name.startsWith('prepare') && it.name.endsWith('Dependencies')
}.each { Task task ->
    task.dependsOn stripPlayServices
}
project.tasks.findAll { it.name.contains(prepareTaskName) }.each { Task task ->
    stripPlayServices.mustRunAfter task
}

}

그런 다음 build.gradle에이 스크립트를 다음과 같이 적용합니다.

apply plugin: 'com.android.application'
apply from: 'strip_play_services.gradle'

1

Google Play 서비스를 사용하는 경우 2 만 개 이상의 메서드가 추가된다는 것을 알 수 있습니다. 이미 언급했듯이 Android Studio에는 특정 서비스의 모듈 식 포함 옵션이 있지만 Eclipse를 사용하는 사용자는 모듈화를 직접 수행해야합니다.

다행히도 작업을 상당히 쉽게 만드는 쉘 스크립트 가 있습니다. google play services jar 디렉토리에 압축을 풀고 필요에 따라 제공된 .conf 파일을 편집하고 셸 스크립트를 실행하면됩니다.

사용 예는 여기에 있습니다 .


1

Google Play 서비스를 사용하는 경우 2 만 개 이상의 메서드가 추가된다는 것을 알 수 있습니다. 이미 언급했듯이 Android Studio에는 특정 서비스의 모듈 식 포함 옵션이 있지만 Eclipse를 사용하는 사용자는 모듈화를 직접 수행해야합니다.

다행히도 작업을 상당히 쉽게 만드는 쉘 스크립트가 있습니다. google play services jar 디렉토리에 압축을 풀고 필요에 따라 제공된 .conf 파일을 편집하고 셸 스크립트를 실행하면됩니다.

그 사용의 예는 여기에 있습니다.

그가 말했듯이, compile 'com.google.android.gms:play-services:9.0.0'나는 필요한 라이브러리로 교체 하고 작동했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.