Almost every Android developer knows sad true – Dalvik, Android’s virtual machine used by applications and some system services has one major limit – single .dex file (bytecode interpreted by Dalvik VM) can have only 64k (exactly 65536) methods.
What does it mean (for those who haven’t faced with this limit, yet)? In short, if your application contains more methods and you invoke one of these which are placed after 65536 position your application will crash with error:
In the great article: “DEX Sky’s the limit? No, 65K methods is” you can find more details and explanations about this problem.
In here I want to show you how to face this problem once and for all.
64k it’s a huge number. Do I really need to care about this limit?
Yes, you do. Android grows very fast. Also from the development side. Libraries evolve, Google releases new Play Services in which every new version has couple hundreds or even thousands of methods more than previous one.
To prove this problem I created a small example project.
Let suppose that we want to create MVP product of app with:
- Simple REST (json) client
- clean project structure and good programming practices (In case of our app has been successful and we don’t want to create it from scratch after that)
- some simple and fancy animations and modern UI elements
- Login by facebook
- Android 4 and 5 compatibility.
Reaching the limit
Ok then, let’s create a simple project in Android Studio with Blank Activity and #minSdkVersion=”15” (in time of writing this post I use Android Studio ver.0.8.14 and Android Gradle plugin ver.0.13).
Now I’ll to add my favorite libraries. Here you have whole dependencies list from our
What do we have here? In short:
- Play Services and support libraries
- Dagger for dependency injection and clean project architecture
- Some tools for fast and fun programming (Guava, Butterknife, Timber)
- Some UI/Animation tools (Rebound – superb animations tools from facebook, RecyclerView, Palette, SlidingTabStrip etc.)
- Retrofit, Gson, Picasso for clean and simple networking
- RxJava for simple and clean asynchronous tasks
- NewRelic and Crashlytics for crash reporting, performance and usage statistics etc.
Great, now let’s start counting all used methods (yeah, that’s right, 64k limit includes also methods from all dependencies used in the project).
First of all build/run project (we have to generate .apk or/and .dex files. You can do this by clicking ▶️ in Android Studio or by running
in project root directory.
After that our .apk file should be placed in
Now let’s analyze it. For this I used dex-method-counts tool. After we download it we’ll have to run two commands (copied from README):
The result surprised me as well because I’ve just updated Google Play Services from 5 to 6 and… well, better see yourself:
Yes, it’s true. We haven’t written any line of code yet but we’ve almost reached dex limit with our 63897 methods.
Oh, I almost forgot. We haven’t attached any Analytics library. So let’s add FlurryAnalytics. And in case of fast datastore prototyping lets add Parse SDK. Just in case.
Let’s build it again and… 💥boom💥:
Yes, it has happened. And we still haven’t written any line of code.
Of course, the most correct solution is ProGuard. But again – we’re working on MVP version of our app and we don’t have time to deal with:
in almost every library which we have in our project.
Fortunately, Google has another solution for us. We can split our project into more than one .dex files and load the at runtime. But until now this process wasn’t too straightforward and clear (couple years ago Google published article about this).
With Android 5.0 Lollipop release, new support library appeared in Android SDK. It contains only two classes:
MultiDexApplication and simplifies whole multidex loading process. According to MultiDex documentation it does nothing on Android 5.0 which provide built-in support for secondary dex files. On previous system versions it adds to classloader additional .dex files attached in .apk archive.
Where using MultiDex is pretty simple it’s some important things which we should care about.
First of all, we have to configure build instructions to split our project into multiple dex files.
app/build.gradle file we have to add:
We have two params:
--multi-dexenables splitting mechanism in a build process
--main-dex-list(not required) – file with a list of classes which have to be attached in main dex file.
For now we can comment second parameter. We’ll back to it later.
After that our app will be split to multiple dex files.
Now we have to merge it (load additional dex files) inside our app. At the beginning, we have to attach
android-support-multidex.jar library into our project. You can find it in:
.../Android SDK directory/extras/android/support/multidex/library. After that we have three ways for loading .dex files in our app:
- Declare MultiDexApplication class in AndroidManifest.xml file:
- Extend MultiDexApplication in our Application class (I picked this way):
- If we can’t extend
MultiDexApplicationwe can install multiple dex files manually by overriding
attachBaseContext(Context base)method in our Application class:
In general, this configuration should be sufficient. But after we build and run our project we get an error:
If you have any additional libraries in your project (in our example we have Facebook SDK) we have to be sure that we disabled pre-dexing on them. Unfortunately
--multi-dex option is not compatible with pre-dexed libs.
You can do this by adding:
Now after we build and run our project everything works as it should.
Here you have 3rd commit with complete changes required for enabling MultiDex support.
It’s good to keep in mind these important things:
- Additional .dex files are loaded in
MultiDex.install(Context)invokation). It means, that before this moment we can’t use classes from them. So i.e. we cannot declare static fields with types attached out of main .dex file. Otherwise, we’ll get
- The similar case is with methods in our application class. We have to be sure that we don’t want to access classes and methods loaded from secondary .dex files. But this issue is easy to workaround by moving all invokations to inner, anonymous class. Here you have how it could look like:
Why will it work this way? In short, because ClassLoader looks for dependencies while class is initialized. So when we’re looking for classes and methods inside
Runnable class we have secondary .dex files loaded.
Another way to deal with problems described above is
--main-dex-list param described earlier. It points file with list of classes which we want to put in main .dex file.
It’s always good to use this option in case of
android.support.multidex package do secondary .dex files and efficiently stop us from merging them.
Example of the described file (named multidex.keep in example project):
The full source code of the described example is available on Github repository.