Play Services 5.0 Is A Monolith Abomination

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:

(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:

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 14824 $ 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 20298

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