The Case of the Missing Method

The android build process, it has been the cause of much hardship.  We have a lovely continuous integration machine, churning away after every commit, but only ever spitting out a useless string of ones and zeros.  Several attempts had been made before.  We were now getting a signed APK, but still a useless one.  The fact that everything compiles just fine on our dev machines allowed us to put off facing this problem for a while. but the time had come.

Log onto the build machine, crack open the logcat and brace yourself for terrifying ANT…

AAAARRRRRRGGGGGGHHHHHH!

We’re going to fix this.

So, first things first, what does the logcat say?
java.lang.RuntimeException: Unable to create application x.xxxx.handheld.HandheldApplication: com.google.inject.internal.util.$ComputationException: java.lang.NoSuchMethodException
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:3398)
at android.app.ActivityThread.access$2200(ActivityThread.java:124)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1006)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3806)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)
Caused by: com.google.inject.internal.util.$ComputationException: java.lang.NoSuchMethodException
at com.google.inject.internal.util.$MapMaker$StrategyImpl.compute(MapMaker.java:553)
at com.google.inject.internal.util.$MapMaker$StrategyImpl.compute(MapMaker.java:419)
at com.google.inject.internal.util.$CustomConcurrentHashMap$ComputingImpl.get(CustomConcurrentHashMap.java:2041)
at com.google.inject.internal.FailableCache.get(FailableCache.java:50)
at com.google.inject.internal.MembersInjectorStore.get(MembersInjectorStore.java:65)
at com.google.inject.internal.InjectorImpl.getMembersInjector(InjectorImpl.java:950)
at com.google.inject.internal.InjectorImpl.getMembersInjector(InjectorImpl.java:957)
at com.google.inject.internal.InjectorImpl.injectMembers(InjectorImpl.java:943)
at roboguice.inject.ContextScopedRoboInjector.injectMembersWithoutViews(ContextScopedRoboInjector.java:243)
at roboguice.inject.ContextScopedRoboInjector.injectMembers(ContextScopedRoboInjector.java:236)
at roboguice.RoboGuice.injectMembers(RoboGuice.java:156)
at x.xxxx.handheld.HandheldApplication.onCreate(HandheldApplication.java:70)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:969)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:3395)
... 10 more
Caused by: java.lang.NoSuchMethodException
at java.lang.Class.getDeclaredMethods(Native Method)
at java.lang.ClassCache.getDeclaredMethods(ClassCache.java:140)
at java.lang.Class.getDeclaredMethods(Class.java:757)
at com.google.inject.spi.InjectionPoint.getInjectionPoints(InjectionPoint.java:662)
at com.google.inject.spi.InjectionPoint.forInstanceMethodsAndFields(InjectionPoint.java:356)
at com.google.inject.internal.MembersInjectorStore.createWithListeners(MembersInjectorStore.java:90)
at com.google.inject.internal.MembersInjectorStore.access$000(MembersInjectorStore.java:34)
at com.google.inject.internal.MembersInjectorStore$1.create(MembersInjectorStore.java:42)
at com.google.inject.internal.MembersInjectorStore$1.create(MembersInjectorStore.java:39)
at com.google.inject.internal.FailableCache$1.apply(FailableCache.java:39)
at com.google.inject.internal.util.$MapMaker$StrategyImpl.compute(MapMaker.java:549)

Oh god, its a bloody NoSuchMethodException.  These things have been haunting us since the project began.  Just when you think you’ve got away from them.  Why Java, why?  The methods are right there.  Right THERE!

behind you!

Trusty old Guice is right there in the middle of it before.  Guice just so happens to have the greatest stack traces I’ve ever seen – long, long comments with tips on how to solve the problem.  But no such luck here.  Just a mysterious error about a missing method.  This could be a three pipe problem.

A little closer inspection reveals that the crash takes place within the Application class’s onCreate method.  Pretty much the first thing the App does.  And it happens when Guice is ordered to inject the members of the main controller class.

Now, being a controller, that class has a fair bit going on (maybe too much, but that’s refactoring for another day).  So we create a new, simple class, and inject into that first.  This gives us our first clue.  The injection occurs just fine – thank goodness, Guice is not the villain of the piece, and I can stand by my previous comments.

So, now we have a strategy to find out the cause of our error.  Just slowly add bits to the dummy class and see what fails.

Might I remind you at this point, this problem only manifests on the build machine, so each iteration is a frustrating 10 minute cycle.

Several builds later and the culprit is identified.  It is a method calling a Jar that we have created to wrap a bit of code.

For some complicated reasons we didn’t want that code to be in our main source control, so a separate repo had been created.  Now the code itself was not particularly big, and was entirely finished, so no kind of continuous integration had been set up for it.  The Jar had been built on a dev machine, then added to the main repo.

Well this clearly had to be something to do with our problems, but why the hell would this work locally but not on build?

I wish I could tell you the flash of insight that revealed the answer, but I still don’t really get how Gary got to it.  But the problem was the Jar had been compiled under Java 7, recompiling it under Java 6 and all those problems went away.

Damn it Java!