Deal with Titanium modules and its missing support for Android multidex

Overflow in real life.

I am a Titanium developer since 2014, and despite everything, I love it. The work behind this awesome framework it’s big and incredible, and I’m really proud of its community of which I am a member.

I still use it for the apps I develop for Caffeina.

The main problem is that when building complex apps, the number of your native modules increases, and when install two or more modules that requires the the same JARs file with different versions, Titanium compilation will fail on Android.

You’ve undoubtedly encountered this problem when building apps that require native modules with the Google Play Services SDK.

The very quick and dirt solution is to keep a JAR file from one the modules, and replace it in the other modules. Unfortunately, this trick is not always a valid solution, because the modules are now referencing a stripped version of the Google Play Services SDK to avoid to bring in the app unnecessary methods.

Let’s take an example, if you’ve installed in your Titanium app this modules:

Exploring these modules, we discover that the ti.ga module brings a stripped jar of the Google Play Services, with the version A.X.Y; instead, ti.dfp has the complete SDK with entire jar file, with the version B.X.Y.

You can’t just delete the SDK version A.X.Y and replace with the SDK with a possible lowest version, because (maybe not, but maybe), some features weren’t available yet in version B.X.Y.

So, what can you do? I mistakenly thought to download the latest Google Play Services SDK and replace it in all two modules.

But, currently, the google-play-services.jar is a 5.5 MB huge file, and, just to be clear, I don’t simply want to keep unnecessary code in my app if I don’t use it.

Google Play Services library and it’s huge jar file

Furthermore, we had an error at compile time about the 64K method limit. Every developer who uses or ever used Titanium to build apps had to fight with the issue of the 64K method limit in Android.

trouble writing output: Too many method references: 68927; max is 65536.

Yes, you understood well. There’s is a limit of methods you can use in your applications. Hey, just to be clear, this is not a Titanium issue, but an Android limitation.

As the Android platform has continued to grow, so has the size of Android apps. When your application and the libraries it references reach a certain size, you encounter build errors that indicate your app has reached a limit of the Android app build architecture.

In versions of Google Play services prior to 6.5, you had to compile the entire package of APIs into your app. In some cases, doing so made it more difficult to keep the number of methods in your app (including framework APIs, library methods, and your own code) under the 65,536 limit.

For this reason, when building native Android apps, you can select which library to compile.

From version 6.5, you can instead selectively compile Google Play service APIs into your app. For example, to include only the Google Fit and Android Wear APIs, replace the following line in your build.gradle file:

You can read more about selective compiling here: https://developers.google.com/android/guides/setup

A possibile solution could be to use the multidex support introduced in Android 6.0, but Titanium currently doesn’t support it: https://jira.appcelerator.org/browse/TIMOB-18082

Read more about 64K limit and multidex here: http://developer.android.com/tools/building/multidex.html

So… what’s the solution?

Build a stripped version of the SDK

It’s Proguard!

ProGuard is a free Java class file shrinker, optimizer, obfuscator, and preverifier. It detects and removes unused classes, fields, methods, and attributes. It optimizes bytecode and removes unused instructions. It renames the remaining classes, fields, and methods using short meaningless names. (http://proguard.sourceforge.net/)

From a year now, each JAR library will provide its own proguard configuration file. This a start point to generate a new version of the JAR with only the classes we want in. Let’s start:

Install Proguard

On Mac OS X, you can easily install with brew:

brew install proguard

Install the Google Play Services SDK and referenced libraries

Make sure you’ve downloaded the Google Play services in the default Android SDK directory. First of all, install the Android SDK. In my case, I installed it in /opt/android.

Launch the Android SDK manager, by typing:

/opt/android/tools/android &

After the Android SDK Manager application launches, select:

  • Latest SDK Platform (as the time I write, Android 6.0 API 23)
  • Android 22 SDK (needed for referenced org.apache.http.*)
  • Extras/Android support library (obsolete) (needed for referenced classes v4 / v7 mediarouter)

then click Install X Packages.

Once finished, download the google-play-services.jar from a Google Repository. You can find it here: http://stackoverflow.com/questions/20982533/how-to-download-older-google-play-services.

The latest one is: https://dl-ssl.google.com/android/repository/google_play_services_8487000_r29.zip

I suggest you to copy in a know location like /opt.

You can read more about the support library here: http://developer.android.com/tools/support-library/setup.html

Create the appropriate proguard file

In your Titanium project, create a directory jar and copy the delivered proguard file:

mkdir jar
cp [YOUR_GOOGLE_PLAY_SERVICES_PATH]/libproject/google-play-services_lib/proguard.txt jar/proguard.conf

Now, with your preferred editor, let’s edit the proguard.conf file.

At the top of the file, add these lines:

-injars [YOUR_GOOGLE_PLAY_SERVICES_PATH]/google-play-services.jar
-outjars google-play-services-light.jar

-libraryjars /opt/android/extras/android/support/v4/android-support-v4.jar
-libraryjars /opt/android/extras/android/support/v7/mediarouter/libs/android-support-v7-mediarouter.jar
-libraryjars /opt/android/platforms/android-23/android.jar
-libraryjars /opt/android/platforms/android-22/android.jar

-dontoptimize
-dontobfuscate
-dontwarn com.google.**.R
-dontwarn com.google.**.R$*
-dontnote

Please note:

  • Replace the /opt/android path with your Android SDK path.
  • Replace the android-23 with your version of the target SDK (23 is the latest, now)

The injars parameter specify the input JAR, outjar the output jar.

Keep the the extended -libraryjars, because they are needed by proguard to build the final JAR.

If you have any error in the proguard build, maybe you need to add other libraryjars.

Now, let’s add the services of the Google Play that we want in the output JAR. In the example above, ti.ga require the analytics, and ti.dfp the ads.

So, at the bottom of the proguard.conf file, add:

# Add the Ads
-keep public class com.google.android.gms.ads.** {
public protected *;
}
# Add the analytics
-keep public class com.google.android.gms.analytics.** {
public protected *;
}

You can add all the classes you want proceeding this way. Go to https://developers.google.com/android/guides/setup, detect the classes you want to include, and add in the proguard file with this syntax:

-keep public class com.google.android.gms.FEATURE.** {
public protected *;
}

This is the final proguard.conf file:

Build the stripped JAR

Move to your jar directory, and type:

java -jar /usr/local/opt/proguard/libexec/proguard.jar @proguard.conf

If no errors occur, the final JAR will be places in the same directory with the name of google-play-services-light.jar

You can effectively verify which classes have been included by typing:

jar tf google-play-services-light.jar

In this case, the output JAR is 1.5 MB instead of 5.5 MB and the files inside are 1434 instead of 5645. Fresh air for the dexer.

Replace the JAR files

And now? You can simply replace the JAR of your modules with this light version of the SDK.

You can make a symbolic link to your JAR instead of copying it, but I had an issue with TiShadow, so I prefer copying it.

Conclusions

This is not a final solution. We’ll wait that Titanium will support the multidex support.