Home » Android » Limit of methods 64K per a dex file in Android

Limit of methods 64K per a dex file in Android

Posted by: admin June 15, 2020 Leave a comment

Questions:

I faced with this problem java.lang.IllegalArgumentException: method ID not in [0, 0xffff]: 65536 and I decided to exclude some methods from a dex file. My gradle.build:

compile ('com.google.android.gms:play-services:+') {
        exclude group: "com.google.android.gms.analytics"
        exclude group: "com.google.android.gms.games"
        exclude group: "com.google.android.gms.plus"
        exclude group: "com.google.android.gms.drive"
        exclude group: "com.google.android.gms.ads"
    }

I think that this snippet of code is wrong, because there is error method ID not in [0, 0xffff].... How can I exclude unnecessary parts of Google Play Service? I use only maps and GCM.

Updated.

Thanks reVerse. It’s really useful code. There is a script for getting count of methods (also can see names of existing packages) https://gist.github.com/JakeWharton/6002797 (source ./dex.sh; dex-method-count-by-package test.apk)

Before using the snippet of code from reVerse’s answer

Count of methods / Package
...
22484   com.google.android.gms
2   com.google.android.gms.actions
578 com.google.android.gms.ads
152 com.google.android.gms.ads.doubleclick
25  com.google.android.gms.ads.identifier
86  com.google.android.gms.ads.internal
86  com.google.android.gms.ads.internal.rawhtmlad
86  com.google.android.gms.ads.internal.rawhtmlad.client
88  com.google.android.gms.ads.mediation
4   com.google.android.gms.ads.mediation.admob
73  com.google.android.gms.ads.mediation.customevent
26  com.google.android.gms.ads.purchase
118 com.google.android.gms.ads.search
...
858 com.google.android.gms.games.internal.api
43  com.google.android.gms.games.internal.constants
8   com.google.android.gms.games.internal.data
31  com.google.android.gms.games.internal.events
9   com.google.android.gms.games.internal.experience
215 com.google.android.gms.games.internal.game
56  com.google.android.gms.games.internal.multiplayer
23  com.google.android.gms.games.internal.notification
80  com.google.android.gms.games.internal.player
86  com.google.android.gms.games.internal.request
...

After using the snippet of code from reVerse’s answer, packages: ads, games and etc were deleted.

How to&Answers:

Update – Google Play Services 6.5 (12-08-14)

With version 6.5 Google finally unbundled the Google Play Services. So from now on it’ll be possible to selectively compile the APIs into your executable.

Example (only using AdMob and Android Wear APIs)

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

For all the other individual Google Play Services APIs check this page on d.android.com.

Note: Using the + is typically discouraged. As of now the current correct version would be 6.5.87. For more information see the official Blog-Post (click).


Some time ago there’s been an article on Medium.com called “[DEX] Sky’s the limit? No, 65K methods is” (definitely worth a read), which describes a way to strip the Google Play Services with a shell-script which you can find here (google-play-services-strip-script).
While this is an option, there’s also a gradle task which does this for you:

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"
    File playServiceRootFolder = project.tasks.find { it.name.equals(prepareTaskName) }.explodedDir

    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 {
            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"))) {
  ----->                // Specify what should be removed
                }
            }.execute()
            delete {
                delete (file(new File(playServiceRootFolder, "classes_orig.jar")))
            }
        }
    }

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

Note: This is taken from Gradle task to strip unused packages on Google Play Services library @GitHubGist

The relevant part for you is where the arrow is in the task.create(...). There you need to specify which parts should be removed. So in your case just write something like this in there:

exclude "com/google/ads/**"
exclude "com/google/android/gms/analytics/**"
exclude "com/google/android/gms/games/**"
exclude "com/google/android/gms/panorama/**"
exclude "com/google/android/gms/plus/**"
exclude "com/google/android/gms/drive/**"
exclude "com/google/android/gms/ads/**"
exclude "com/google/android/gms/wallet/**"
exclude "com/google/android/gms/wearable/**"

This will remove everything except the Maps- and GCM-Part.

Note: In order to use it, just copy the contents of the gradle-task to the bottom of the build.gradle file of your app module.