Jake Wharton

Play Services 5.0 Is A Monolith Abomination

03 July 2014

Guava is a monolithic library, but that’s not necessarily a bad thing. Nobody thinks twice when bundling it for the JVM. In the world of Android the mention of Guava has a bit of a negative stigma due to the dex file format’s method limit and a concern about bloating APK size. The latter is no longer a valid argument. The dex method limit is a hard 64k limit to which Guava contributes just over 14k methods. 20% of this hard limit vanishes when you include Guava.

Sounds scary, right? It isn’t.

Google Play Services 5.0 which just launched contributes over twenty thousand methods to your app. 20k+. One third of the limit! Now that is scary.

The Play Services library includes proprietary functionality built on the normal Android APIs and a separate APK downloaded on all devices with the Play Store. Some of the services it provides are invaluable. Like Guava it is also a monolothic library but it is a bad thing in this case.

A lot of really cool functionality is being put in Play Services. You’ll have a hard time making a compelling app that lives in the Google Play ecosystem without it. You should want to put it in your applications and not have to worry about the overhead it brings.

Most of the library’s offerings are very disparate, having only the fact that they’re by Google as a common thread. This screams for small, modular artifacts which can be composed!

Google, it’s time to unbundle. All the cool kids are doing it. (Spoiler alert: it happened)

At worst, we specify a few dependencies manually:

dependencies {
  compile 'com.google.android.gms:play-services-ads:5.0.+'
  compile 'com.google.android.gms:play-services-analytics:5.0.+'
  compile 'com.google.android.gms:play-services-games:5.0.+'

Best case would be a plugin that provided a clear DSL to what you were getting and offered easier configuration of the various components.

apply plugin: 'com.google.playservices'

playServices {
  version '5.0.+'
  components 'ads', 'analytics', 'games'

(You can even still provide the “fat” jar in both the dependency management world and the people who like manual dependency management.)

ProGuard is not the answer. Yes, for release builds it’s nice to strip out any methods which are not being used. However, this is not justification for having large chunks of unused code as dependencies. Besides, if you read my post on a simulator you know that we deserve a faster development build pipeline which removes steps, not adds them.

It’s not going to be a walk in the park but the packages inside Play Services are surprisingly well-configured to partitioning:

Play Services Dependency Diagram

(Top-left: Games, top-center: Drive, middle-left: Plus, middle: common, middle-right: Maps, bottom: Ads)

Here’s Guava for comparison which has less clear partition lines:

Guava Dependency Diagram

Here’s how the method counts were determined:

$ curl 'http://search.maven.org/remotecontent?filepath=com/google/guava/guava/17.0/guava-17.0.jar' > guava.jar
$ ~/android-sdk/build-tools/20.0.0/dx --dex --output guava.dex guava.jar
$ dex-method-count guava.dex

$ cp ~/android-sdk/extras/google/m2repository/com/google/android/gms/play-services/5.0.77/play-services-5.0.77.aar .
$ unzip play-services-5.0.77.aar
$ ~/android-sdk/build-tools/20.0.0/dx --dex --output play-services.dex classes.jar
$ dex-method-count play-services.dex

And the full by-package breakdown of Play Services:

$ dex-method-count-by-package play-services.dex
20298 com
20298 com.google
207   com.google.ads
169   com.google.ads.mediation
73    com.google.ads.mediation.admob
62    com.google.ads.mediation.customevent
20188 com.google.android
20188 com.google.android.gms
2     com.google.android.gms.actions
480   com.google.android.gms.ads
135   com.google.android.gms.ads.doubleclick
25    com.google.android.gms.ads.identifier
88    com.google.android.gms.ads.mediation
4     com.google.android.gms.ads.mediation.admob
73    com.google.android.gms.ads.mediation.customevent
26    com.google.android.gms.ads.purchase
118   com.google.android.gms.ads.search
866   com.google.android.gms.analytics
52    com.google.android.gms.analytics.ecommerce
10    com.google.android.gms.appindexing
151   com.google.android.gms.appstate
80    com.google.android.gms.auth
644   com.google.android.gms.cast
1026  com.google.android.gms.common
12    com.google.android.gms.common.annotation
382   com.google.android.gms.common.api
235   com.google.android.gms.common.data
202   com.google.android.gms.common.images
126   com.google.android.gms.common.internal
126   com.google.android.gms.common.internal.safeparcel
1940  com.google.android.gms.drive
87    com.google.android.gms.drive.events
897   com.google.android.gms.drive.internal
241   com.google.android.gms.drive.metadata
202   com.google.android.gms.drive.metadata.internal
205   com.google.android.gms.drive.query
151   com.google.android.gms.drive.query.internal
451   com.google.android.gms.drive.realtime
451   com.google.android.gms.drive.realtime.internal
123   com.google.android.gms.drive.realtime.internal.event
38    com.google.android.gms.drive.widget
332   com.google.android.gms.dynamic
4534  com.google.android.gms.games
73    com.google.android.gms.games.achievement
113   com.google.android.gms.games.event
2956  com.google.android.gms.games.internal
858   com.google.android.gms.games.internal.api
43    com.google.android.gms.games.internal.constants
8     com.google.android.gms.games.internal.data
31    com.google.android.gms.games.internal.events
9     com.google.android.gms.games.internal.experience
215   com.google.android.gms.games.internal.game
56    com.google.android.gms.games.internal.multiplayer
23    com.google.android.gms.games.internal.notification
80    com.google.android.gms.games.internal.player
86    com.google.android.gms.games.internal.request
256   com.google.android.gms.games.leaderboard
640   com.google.android.gms.games.multiplayer
239   com.google.android.gms.games.multiplayer.realtime
256   com.google.android.gms.games.multiplayer.turnbased
213   com.google.android.gms.games.quest
150   com.google.android.gms.games.request
210   com.google.android.gms.games.snapshot
47    com.google.android.gms.gcm
111   com.google.android.gms.identity
111   com.google.android.gms.identity.intents
62    com.google.android.gms.identity.intents.model
5760  com.google.android.gms.internal
295   com.google.android.gms.location
2342  com.google.android.gms.maps
804   com.google.android.gms.maps.internal
1068  com.google.android.gms.maps.model
483   com.google.android.gms.maps.model.internal
14    com.google.android.gms.panorama
902   com.google.android.gms.plus
352   com.google.android.gms.plus.internal
316   com.google.android.gms.plus.model
192   com.google.android.gms.plus.model.moments
126   com.google.android.gms.plus.model.people
33    com.google.android.gms.security
1367  com.google.android.gms.tagmanager
867   com.google.android.gms.wallet
376   com.google.android.gms.wallet.fragment
143   com.google.android.gms.wallet.wobs
1011  com.google.android.gms.wearable
714   com.google.android.gms.wearable.internal

You can grab these two scripts from here: gist.github.com/JakeWharton/6002797

The dependency graphs were generated using degraph and yEd. Download the .graphml for Play Services and Guava.

— Jake Wharton