code

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

codestyles 2020. 9. 5. 09:57
반응형

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


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

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

1) 코드 축소

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

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

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


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

65K 참조 제한 정보

Android 애플리케이션 (APK) 파일에는 앱 실행에 사용되는 컴파일 된 코드가 포함 된 DEX (Dalvik Executable) 파일 형식의 실행 가능한 바이트 코드 파일이 포함되어 있습니다. 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) 파일로 앱 빌드를 지원합니다. multidex 구성을 사용하려면 65536 개 이상의 메서드를 참조하는 앱이 필요합니다. multidex 사용에 대한 자세한 내용은 65,000 개 이상의 메서드로 앱 빌드를 참조하세요 .

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

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

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


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


당신은 사용하려면, 그의 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>

잘 작동합니다!


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

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'

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


Use proguard to lighten your apk as methods that are unused will not be in your final build. Double check you have following in your proguard config file to use proguard with guava (my apologies if you already have this, it wasn't known at time of writing) :

# 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.**

In addition, if you are using ActionbarSherlock, switching to the v7 appcompat support library will also reduce your method count by a lot (based on personal experience). Instructions are located :


You could use Jar Jar Links to shrink huge external libraries like Google Play Services (16K methods!)

In your case you will just rip everything from Google Play Services jar except common internal and drive sub-packages.


For Eclipse users not using Gradle, there are tools that will break down the Google Play Services jar and rebuild it with only the parts you want.

I use strip_play_services.sh by dextorer.

It can be difficult to know exactly which services to include because there are some internal dependencies but you can start small and add to the configuration if it turns out that needed things are missing.


I think that in the long run breaking your app in multiple dex would be the best way.


Multi-dex support is going to be the official solution for this issue. See my answer here for the details.


If not to use multidex which making build process very slow. You can do the following. As yahska mentioned use specific google play service library. For most cases only this is needed.

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

Here is all available packages Selectively compiling APIs into your executable

If this will be not enough you can use gradle script. Put this code in file '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
}

}

Then apply this script in your build.gradle, like this

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

If using Google Play Services, you may know that it adds 20k+ methods. As already mentioned, Android Studio has the option for modular inclusion of specific services, but users stuck with Eclipse have to take modularisation into their own hands :(

Fortunately there's a shell script that makes the job fairly easy. Just extract to the google play services jar directory, edit the supplied .conf file as needed and execute the shell script.

An example of its use is here.


If using Google Play Services, you may know that it adds 20k+ methods. As already mentioned, Android Studio has the option for modular inclusion of specific services, but users stuck with Eclipse have to take modularisation into their own hands :(

Fortunately there's a shell script that makes the job fairly easy. Just extract to the google play services jar directory, edit the supplied .conf file as needed and execute the shell script.

An example of its use is here.

Just like he said, I replaces compile 'com.google.android.gms:play-services:9.0.0' just with the libraries that I needed and it worked.

참고URL : https://stackoverflow.com/questions/15471772/how-to-shrink-code-65k-method-limit-in-dex

반응형