Using Multidex to solve INSTALL_FAILED_DEXOPT errors on Android

At work, we have been receiving reports of people, particularly on Android 2, not being able to install from the Google Play Store citing an error message “Package file is invalid“. At the same time, I was trying to test one of the features I was developing on a couple of Android 2 test devices we have in the office, but I was repeatedly getting an INSTALL_FAILED_DEXOPT error from adb. It seemed that both of these issues could be related.

Investigation

First of all, I tried to get more details about the error from adb logcat. It turns out I was getting this error during the install of the app:


11-19 18:43:19.863  4273  4273 E dalvikvm: LinearAlloc exceeded capacity (5242880), last=2288
11-19 18:43:19.863  4273  4273 E dalvikvm: VM aborting

From searching online, this issue was generally related to the 65k method limit on the dex file. I knew that we were hovering close to the 65k using the dex-method-counts tool (we were at about 62k).  So although we weren’t hitting the hard 65k compile-time limit, we were hitting some sort of install-time limit specifically on Android 2 more frequently as we got closer to the 65k limit.  I wasn’t really certain what the root issue is, but there are a couple of possibilities:

Solution: Integrating the multidex library

At the time, we were not using the multidex library, due to inheriting an old pre-Gradle build process that made integration difficult.   However since we recently did migrate to Gradle, we were able to take advantage of the Android multidex library very easily.  There were only two changes that needed to be made.  The two steps are as follows: (more details can be found on the multidex library page)

  1. The build.gradle file needs to be updated to add the multidex library dependency and to enable multidex in the global or build type/flavor configuration.
    android {
        defaultConfig {
            multiDexEnabled true
            ...
        }
        ...
    }
    
    dependencies {
      compile 'com.android.support:multidex:1.0.0'
    }
    
  2. The application class specified in the AndroidManifest.xml needs to be MultiDexApplication instead of Application.
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.android.multidex.myapplication">
        <application
            ...
            android:name="android.support.multidex.MultiDexApplication">
            ...
        </application>
    </manifest>
    

    Alternatively, your application subclass can override attachBaseContext to add a statement to initialize MultiDex.

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

Simple enough right?  So I proceeded to build the app on the Android 2 test device again.  When it installed though, I received the INSTALL_FAILED_DEXOPT again.  What?

Well of course, since we weren’t actually over the 65k limit, the build process wouldn’t generate separate dex files.  Off I went back onto the internet to find a solution and thankfully there were people on StackOverflow who had the same question.  It turns out that dx command does have an --set-max-idx-number argument in order to limit the number of methods in the dex file before it creates the next one.  It also has a –minimal-main-dex argument to only include the absolute necessary methods in the primary dex file and leaving the rest in the secondary dex files.  To hook these up into Gradle, I added the following snippet into the build.gradle file, following the StackOverflow solution:

tasks.withType(com.android.build.gradle.tasks.Dex) { dexTask ->
    def command = [] as List
    command << '--minimal-main-dex'
    command << '--set-max-idx-number=50000'
    dexTask.setAdditionalParameters(command)
}

And voila! The app magically started to install and run on Android 2 again. So we shipped a build that night to keep our players on old devices happy.

Further Build Optimizations

On the multidex library page, there are ways to optimize multidex builds for development, since Gradle builds are slow enough already.  The original build time for my project with multidex enabled was three minutes for a one-line change.  Since we were using multidex, we could forgo the Proguard task for debug builds, which saved two minutes, so we were down to a one minute build.  The pre-dexing optimization brought our development builds down by half to 30 seconds, which really speeds up our workflow.

Leave a Reply

Your email address will not be published. Required fields are marked *