Running Clojure on Android: "can't load this type of class file"

Viewed 1372

I'm trying to get Clojure and JavaFXPorts running on Android. But it crashes at startup with the following stacktrace. I know too little about the JVM internals, so I'm at a loss about why this particular UnsupportedOperationException happens. My assumption is that Clojure registers a new class file type with its compiler, and for some reason this fails on Android - at least in combination with JavaFXPorts (I didn't try creating an Android app without JavaFXPorts; I'm going to need it anyway).

10-30 19:00:24.450 11088 11130 W System.err: java.lang.reflect.InvocationTargetException
10-30 19:00:24.450 11088 11130 W System.err:    at java.lang.reflect.Method.invoke(Native Method)
10-30 19:00:24.450 11088 11130 W System.err:    at javafxports.android.DalvikLauncher$1.run(DalvikLauncher.java:188)
10-30 19:00:24.451 11088 11130 W System.err:    at java.lang.Thread.run(Thread.java:761)
10-30 19:00:24.452 11088 11130 W System.err: Caused by: java.lang.RuntimeException: Unable to construct Application instance: class lyrion.cec.core
10-30 19:00:24.452 11088 11130 W System.err:    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:907)
10-30 19:00:24.452 11088 11130 W System.err:    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$138(LauncherImpl.java:182)
10-30 19:00:24.453 11088 11130 W System.err:    at com.sun.javafx.application.LauncherImpl.access$lambda$1(LauncherImpl.java)
10-30 19:00:24.453 11088 11130 W System.err:    at com.sun.javafx.application.LauncherImpl$$Lambda$2.run(Unknown Source)
10-30 19:00:24.453 11088 11130 W System.err:    ... 1 more
10-30 19:00:24.454 11088 11130 W System.err: Caused by: java.lang.ExceptionInInitializerError
10-30 19:00:24.454 11088 11130 W System.err:    at java.lang.Class.classForName(Native Method)
10-30 19:00:24.454 11088 11130 W System.err:    at java.lang.Class.forName(Class.java:400)
10-30 19:00:24.454 11088 11130 W System.err:    at clojure.lang.RT.classForName(RT.java:2204)
10-30 19:00:24.454 11088 11130 W System.err:    at clojure.lang.RT.classForName(RT.java:2213)
10-30 19:00:24.455 11088 11130 W System.err:    at clojure.lang.RT.loadClassForName(RT.java:2232)
10-30 19:00:24.455 11088 11130 W System.err:    at clojure.lang.RT.load(RT.java:450)
10-30 19:00:24.455 11088 11130 W System.err:    at clojure.lang.RT.load(RT.java:426)
10-30 19:00:24.455 11088 11130 W System.err:    at clojure.lang.RT.doInit(RT.java:468)
10-30 19:00:24.455 11088 11130 W System.err:    at clojure.lang.RT.<clinit>(RT.java:336)
10-30 19:00:24.455 11088 11130 W System.err:    at clojure.lang.Namespace.<init>(Namespace.java:34)
10-30 19:00:24.456 11088 11130 W System.err:    at clojure.lang.Namespace.findOrCreate(Namespace.java:176)
10-30 19:00:24.456 11088 11130 W System.err:    at clojure.lang.Var.internPrivate(Var.java:153)
10-30 19:00:24.456 11088 11130 W System.err:    at lyrion.cec.core.<clinit>(Unknown Source)
10-30 19:00:24.456 11088 11130 W System.err:    at java.lang.reflect.Constructor.newInstance0(Native Method)
10-30 19:00:24.456 11088 11130 W System.err:    at java.lang.reflect.Constructor.newInstance(Constructor.java:430)
10-30 19:00:24.456 11088 11130 W System.err:    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$144(LauncherImpl.java:819)
10-30 19:00:24.457 11088 11130 W System.err:    at com.sun.javafx.application.LauncherImpl.access$lambda$7(LauncherImpl.java)
10-30 19:00:24.457 11088 11130 W System.err:    at com.sun.javafx.application.LauncherImpl$$Lambda$8.run(Unknown Source)
10-30 19:00:24.457 11088 11130 W System.err:    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$158(PlatformImpl.java:326)
10-30 19:00:24.457 11088 11130 W System.err:    at com.sun.javafx.application.PlatformImpl.access$lambda$6(PlatformImpl.java)
10-30 19:00:24.457 11088 11130 W System.err:    at com.sun.javafx.application.PlatformImpl$$Lambda$7.run(Unknown Source)
10-30 19:00:24.457 11088 11130 W System.err:    at com.sun.javafx.application.PlatformImpl.lambda$null$156(PlatformImpl.java:295)
10-30 19:00:24.458 11088 11130 W System.err:    at com.sun.javafx.application.PlatformImpl.access$lambda$18(PlatformImpl.java)
10-30 19:00:24.458 11088 11130 W System.err:    at com.sun.javafx.application.PlatformImpl$$Lambda$19.run(Unknown Source)
10-30 19:00:24.458 11088 11130 W System.err:    at java.security.AccessController.doPrivileged(AccessController.java:57)
10-30 19:00:24.458 11088 11130 W System.err:    at com.sun.javafx.application.PlatformImpl.lambda$runLater$157(PlatformImpl.java:294)
10-30 19:00:24.458 11088 11130 W System.err:    at com.sun.javafx.application.PlatformImpl.access$lambda$5(PlatformImpl.java)
10-30 19:00:24.458 11088 11130 W System.err:    at com.sun.javafx.application.PlatformImpl$$Lambda$6.run(Unknown Source)
10-30 19:00:24.459 11088 11130 W System.err:    at com.sun.glass.ui.monocle.RunnableProcessor.runLoop(RunnableProcessor.java:93)
10-30 19:00:24.459 11088 11130 W System.err:    at com.sun.glass.ui.monocle.RunnableProcessor.run(RunnableProcessor.java:52)
10-30 19:00:24.459 11088 11130 W System.err:    ... 1 more
10-30 19:00:24.460 11088 11130 W System.err: Caused by: java.lang.UnsupportedOperationException: can't load this type of class file, compiling:(clojure/core_instant18.clj:9:1)
10-30 19:00:24.460 11088 11130 W System.err:    at clojure.lang.Compiler.analyzeSeq(Compiler.java:7010)
10-30 19:00:24.461 11088 11130 W System.err:    at clojure.lang.Compiler.analyze(Compiler.java:6773)
10-30 19:00:24.461 11088 11130 W System.err:    at clojure.lang.Compiler.eval(Compiler.java:7059)
10-30 19:00:24.461 11088 11130 W System.err:    at clojure.lang.Compiler.load(Compiler.java:7514)
10-30 19:00:24.493 11088 11130 W System.err:    at clojure.lang.RT.loadResourceScript(RT.java:379)
10-30 19:00:24.494 11088 11130 W System.err:    at clojure.lang.RT.loadResourceScript(RT.java:370)
10-30 19:00:24.494 11088 11130 W System.err:    at clojure.lang.RT.load(RT.java:460)
10-30 19:00:24.494 11088 11130 W System.err:    at clojure.lang.RT.load(RT.java:426)
10-30 19:00:24.494 11088 11130 W System.err:    at clojure.core$load$fn__6548.invoke(core.clj:6046)
10-30 19:00:24.494 11088 11130 W System.err:    at clojure.core$load.invokeStatic(core.clj:6045)
10-30 19:00:24.495 11088 11130 W System.err:    at clojure.core$load.doInvoke(core.clj:6029)
10-30 19:00:24.495 11088 11130 W System.err:    at clojure.lang.RestFn.invoke(RestFn.java:408)
10-30 19:00:24.495 11088 11130 W System.err:    at clojure.core$fn__8055.invokeStatic(core.clj:6709)
10-30 19:00:24.495 11088 11130 W System.err:    at clojure.core$fn__8055.invoke(core.clj:6708)
10-30 19:00:24.495 11088 11130 W System.err:    at clojure.core__init.load(Unknown Source)
10-30 19:00:24.495 11088 11130 W System.err:    at clojure.core__init.<clinit>(Unknown Source)
10-30 19:00:24.496 11088 11130 W System.err:    ... 31 more
10-30 19:00:24.497 11088 11130 W System.err: Caused by: java.lang.UnsupportedOperationException: can't load this type of class file
10-30 19:00:24.497 11088 11130 W System.err:    at java.lang.ClassLoader.defineClass(ClassLoader.java:520)
10-30 19:00:24.497 11088 11130 W System.err:    at clojure.lang.DynamicClassLoader.defineClass(DynamicClassLoader.java:46)
10-30 19:00:24.497 11088 11130 W System.err:    at clojure.lang.Compiler$ObjExpr.getCompiledClass(Compiler.java:4979)
10-30 19:00:24.498 11088 11130 W System.err:    at clojure.lang.Compiler$FnExpr.parse(Compiler.java:4109)
10-30 19:00:24.498 11088 11130 W System.err:    at clojure.lang.Compiler.analyzeSeq(Compiler.java:7001)
10-30 19:00:24.498 11088 11130 W System.err:    ... 46 more
2 Answers

First the spoiler: for Clojure it has been done already.

The problem is that Clojure has a DynamicClassLoader which as you might guess, loads classes made up at runtime, and Android doesn't support that. In fact, you can do that on the JVM via ClassLoader.defineClass(), but the implementation of that method on Android just throws the exception you saw, meaning that Clojure cannot even initialize on Android.

Another issue is that even if that method was available, Android would not be able to run the JVM bytecode generated by Clojure, precisely because it is JVM bytecode.

Fortunately this hero solved this problem by adding another dynamic ClassLoader which converted the bytes of the dynamic class to a DEX file, and then to ODEX for the Android runtime to accept it (pretty similar if not the same that Instant Run does on Android Studio).

Unfortunately that was on 2011 and his Clojure fork has not seen maintainance for four years.

You can see the author of the Android fork for Clojure explain it in his wonderful talk at ClojureConj 2011.

Related