Let’s run our new sample application on device/emulator with Android 5.0 or higher.
Works as expected – the app does nothing.
Try that on a bit older device with Android less than 5.0 – I run it on Jellybean 4.2.
It’s crashing. By examining logcat logs we can see it crashed with NoClassDefFoundException.
Why Does the Application Crash on API Less Than 21?
As I have previously mentioned, the MultiDex technique relies on splitting your application code into multiple .dex files. To explain why it has crashed on an API lower than 21, first you need to understand how Android handles .dex files on different API levels – from Android documentation:
Android 5.0 (API level 21) and higher uses a runtime called ART which natively supports loading multiple dex files from application APK files. ART performs pre-compilation at application install time which scans for classes(..N).dex files and compiles them into a single .oat file for execution by the Android device. For more information on the Android 5.0 runtime, see Introducing ART.
Versions of the platform prior to Android 5.0 (API level 21) use the Dalvik runtime for executing app code. By default, Dalvik limits apps to a single classes.dex bytecode file per APK. In order to get around this limitation, you can use the multidex support library, which becomes part of the primary DEX file of your app and then manages access to the additional DEX files and the code they contain.
The problem is: some important code related to MutliDex/Application initialization can accidentally land in a secondary .dex file – therefore we will see an exception similar to the one shown on the picture from previous paragraph (cause code from secondary dex can’t be loaded on app Startup!).
The reason why we have added loads of libraries and custom application class is to “manually” provoke moving Application initialization classes to secondary dex file (this step usually is done automatically – by your in-app source code).
Sometimes, your application might even work fine on API less than 21, but it can crash when it’s loaded by some kind of service (like application launched by push notification handler service) – code needed to launch an app by push notification can be moved to secondary dex file (real-world example I had in past)!
Can We Fix That, Please?
In an ideal world, it should work automatically. However, Android SDK Build tools is not bug-free. During the build, Android SDK tools automatically detect what is important and what should be kept in the first .dex file. It does not always work perfectly though.
Android has some kind of “MultiDex.keepthat classes in first dex” file concept. You add class names you want to preserve in a text file and they won’t be moved to secondary dex file.
Xamarin has not always supported this feature – but after my feature request (https://bugzilla.xamarin.com/show_bug.cgi?id=35491) they have added Build action called “MultiDexMainDexList”
We just need to find out which classes we need to preserve.
The fastest and most reliable way to do that is by examining and decompiling .dex bytecode.
Download two tools:
- https://github.com/pxb1988/dex2jar – convert .dex bytecode to jar
- https://github.com/java-decompiler/jd-gui/releases – decompile and browse jar
Steps to examine the dex file:
1. Go to your %Project Root Folder%/obj/Debug/android/bin, copy classes2.dex file to your dex2jar tool folder.
2. Type in command line: dex2jar classes2.dex -o classes2Jar.jar
3. Launch jd-gui tool and open generated classes2jar.jar
The missing class is mono/MonoPackageManager.class, lets use this information.
Add a new text file to project (ex. multidex.keep), set its build action to “MutliDexMainDexList”: