- ANDROID — MULTI DEX
- 1. What is Android MultiDex?
- 2. What causes number of references to exceed 65,536 limit ?
- 3. How to Configure MultiDex?
- 4. Benefit Using MultiDex?
- Android’s multidex slows down app startup
- Intro
- Context
- Setting up multidex
- NoClassDefFoundError?!
- YesClassDefFound
- Multidex app startup performance impact
- Addressing the multidex app startup performance impact
- Results
- Enable multidex for apps with over 64K methods
- About the 64K reference limit
- Multidex support prior to Android 5.0
- Groovy
- Kotlin
- Groovy
- Kotlin
- Multidex support for Android 5.0 and higher
- Avoid the 64K limit
- Configure your app for multidex
- Groovy
- Kotlin
- Kotlin
- Kotlin
- Limitations of the multidex library
- Declare classes required in the primary DEX file
- multiDexKeepFile property
- Groovy
- Kotlin
- multiDexKeepProguard property
- Groovy
- Kotlin
- Optimize multidex in development builds
- Groovy
- Kotlin
- Test multidex apps
- Kotlin
ANDROID — MULTI DEX
Sep 24, 2018 · 3 min read
In this story, I just want to make my note about multidex android.
1. What is Android MultiDex?
Android application (APK) files contain executable bytecode files in the form of Dalvik Executable (DEX) files, which contain the compiled code used to run your app. Getting past this limit requires that you configure your app build process to generate more than one DEX file, known as a multidex configuration.
2. What causes number of references to exceed 65,536 limit ?
- Methods written by you + Android Framework methods + Third party library(eg Volley,Retrofit,Facebook SDK etc) methods.
- I have read “somewhere”
— App Compat 24.2.1 contains 16.5k methods
— Google Play Services GCM 9.6.1 contains 16.7k methods
— So if you have created a simple Hello world application which has App — Compat 24.2.1, you are already 1/4 way to cross the single dex methods limit
3. How to Configure MultiDex?
If your minSdkVersion is set to 21 or higher, all you need to do is set multiDexEnabled to true in your module-level build.gradle file, as shown here:
However, if your minSdkVersion is set to 20 or lower, then you must use the multidex support library as follows:
- Modify the module-level build.gradle file to enable multidex and add the multidex library as a dependency, as shown here:
2. Depending on whether you override the Application class, perform one of the following:
- If you do not override the Application class, edit your manifest file to set android:name in the tag as follows:
- If you do override the Application class, change it to extend MultiDexApplication (if possible) as follows:
- Or if you do override the Application class but it’s not possible to change the base class, then you can instead override the attachBaseContext() method and call MultiDex.install(this) to enable multidex:
Caution: Do not execute Mu l tiDex.install() or any other code through reflection or JNI before MultiDex.install() is complete. Multidex tracing will not follow those calls, causing ClassNotFoundException or verify errors due to a bad class partition between DEX files.
4. Benefit Using MultiDex?
- MultiDex solves the 64K method limit but it’s not a silver bullet and IMHO shouldn’t be used as an excuse to avoid optimizing the size of the APK but if
- You’re only targeting modern devices (e.g. API 16+),
- Performance/cold-start times are not critical
- You have no time to optimize the app further
2. Problem : Some devices, specially old Gingerbread phones (but also ICS), use a very small LinearAlloc buffer which might cause errors. Enabling MultiDex doesn’t contribute directly to this problem but allows developers to continue the “code bloat” up to the point where the app becomes too big to run on those devices.
The benefits are not quite good enough to be used in your real world. but take a note about this opinion :
Источник
Android’s multidex slows down app startup
Intro
The 65k method limit is a problem that has been addressed time and time again within the Android community. The current state of Android addresses the issue with multidexing. While multidexing is an excellent solution by Google, I’ve noticed a significant impact on app startup performance which has yet to raise any attention in the community. I wrote this article for developers who have never heard of this issue (but would like to implement multidexing) and to folks who already have multidexing but would like to observe the performance gains that I’ve seen with my solution.
Context
For the uninitiated, Android apps are written in Java which is converted into a .class file. This class file (and any jar dependencies) are then compiled into a single classes.dex file. This dex file is then combined with any resources needed into a .apk file: which is ultimately what you download from the app store. Read more here.
One limitation with this compilation process is that the system only allows up to 65k methods within a single dex file. In the early days of Android, applications that hit the 65k method limit addressed the issue using Proguard to shrink any unused code. However, this approach is limited and only delayed the inevitable approach to the 65k method limit for production apps.
In light of this issue, Google has released a solution in the recent Android Support Libraries to address the 65k method limit: multidexing. This solution is handy and will allow your app to break well beyond the 65k method limit, but (as I’ve said before), there is a significant impact on performance which may slow down your app startup.
Setting up multidex
Multidexing is a fairly mature solution with great documentation. I highly recommend following the instructions on the Android Developer site to enable multidex in your project. You can also refer to the sample project in my Github.
NoClassDefFoundError?!
While setting up multidexing for your project, you may notice a java.lang.NoClassDefFoundError when running your app. This means that the class for app startup is not located in the main dex file. The Android plugin for Gradle in Android SDK Build Tools 21.1 or higher has multidex support. This plugin uses Proguard to analyze your project and generate a list of classes for app startup in the file [buildDir]/intermediates/multi-dex/[buildType]/maindexlist.txt. However, this list is not 100% accurate and may miss some of the classes necessary for app startup.
YesClassDefFound
To solve this, you should list those classes in the multidex.keep file to let the compiler know which classes to keep in the main dex file:
- Create a multidex.keep file in your project folder.
- List classes reported in the java.lang.NoClassDefFoundError into the multidex.keep file (note: don’t modify the maindexlist.txt in the build folder directly; this is generated every time the app compiles).
- Add the following scripts to the build.gradle in your app. This script will combine your multidex.keep and maindexlist.txt generated by Gradle while compiling your project.
Multidex app startup performance impact
If you use multidex, then you need to be aware that there will be an impact on your app startup performance. We’ve noticed this by keeping track of our app’s startup time — defined as the time between when a user clicks on the app icon up to when all images have been downloaded and displayed to users. Once multidex was enabled, our app startup time increased by about 15% on all devices running Kitkat (4.4) and below. Refer to Carlos Sessa’s Lazy Loading Dex files for more info.
The reason is because Android 5.0 and higher uses a runtime called ART which natively supports loading multiple dex files from application APK files. However, devices prior to 5.0 have extra overhead when loading classes from dex files outside of the main dex.
Addressing the multidex app startup performance impact
In the span between when the app starts and when all of the images are displayed, there exist many classes which are not be detected by Proguard and thus not stored in the main dex file. The question is, how can we know what classes have been loaded during app startup?
Fortunately, we have the findLoadedClass method in ClassLoader. Our solution is to do a runtime check at the end of the app startup. And if there is any class stored in the second dex file and that loads during app startup, then we move them into the main dex file by adding the class name into the multidex.keep file. My sample project will have the specific implementation details, but you can do this by:
- running getLoadedExternalDexClasses in the following util class at the end of what you consider to be your app startup
- adding the list returned by the method above to your multidex.keep file and then recompile
Results
Here are the startup performance improvements that we’ve observed in our app across multiple devices. The first column (blue) is the base app startup time without multidexing. You can see the significant time increase in the second column (red), which is the app startup time when we enabled multidex without any extra work. The third column (green) is the app startup time with multidex turned on and the improvements discussed. As you can see from the graph, the app startup time drop back to the same level or even faster than before turning on multidex. Give it a try and you should notice performance improvements as well.
Источник
Enable multidex for apps with over 64K methods
When your app and the libraries it references exceed 65,536 methods, you encounter a build error that indicates your app has reached the limit of the Android build architecture:
Older versions of the build system report a different error, which is an indication of the same problem:
Both these error conditions display a common number: 65536. This number represents the total number of references that can be invoked by the code within a single Dalvik Executable (DEX) bytecode file. This page explains how to move past this limitation by enabling an app configuration known as multidex, which allows your app to build and read multiple DEX files.
About the 64K reference limit
Android app (APK) files contain executable bytecode files in the form of Dalvik Executable (DEX) files, which contain the compiled code used to run your app. The Dalvik Executable specification limits the total number of methods that can be referenced within a single DEX file to 65,536—including Android framework methods, library methods, and methods in your own code. In the context of computer science, the term Kilo, K, denotes 1024 (or 2^10). Because 65,536 is equal to 64 X 1024, this limit is referred to as the ’64K reference limit’.
Multidex support prior to Android 5.0
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 add the multidex library to the module-level build.gradle file:
Groovy
Kotlin
To view the current versions for this library, see the information about Multidex on the versions page.
If you aren’t using AndroidX, add the following deprecated support library dependency instead:
Groovy
Kotlin
This library becomes part of the primary DEX file of your app and then manages access to the additional DEX files and the code they contain. More details are below in the section about how to configure your app for multidex.
Multidex support for Android 5.0 and higher
Android 5.0 (API level 21) and higher uses a runtime called ART which natively supports loading multiple DEX files from APK files. ART performs pre-compilation at app install time which scans for classes N .dex files and compiles them into a single .oat file for execution by the Android device. Therefore, if your minSdkVersion is 21 or higher multidex is enabled by default, and you do not need the multidex library.
For more information on the Android 5.0 runtime, read ART and Dalvik.
Note: When running your app using Android Studio, the build is optimized for the target devices you deploy to. This includes enabling multidex when the target devices are running Android 5.0 and above. Because this optimization is applied only when deploying your app using Android Studio, you might still need to configure your release build for multidex to avoid the 64K limit.
Avoid the 64K limit
Before configuring your app to enable use of 64K or more method references, you should take steps to reduce the total number of references called by your app code, including methods defined by your app code or included libraries. The following strategies can help you avoid hitting the DEX reference limit:
- Review your app’s direct and transitive dependencies — Ensure any large library dependency you include in your app is used in a manner that outweighs the amount of code being added to the app. A common anti-pattern is to include a very large library because a few utility methods were useful. Reducing your app code dependencies can often help you avoid the DEX reference limit.
- Remove unused code with R8 — Enable code shrinking to run R8 for your release builds. Enabling shrinking ensures you are not shipping unused code with your APKs.
Using these techniques might help you avoid the need to enable multidex in your app while also decreasing the overall size of your APK.
Configure your app for multidex
If your minSdkVersion is set to 21 or higher, multidex is enabled by default and you do not need the multidex library.
However, if your minSdkVersion is set to 20 or lower, then you must use the multidex library and make the following modifications to your app project:
Modify the module-level build.gradle file to enable multidex and add the multidex library as a dependency, as shown here:
Groovy
Kotlin
If you do not override the Application class, edit your manifest file to set android:name in the tag as follows:
If you do override the Application class, change it to extend MultiDexApplication (if possible) as follows:
Kotlin
Or if you do override the Application class but it’s not possible to change the base class, then you can instead override the attachBaseContext() method and call MultiDex.install(this) to enable multidex:
Kotlin
Caution: Do not execute MultiDex.install() or any other code through reflection or JNI before MultiDex.install() is complete. Multidex tracing will not follow those calls, causing ClassNotFoundException or verify errors due to a bad class partition between DEX files.
Now when you build your app, the Android build tools construct a primary DEX file ( classes.dex ) and supporting DEX files ( classes2.dex , classes3.dex , and so on) as needed. The build system then packages all DEX files into your APK.
At runtime, the multidex APIs use a special class loader to search all of the available DEX files for your methods (instead of searching only in the main classes.dex file).
Limitations of the multidex library
The multidex library has some known limitations that you should be aware of and test for when you incorporate it into your app build configuration:
- The installation of DEX files during startup onto a device’s data partition is complex and can result in Application Not Responding (ANR) errors if the secondary DEX files are large. To avoid this issue, enable code shrinking to minimize the size of DEX files and remove unused portions of code.
- When running on versions prior to Android 5.0 (API level 21), using multidex is not enough to work around the linearalloc limit (issue 78035). This limit was increased in Android 4.0 (API level 14), but that did not solve it completely. And on versions lower than Android 4.0, you might reach the linearalloc limit before reaching the DEX index limit. So if you are targeting API levels lower than 14, test thoroughly on those versions of the platform, because your app might have issues at startup or when particular groups of classes are loaded.
Code shrinking can reduce or possibly eliminate these issues.
Declare classes required in the primary DEX file
When building each DEX file for a multidex app, the build tools perform complex decision-making to determine which classes are needed in the primary DEX file so that your app can start successfully. If any class that’s required during startup is not provided in the primary DEX file, then your app crashes with the error java.lang.NoClassDefFoundError .
This shouldn’t happen for code that’s accessed directly from your app code because the build tools recognize those code paths, but it can happen when the code paths are less visible such as when a library you use has complex dependencies. For example, if the code uses introspection or invocation of Java methods from native code, then those classes might not be recognized as required in the primary DEX file.
So if you receive java.lang.NoClassDefFoundError , then you must manually specify these additional classes as required in the primary DEX file by declaring them with the multiDexKeepFile or the multiDexKeepProguard property in your build type. If a class is matched in either the multiDexKeepFile or the multiDexKeepProguard file, then that class is added to the primary DEX file.
multiDexKeepFile property
The file you specify in multiDexKeepFile should contain one class per line, in the format com/example/MyClass.class . For example, you can create a file called multidex-config.txt that looks like this:
Then you can declare that file for a build type as follows:
Groovy
Kotlin
Remember that Gradle reads paths relative to the build.gradle file, so the above example works if multidex-config.txt is in the same directory as the build.gradle file.
multiDexKeepProguard property
The multiDexKeepProguard file uses the same format as Proguard and supports the entire Proguard grammar. For more information about Proguard format and grammar, see the Keep Options section in the Proguard manual.
The file you specify in multiDexKeepProguard should contain -keep options in any valid ProGuard syntax. For example, -keep com.example.MyClass.class . You can create a file called multidex-config.pro that looks like this:
If you want to specify all classes in a package, the file looks like this:
Then you can declare that file for a build type as follows:
Groovy
Kotlin
Optimize multidex in development builds
A multidex configuration requires significantly increased build processing time because the build system must make complex decisions about which classes must be included in the primary DEX file and which classes can be included in secondary DEX files. This means that incremental builds using multidex typically take longer and can potentially slow your development process.
To mitigate longer incremental build times, you should use pre-dexing to reuse multidex output between builds. Pre-dexing relies on an ART format available only on Android 5.0 (API level 21) and higher. If you’re using Android Studio 2.3 and higher, the IDE automatically uses this feature when deploying your app to a device running Android 5.0 (API level 21) or higher.
Tip: Android plugin for Gradle 3.0.0 and higher include further improvements to optimize build speeds, such as per-class dexing (so that only the classes that you modify are re-dexed). In general, for the best development experience, you should always upgrade to the latest version of Android Studio and the Android plugin.
However, if you’re running Gradle builds from the command line, you need to set the minSdkVersion to 21 or higher to enable pre-dexing. A helpful strategy to preserve settings for your production build is to create two versions of your app using product flavors: a development flavor and a release flavor with different values for minSdkVersion , as shown below.
Groovy
Kotlin
To learn more strategies to help improve build speeds (from either Android Studio or the command line), read Optimize Your Build Speed. For more information about using build variants, see Configure Build Variants.
Tip: Now that you have different build variants for different multidex needs, you can also provide a different manifest file for each variant (so only the one for API level 20 and lower changes the tag name), or create a different Application subclass for each variant (so only the one for API level 20 and lower extends the MultiDexApplication class or calls MultiDex.install(this) ).
Test multidex apps
When you write instrumentation tests for multidex apps, no additional configuration is required if you use a MonitoringInstrumentation (or an AndroidJUnitRunner ) instrumentation. If you use another Instrumentation , then you must override its onCreate() method with the following code:
Kotlin
Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates.
Источник