RoboVM Lets Developers Use Java to Build iOS Apps Using native UI’s With Full Hardware Access
|Richard Harris in iOS Thursday, April 2, 2015|
RoboVM Lets Developers Use Java to Build iOS Apps Using native UI’s With Full Hardware Access
RoboVM has released the first stable version of its coding platform which lets developers reuse Java language and tools skills to build iOS Apps using native UI’s and with full hardware access.
RoboVM is setup as a platform with multiple components:
- The ahead-of-time (AOT) compiler
- The runtime
- The class library
- Easily extendible bindings to iOS APIs
- Integration with IDEs
- Integration with build systems
All of these components allow developers to write native iOS apps, share code with Android and the backend, and have a stable build and test. Apart from the debugger, everything is OSS and available on Github.
Here is how RoboVM works:
- RoboVM translates Java bytecode into native ARM or x86 code. Apps run fast directly on the CPU. No interpreter involved.
- RoboVM includes a Java to Objective-C bridge that makes it possible to call into the native iOS CocoaTouch APIs. Objective-C objects can be used just like any other Java object.
- Easily share code between desktop, Android and iOS apps. The standard classes (java.lang.*, java.util.*, etc) included in RoboVM are based on Android’s runtime.
- The compile time tools are GPLv2 licensed. Runtime code is licensed under business friendly licenses, mostly the Apache License v2.0.
- RoboVM comes with Eclipse and Maven integration. Use the tools you’re used to from the Java world.
- The platform is App Store ready with a number of apps built with the help of RoboVM in the App Store.
Here is what the company says about coding with the RoboVM platform:
At the heart of RoboVM lies its AOT compiler. JVMs usually employ just-in-time (JIT) compilation: JVM bytecode is loaded at runtime and transformed to native code. Apple does not allow JIT compilers on iOS, memory pages can not be set as executable. For this reason, RoboVM takes Java 8 bytecode and transforms it to a native executable before an app is deployed to a device. As a result, RoboVM can not load bytecode at runtime, which is pretty much the only limitation of RoboVM.
By taking bytecode as input, RoboVM does not need to care about what produced this bytecode. You can feed RoboVM’s compiler bytecode generated by Java, Scala, Groovy, Kotlin, Clojure or any other alternative JVM language compiler, and it will happily compile it to native code. This is in contrast to approaches such as J2ObjC, which takes Java source code instead of bytecode and transforms it to mostly equivalent Objective-C code. Not having to rely on the original source makes it possible to integrate any 3rd party JAR easily.
As a first step in the compilation process, RoboVM parses the bytecode of a class file via Soot. Soot allows us to transform the stack-machine based JVM bytecode to more manageable 3-address code representation called Jimple. We then apply many optimization and simplification passes on this Jimple code, such as dead code elimination.
Once we are done jimpling up all the things, we apply custom transformations that allow us to support Java 8 language features such as lambdas and our custom binding bridge called Bro, responsible for easily interfacing with C and Objective-C code from Java.
After all these transformations we translate our heavily modified Jimple to LLVM IR. LLVM is the compiler infrastructure used by Clang to compile C, C++, Objective-C and Swift, among other languages. By employing LLVM, we reap the same benefits as those “native” languages: highly optimized emitters for a broad range of architectures such as x86, x86_64, ARM thumbv7 and ARM 64-bit. With the LLVM IR in hand, we generate an architecture specific assembly file that we then assemble to a final object file.
The compiler is modular, so we can add any amount of plugins that interfer with this compilation process. One such plugin allows us to add DWARF metadata to the LLVM IR, which we later use to know where local variables are located on the stack or where to insert instrumentation for debugging.
Every class file is hence compiled to a corresponding object file. Doing this for every compilation would be rather time-consuming, which is why RoboVM employs incremental compilation: only the set of class files that changed since the last compilation is recompiled, along with their dependent classes. The resulting object files are cached for re-use.
Just blindly compiling all classes specified in the classpath of a project would also not be the smartest thing to do. Given a main class as the main entry point, RoboVM will figure out the set of classes that are actually required. We can bring down the number of classes, methods and fields being compiled even further by tree-shaking, an area we are currently investigating.
With all the object files for a project in hand, we can link the final executable. RoboVM allows you to easily specify any additional native frameworks or libraries you want to link in, either via annotations in code or the project’s configuration file for non-system frameworks and libraries. We also need to link in RoboVM’s runtime, consisting of things like the GC, debugging support, native code for the class library and so on.
An executable alone is not enough, so RoboVM will also take any resources you have, optionally run them through tools such as TextureAtlas, and compile the final application bundle, ready for submission to the app store!
A virtual machine requires a runtime to provide low-level services to the user code. Such services include garbage collection, threading, reflection and so on. Let’s look at a few pieces of RoboVM’s runtime.
One central part of RoboVM’s runtime is the garbage collector GC. RoboVM uses the Boehm-Demers-Weisser GC, a conservative GC originally designed for languages like C or C++. A conservative GC has to scan the heap and registers under the assumption that anything can be a pointer to managed data. This sounds suboptimal, and it is, but with some tuning and optimizations, the Boehm GC can be a rather capable GC.
RoboVM tunes the Boehm GC in such a way, that it’s working in mostly-precise mode. The GC does not have to blindly scan large portions of the heap, but can instead rely on some additional information we give to it. This way, the GC only has to scan registers and the stack conservatively, greatly improving performance. On top of this, we also enable thread local allocation, meaning we do not have to take a global lock when allocating memory for an object. Finally, we also enable parallel mark & sweep, distributing the GC load to multiple threads, reducing GC latency.
The runtime is also responsible for supporting reflection. A RoboVM executable stores all information required to support the full reflection capabilities found in standard JVMs, including method invocation and proxies. We use a few assembler tricks for the latter two features, which makes RoboVM a very capable runtime for Java bytecode.
The Class Library
Java is known for its extensive standard class library. Any 3rd party library your app depends on expects certain classes of that standard library to be in place. RoboVM takes its class library from Android, which itself is a fork based on the now defunct Apache Harmony. This means that any 3rd party library that works on Android will also work on RoboVM, with the exclusion of Android specific APIs, such as Android’s UI layer.
Just taking Android’s class library and hoping for the best when using it on iOS is of course not going to work. The class library does not only consist of Java code. To interface with system services such as the file system, there’s also a considerable amount of native C/C++ code which is exposed to Java via JNI.
We therefore have to massage those native bits a little bit to make them work on iOS. Both Android and iOS are supposed to implement the Posix standard. In reality, they both have a slightly different interpretation of this standard when it comes to things like networking or threading.
A standard class library has to be rock solid, especially if you port it to a new operating system. We run tests against 3 huge test suites that guarantee the proper behaviour of both the class library and the VM features the class library relies upon. These test suites come from the original Apache Harmony project and the Android Dalivk/ART runtime.
With RoboVM, we want to enable you to write native applications instead of pushing your UI to a webview. This means that you need full access to the iOS frameworks required to implement your UIs, interface with the hardware or use services such as in-app purchasing or notifications. Historically, JVMs interface with native code through the Java Native Interface, a rather cumbersome way of talking to C/C++/Objective-C code.
We wanted something simpler, with better performance. Which is why we came up with Bro, our custom Java-to-native bridge. Bro is inspired by JNA and other similar JVM solutions, as well as .NET’s p/invoke.
With Bro, you can wrap any native C or Objective-C API in pure Java code via annotations…This is of course only the tip of the ice-berg. RoboVM will automatically marshal more complex data types between the Java and native side. You can also bind C structs, Objective-C classes, which is what we do to give you full access to all of iOS’ frameworks and APIs. You can even subclass Objective-C classes!
Bro allows you to use iOS frameworks and APIs in a very natural way. We already did all the heavy lifting and created bindings for all of iOS frameworks and APIs. RoboVM will also take care of the interaction with Objective-C’s Automatic Reference Counting (ARC) mechamism. RoboVM will do the “right thing”, so you do not have to think about it too hard. Should you still require to interact with ARC, RoboVM lets you do it.
The bindings to iOS frameworks & APIs are generated semi-automatically. If you want to create a binding for an existing iOS library, you can employ our binding generator and modify things to suite your needs. To date, we’ve bound 98% of all iOS 8.1 framworks and APIs.
Now, not every scenario lends itself well to writing two separate UI layers. Especially in the enterprise, there’s a need to get things going quickly, with minor compromises regarding the UIs native look and feel. For this use-case, we support JavaFX on both iOS and Android. This approach allows you to share 100% of your code between both platforms. We partner with LogdON to ensure JavaFX works smoothly across iOS and Android.
A RoboVM commercial license give you access to a Java Debug Wire Protocol (JDWP) compatible debugger. JDWP is what allows you to debug a Java process from within your IDE. RoboVM supports JDWP as well, so you can debug apps an both the simulator and the device, using the tools you are used to.
RoboVM’s implements a soft-debugger: the runtime cooperates with the debugger at specific safe-points in the user code to implement thread suspension, stepping, breakpoints and memory inspection. In debugging mode, the app will spin up an additional thread that listens for commands over TCP. On the other side of the TCP channel sits our JDWP server, implementing the protocol on-top of our custom, minimal debugging protocol.
Alternative approaches such as GDB and LLDB use OS services to control the process. We actually investigated the use of LLDB in the beginning but ultimately decided against it. First, LLDB is process-centric: if one thread stops, all other threads have to stop as well. This is ultimately incompatible with JDWP’s thread-centric model: you can stop one or more threads, while the remaining threads continue to run. The second reason for deciding against LLDB is that our JDWP server would have to take over the process via LLDB. This would prevent you from debugging the native side of your app while also debugging the Java-side. As it stands, you can simultaneously debug your app from your Java IDE as well as XCode!
The RoboVM debugger allows you to do anything you are able to do with a normal JVM debugger: suspend/resume threads, set (conditional) breakpoints, step in/out/over source lines, modify variables at runtime and invoke methods, e.g. via Eclipse’ display view or Intellij IDEA’s “Watch Expressions” dialog. All of this works both on the simulator and on the device.
One of the biggest reason we are working on RoboVM is to bring the tooling around the JVM to iOS. For the past year we have focused on Eclipse, which is our current default development environment. Our Eclipse plugin supports the easy creation of both console and iOS projects. It integrates the debugger and JUnit support for a smooth development experience. You can also pair Eclipse with either Gradle or Maven for saner builds and continuous integration.
Android has since shifted fully to Android Studio, which is based on JetBrain’s Intellij IDEA. We have an initial version of the RoboVM Intellij IDEA plugin ready and are working hard on bringing it up to the quality of the Eclipse plugin. We realize the importance of support for both IDEA and Android Studio, especially if you do cross-platform development. Expect more in this area in the upcoming weeks!
Finally, there’s also some community work on NetBeans integration.
For a full featured experience, we also have to play nice with the tools provided by Apple. We are currently working on full interface builder integration, which should make creating basic UIs even simpler. Interface builder integration will be a commercial feature and is planned to be released in Q2 2015.
Build System Integration
RoboVM supports a wide range of build systems. For the die-hard, we provide a distribution of RoboVM that you can invoke from your command line like any other compiler system. That way you can integrate RoboVM with shell scripts should the need arise.
However, we’d strongly suggest to use more established build systems! We created a dedicated Maven plugin as well as a Gradle plugin, which should make your life considerably easier.
Going forward, we’ll provide project templates both via the Maven archetype as well as the IDE integration so setup not only iOS RoboVM projects, but also cross-platform iOS and Android projects.
Where to go from here?
By now, you should have a pretty good idea of what RoboVM is and how it can help you. If we piqued your interest, head over to our documentation page, and get started today!
If you just want to check out some code, we recommend looking through our ports of Apple’s iOS sample apps.
Finally, if you are very adventurous, you can also check out the RoboVM internals, right on Github!
Read more: http://www.robovm.com