MinimalBible.github.io/_posts/2014-09-01-why-not-groovy.md
2015-01-13 19:09:25 -05:00

10 KiB

layout title modified tags image comments share
post Why not Groovy? 2014-09-01 13:36:35 -0400
groovy
dex
64k
play services
feature credit creditlink
true

So, a while back one of the top Groovy developers released a version of Groovy that was ready to run on Android. Even beyond that, it comes with it's nice own Gradle plugin so all you really have to do is add the plugin to build and wa-la! You're good to go!

Now, I would like to say first off that I do sincerely believe that using Groovy would drastically speed up development time. Being able to use closures is great, and I'm a huge fan of dynamic languages (I've spent a lot of time in Python development). Even the New York Times is getting on board.

Unfortunately, it's simply not a possibility for me.

Because of the 64k method limit

So why won't I be using Groovy? Simply put, the 64k method limit for Dex.

This is a well documented issue in many Android projects. The crux of the issue is this: the Android format only supports being able to call 65,536 methods in an application. And while you likely won't write anywhere near that many methods yourself, trying to depend on external libraries fast approaches that limit. So far as I can tell, there are 4 solutions to addressing this problem:

Proguard

Proguard is a code obfuscation tool, that also uses static analysis to do some cool things. For example, Proguard can locate unused methods and strip them out of your application. Sounds nice, right? There are two problems.

  1. Proguard takes a while to run. Even if it's only 30 seconds, that's an extra 30 seconds per build that you lose in development time. It adds up and its frustrating to boot.

  2. Proguard isn't that great at removing methods. Proguard can only remove methods that aren't referred to by anyone else. So if you have a method that is unused during the application, but can still be reached, Proguard can't remove it. This is an issue especially in the Play services library - many methods will never be used, but Proguard can't guarantee that.

While eventually I'll be using Proguard for my release builds, in the mean time it would be painfully slow to wait 30 seconds every time to debug a build on my device.

Dynamic Classloading

Google put up a blog post detailing how you can use Dynamic Classloading to circumvent the 64k method limit. The basic workflow is this:

  1. Build a .dex file separate from your main application - this can contain an external library or whatever else is needed. This should be stored in your assets/ folder.

  2. Use code to load the external .dex file into memory

  3. This is where it gets interesting. To actually use this new Dex file you must either get classes via reflection and bind them to interfaces (such that the interface contains the methods you need), or call everything via reflection.

But if you can accomplish all this, you're done. That said, while this works, it's practically impossible to write maintainable code with this. There's an incredible amount of reflection involved and needed to make this work, and can not be accomplished in Eclipse because it doesn't support the build complexity. This is hardly a solution.

Native Interfaces

While I won't be investigating this one much, a similar solution would be to write as much of the code as possible in a native language (i.e. C++, C, etc.) and then call into that code using Java. Arguably this runs into similar issues as the Dynamic Classloading, but at least there's an API for it.

Alternatively, you can write Javascript for a WebView component and outsource most of your execution. The problem with this is you lose an incredible amount in terms of memory and performance. So while it would theoretically work, it would end up being painfully noticeable.

Use fewer libraries

This unfortunately seems to be the most practical solution. I haven't done any benchmarking, but I'd be willing to guess the time it takes you to write the code yourself is less than the time it would take to implement a solution above. While it forces you to write lower-level code, it's not anywhere near the point of trying to use reflection to load a library at runtime.

Unfortunately you do become very limited in what external libraries you can use. There are a great number of productivity-boosting libraries and things available, but sacrifices have to be made.

Summary

The 64k method limit in Dex is painful. And before you think that you'll never get anywhere near that limit, think about this:

  • The Google Play Services library itself contains 20 thousand methods. And you can't just use part of it. If you depend on Play services at all, one-third of your method count disappears

    • To be fair, people have invented ways of stripping out components you don't need. But this is still ridiculous.
  • The Groovy language implementation for Android uses roughly 30 thousand methods. A full half of your method count is immediately gone if you try and use Groovy.

So while both Play Services and Groovy provide some great features, it's basically one or the other. You can't have both. The 64k method limit is a big problem, and prevents people from using some of the great technology available. While I understand this is a technical nightmare to solve, there isn't really an "official" solution from Google to work around this. So, due to practical concerns, I won't be using Groovy. Much as I'd like to.

P.S.

If you're interested to see how many methods are used in your application, check out the script over here. It's a fantastic resource.

Finally, I've attached below a method count for a bare-bones app compiled to use Groovy. Enjoy!

Groovy Method Count

Read in 31289 method IDs.

<root>: 31289
    android: 5
        app: 3
        view: 2
    com: 19
        example: 17
            bspeice: 17
                testgroovy: 17
        thoughtworks: 2
            xstream: 2
    groovy: 3483
        beans: 111
        grape: 430
        inspect: 21
        io: 59
        lang: 1288
        security: 2
        time: 112
        transform: 265
            builder: 64
            stc: 59
        ui: 30
        util: 1153
            logging: 45
        xml: 12
    groovyjarjarantlr: 3072
        ASdebug: 7
        actions: 357
            cpp: 81
            csharp: 81
            java: 80
            python: 115
        build: 22
        collections: 129
            impl: 89
        debug: 393
            misc: 23
        preprocessor: 172
    groovyjarjarasm: 1319
        asm: 1319
            commons: 451
            signature: 41
            tree: 254
            util: 229
    groovyjarjarcommonscli: 219
    groovyjarjarharmonybeans: 174
        editors: 88
        internal: 10
            nls: 10
    groovyjarjaropenbeans: 815
        beancontext: 208
    java: 1342
        applet: 2
        awt: 51
            event: 6
        io: 179
        lang: 562
            annotation: 12
            invoke: 44
            ref: 12
            reflect: 82
        math: 41
        net: 54
        nio: 4
            charset: 4
        security: 18
        sql: 5
        text: 10
        util: 416
            concurrent: 32
                atomic: 13
                locks: 10
            logging: 13
            prefs: 12
            regex: 17
    javax: 74
        swing: 71
            event: 1
            text: 2
            tree: 2
        xml: 3
            parsers: 3
    org: 20767
        apache: 4
            commons: 4
                cli: 4
        codehaus: 20749
            groovy: 20749
                antlr: 2013
                    java: 304
                    parser: 490
                    treewalker: 943
                ast: 2269
                    builder: 660
                    expr: 500
                    stmt: 150
                    tools: 156
                classgen: 1620
                    asm: 842
                        indy: 30
                        sc: 118
                cli: 7
                control: 928
                    customizers: 436
                        builder: 195
                    io: 29
                    messages: 24
                plugin: 2
                reflection: 381
                    android: 3
                    stdclasses: 69
                runtime: 8887
                    callsite: 309
                    dgmimpl: 413
                        arrays: 190
                    m12n: 31
                    memoize: 38
                    metaclass: 463
                    powerassert: 30
                    typehandling: 731
                    wrappers: 30
                syntax: 160
                tools: 678
                    ast: 150
                    gse: 24
                    javac: 104
                    shell: 53
                        util: 38
                transform: 3194
                    sc: 107
                        transformers: 61
                    stc: 585
                    tailrec: 1160
                    trait: 90
                util: 395
                vmplugin: 197
                    v5: 46
                    v6: 1
                    v7: 140
        fusesource: 7
            jansi: 7
        xml: 7
            sax: 7
                helpers: 1