This article was originally posted on Kongregate's Developer Blog.
Could you ever imagine a Unity Android game that used more than 64K Java methods? Neither could the architects of the Dalvik bytecode. Perhaps they did (I haven’t read the spec) and the blame falls on other elements in your toolchain. The long and short of it is if your game taps against the 64K method limit per DEX file you’re going to need to get down and dirty with your native plugins and/or build workflow. This post will attempt to walk you through the various ways to deal with it.
First Things First
There’s no shortage of forum posts and blog entries on this subject. The most important takeaway is if you can manage to keep your game comfortably below this number, you will save yourself a lot of trouble.
Know Your Plugins
The most common way to hit this limit in Unity is through the use of native plugins. Android native plugins are a necessity for almost all Unity games. Unfortunately, some plugins are quite large. Google Play Game Services, for example, is close to 25K methods on its own, a significant chunk of the 64K you are allotted.
Super Brief Anatomy of Unity Android Plugins
Unity Android Plugins typically consist of some C# Unity code along with native Android code and resources. The native code and resources will be packaged either as an Android Library Project or Android Archive (AAR) under the Assets/Plugins/Android/ directory. Library Projects are the old way of sharing components in the Android ecosystem and AARs are the newer way. You will find plugins that use both.
The classes in both Library projects and AARs exist in JAR files, which are simple zips of compiled Java class files. The AAR file is also simply a zip of various Android resources, some of which will be libs/*.jar (a.k.a. Java class archives). Library projects are simple directory structures, and again the JARs will be in libs/*.jar.
Steps to Minimize Method Counts
The only way to reduce the number of Java methods included in your game's APK using the standard Unity build system is to remove or modify the JAR files included with your Android native plugins. The alternative is to export your Unity Project as an Android Project where you can apply more advanced techniques.
You should try each of the following techniques in order:
Remove any plugins your game is not using
Since Google has broken Play Services into a set of modules, only include the ones you actually use.
Use the Jar Jar Links tool with the zap rule to remove unneeded classes from the plug-ins JARs. You could also simply unzip, delete unused classes, and rezip the JAR.
Export your project as an Android Project so you can apply ProGuard or MultiDex. This is where things get dicey.
Most of this blog post will focus on the last bit because, at the time of writing, there aren’t a lot of resources that walk you through this approach. Exporting as an Android Project will be more disruptive to your development and build cycle. Until ProGuard or MultiDex are directly supported by Unity, you're best off going down this path as a last resort.
What to Look for When Testing
Once you have your game under the 64K limit and are able to generate the APK again, the key thing to look for while testing your game is ClassNotFoundException and VerifyError errors in logcat. This indicates your code is trying to use some class or method which is unavailable. Usually the error will be associated with a crash, so it’ll be quite obvious. Sometimes, however, the plugin may attempt to fail gracefully and, though your app does not crash, some feature you hope to be available will not function as expected.
ProGuard and MultiDex
ProGuard is a tool used to obfuscate and trim unused classes and methods. MultiDex is technology that enables multiple DEX files within your APK, thus removing the 64K method limit for your game. Unity does not have direct support for either of these techniques, but you can make use of them if you export your project to an Android Project.
When all else fails, ProGuard will hopefully bring you below the limit. If not, you can turn to MultiDex. MultiDex has the added strike of only working in API Level 14 (4.0) and up. It is natively supported in Android (5.0) and up. Support libraries must be used for 4.X. Finally, MultiDex comes with a set of Known Limitations.
Exporting to an Android Project
If you need to use ProGuard or MultiDex, the first step is to export your Unity Project as an Android Project. Depending on the complexity of your Project, this in itself can be a daunting task. It also likely means no more Unity Cloud Build. When done correctly, however, it can work similarly to exporting to XCode for iOS. The Android Studio or Gradle project will need to be set up after the export, but this should be a one-time task. You will be able to re-export your project without having to set up the Android build configuration again.
I’ve found three ways to successfully work with a project exported to Android. I’ll briefly cover the first two because they are simpler and may be preferable if your project is not too complex. The last approach requires a little more manual setup, but is probably the cleanest way to organize your project. It also may be your only option if you need MultiDex.
A Few Words of Caution
Even after exporting your game to Android Studio, it’s possible that the plugins your game uses depend on Unity Post Process scripts that will not translate to Android Studio or Gradle builds. You may still hit a dead end.
Approach 1: Simple Unity Export/Android Studio Import
This approach will work for games that do not use too many plugins. I imagine Unity and Android Studio will continue to improve this approach.
Under File -> Build Settings -> Android click the Google Android Project checkbox and the Export button. Create/Choose a directory to export. Android would be a good choice.
Open Android Studio and choose Import project (Eclipse ADT, Gradle, etc.). Navigate to your exported Unity Project, which will be a subdirectory of your export directory (e.g. ./Android/Your Unity Project).
You will need to choose a destination directory. You may leave the various checkboxes checked.
At this point, if all goes well, you should be able to run the project within Android Studio.
Pros & Cons
Pro: It’s simple.
Pro: The imported Android Studio project is also a standard Gradle project, allowing easier integration of Gradle-based tasks.
Con: Every time you export from Unity and import into Android Studio, a brand new project is created. Any manipulation you need to make to the studio project – for example, configure ProGuard – will need to be done every time you build. This would pretty severely impact your development cycle.
Con: Depending on the complexity of your project, it simply may not work without significant modification to the Android Studio Project.
Approach 2: Import the Exported Unity Project from Source
In this approach you directly import the exported Unity Project into Android Studio from sources and then manually update the various modules and dependencies. The difference from the first approach is rather than importing /Android/Your Unity Project, you import /Android, and Android Studio will attempt to set up modules for your main application and each exported library project.
The nice thing about this approach is that once you have the Android Studio project set up, you can re-export the Unity Project to the same location and in general will not need to update Android Studio Project.
The downside to this approach is that your Android Project will be tied to Android Studio Project files. Configuring and managing the dependencies will be a challenge.
Since I’d like to focus on the third approach, I’ll simply say once you have your project in Android Studio, it isn’t too difficult to enable ProGuard. However, the process of setting up the Android Studio Project involves correctly configuring each of the modules and dependencies using Android Studio’s UI. Depending on your familiarity with Android Studio project modules, this could be a tricky task. Further, I found getting MultiDex configured through the Android Studio UI challenging, which led me to the third approach.
Approach 3: Configuring a Gradle Project for an Exported Unity Project
Gradle is the build tool Android settled into a few years back. Android Studio Projects may be synced with Gradle Projects. Though old Android Studio Project modules are still supported, new projects are based on Gradle files. In this approach we correctly set up the Gradle files for the exported Unity Project, at which point we can either work with them and build from Android Studio or from the command line. We are given access to useful Gradle tasks such as ProGuard and MultiDex.