Java programming for Apple's iOS devices is not only possible but it's getting easier all the time. Steve Hannah surveys the recent evolution of the Java iOS landscape, then introduces five open source Java iOS tools. Find out how Avian, Codename One, J2ObjC, RoboVM, and XMLVM resolve the challenges of Java iOS native client development for developers who are ready to go mobile. (Includes an introductory section on garbage collection vs reference counting in Java iOS application development.)
As a Java developer I have felt somewhat excluded by the wall around Apple's iOS garden. Until recently, if I wanted to write an application for an Apple device such as the iPhone or iPad, it meant leaving the Java ecosystem behind and coding in Objective-C. The barriers to entry were both political and technical, but Apple's decision in September 2010 to relax the restrictions on its development tools effectively dissolved the political constraints. Since that announcement, a handful of open source solutions have emerged that allow developers to write iOS applications in Java. I briefly introduce five of those tools in this article:
Strategies for running Java on iOS
Three categories of tools allow Java developers to write to iOS devices:
- Client-side technologies, discussed in this article, run Java code directly on the device using a compile-time conversion to a native binary.
- Hybrid technologies like Oracle ADF deploy both server-side and client-side Java components.
The client-side tools profiled in this article are especially geared to creating native Java apps that run on iOS devices such as the iPhone and iPad. They enable developers to write code in Java that is then compiled into a native iOS binary. Although their user interface and integration strategies vary, each tool provides a native method mechanism that allows you to call Objective-C and C APIs from Java code.
Avian, for instance, uses standard JNI, whereas XMLVM uses its own native method system, which is similar to JNI but provides stronger compile-time validation. RoboVM and XMLVM also provide Java wrappers for many of the iOS APIs (such as UIKit), so that you can develop an entire iOS application in Java without once delving into Objective-C. Codename One solves the user-interface puzzle by providing its own pure-Java lightweight UI toolkit, but it also supports native interfaces that allow you to talk to Objective-C from Java. J2ObjC's answer to native interfaces is similar to GWT's, in that it allows you to define native Objective-C implementations for Java methods inline using OCNI.
Note that this article assumes that you are familiar with the basic architecture of iOS tooling and applications.
Codename One provides a full-development toolchain for writing iOS applications in Java. Applications written in Codename One can be deployed to a variety of devices, including Android, iOS, BlackBerry, Windows Phone, and J2ME (although some features may not be available on lower powered devices). Codename One comes the closest of any Java mobile toolkit to supporting Java's "write once run anywhere" premise. Two components are key to enabling it to work across multiple platforms:
- Lightweight UI toolkit: The cross-platform UI toolkit is similar to Swing. Codename One UIs are pluggable and themeable, enabling them to take on the look-and-feel of a native device. A Codename One app running on an Android device will match the Android look and feel; on iOS it will match the iOS look and feel.
- Pluggable implementation: All of Codename One's platform-dependent code is contained in a single class that can be easily overridden to support another platform. This makes the framework extremely portable. The default iOS port is is currently XMLVM, but it isn't difficult to change it to Avian or RoboVM. (In fact, I created an Avian port as a research exercise without too much difficulty.)
Figure 1 shows a typical path from Java to iOS using Codename One.
Codename One comprises a Java API, the Codename One Designer tool (a WYSIWYG GUI builder), a simulator that allows you to test your application, and a cloud build server that allows you to build your application for any device. One nice thing about the cloud build server is that you don't need to have proprietary tools such as the Apple developer tools installed. Codename One's build server frees you from the platform dependency typically associated with iOS development. With Codename One, you don't need to have a Mac to develop apps for iOS anymore.
It's also possible to set up your own build environment and not depend on the Codename One cloud server. Doing that would entail creating a multi-step build process involving
javac, a build tool such as Ant, XMLVM (see below), and Xcode.
RoboVM is brand new (in version 0.0.1 at time of writing) and much of the Cocoa/Objective-C bridge is unfinished, but it appears to be full of potential. RoboVM is a command-line tool that you can use to compile Java .class files into native machine code. It provides an AOT (ahead-of-time) compiler based on LLVM, C bindings, and an Objective-C bridge, which lays the foundation for pure-Java applications to be deployed on iOS. It also includes a full set of Cocoa bindings (which should enable you to use the iOS native APIs directly from Java) and an Eclipse plugin that allows you to compile and run on the iOS simulator directly from Eclipse.
Figure 3 shows a typical toolchain path from Java to iOS using RoboVM.
RoboVM was designed as a path for Android developers to reuse their business logic code on iOS. You can either create the UI programmatically in Java, or you can use Apple's Interface Builder and Xcode to develop the UI and then copy the Nib file into your RoboVM project and link it up to your Java classes. It includes some nifty annotations that enable you to register Java classes with the Objective-C runtime and register Java methods to respond to Objective-C messages, so that you can define outlets for a Nib file inside a Java class. Figure 4 shows RoboVM's application architecture.
RoboVM is still very young, so if you decide to try it out you should expect to get your hands dirty with its API. You will likely run into missing methods for which you'll need to generate bindings. All of the tools are present but documentation at this early stage is still scarce, so you may have to go through some cycles of trial and error.
Avian is a bit of a misfit in this group because it wasn't written to solve the Java iOS problem specifically. Instead, Avian is a lightweight JVM that includes a tool (
bootimage-generator) that compiles Java bytecode into native binaries that can be run on ARM devices like the iPhone. Avian's creator, Joel Dice, has published a sample iOS application that serves as a proof-of-concept for building iOS applications in Avian.
Figure 5 shows a typical path from Java to iOS using Avian.
Avian doesn't provide specific bindings for iOS native APIs and it doesn't provide special APIs for building mobile applications. Any interaction between Java and the native environment must take place using JNI. Avian includes its own class library that is a more modular and portable subset of JavaSE, but it also allows you to build applications against OpenJDK 7. In order to reduce the size of the resulting binary, Avian uses Proguard to strip out dead code. Figure 6 shows Avian's application architecture.
XMLVM has a much broader focus than most of the other tools discussed here because it aims to allow translation between many different languages. One such translation path is from Java bytecode to C source code, which is used to build iOS applications in Java. The typical XMLVM build process is to write some code in Java, compile it using
javac, then use XMLVM to convert the .class files into .h and .c files. These C source files are then added to an Xcode project and built into an iOS application, as shown in Figure 7.
XMLVM can be invoked from the command-line to perform conversion directly, or it can be invoked to create a skeleton NetBeans project that includes the appropriate libraries and build scripts to compile a Java application for iOS. The "Run" option actually creates an Xcode project with the translated C files, and opens it in Xcode. There you can debug the project, run it on a connected device (iPhone or iPad), or run it in Apple's simulator.
Like RoboVM, XMLVM contains Cocoa bindings that enable you to use native iOS APIs directly from Java, but they have many missing pieces. If you intend to use these bindings you should be prepared to pop the hood and possibly fill in some gaps yourself. Figure 8 shows an XMLVM application architecture using Cocoa wrapper classes for UI.
If you want to include source that has been translated by XMLVM into an existing Xcode project, you can do so, but you will need to take care to add the appropriate build rules and initialization code for the garbage collector. You should also understand the implications of mixing garbage-collected code with reference-counted code. Figure 9 shows an XMLVM application architecture where the Java business logic has been compiled to C and included in an existing Xcode project.
J2ObjC has the narrowest focus of the tools introduced in this article. Open sourced late last year, it was designed by Google specifically to allow code to be shared between Java (Android and GWT) and Objective-C (iOS) projects. Figure 10 shows a typical path from Java to iOS using J2ObjC.
J2ObjC includes a command-line tool for converting Java source code to Objective-C source code. It also includes some Java annotations and a Java compatibility library for Objective-C. J2ObjC's narrow focus makes it an ideal choice if your goal is to share business logic between apps written inJava and Objective-C. Integrating code generated with J2ObjC with an existing Objective-C project is actually painless, and the close correspondence between the original Java class structure and the resulting Objective-C class structure makes it easy to use the APIs from Objective-C.
A key differentiator between J2ObjC and the other solutions is its preference for reference counting over garbage collection (that is, while it can use GC, it isn't supported on iOS). In addition to "transpile-time" management for reference-counting, and its support for manual reference counting and ARC via command-line flags, J2ObjC provides two mechanisms to help you deal with memory management in your Java code:
- Annotations to provide memory management hints (e.g.
- A memory profiling tool to help detect memory cycles, called
Garbage collection versus reference counting
While garbage collection makes our lives easier inside the Java world, it complicates them in the native world, where reference counting is the rule of the day. For instance, if you set a property of an Objective-C object to be a Java object, the garbage collector will know nothing about this reference. As a result, it may free the memory of the object if it is no longer accessible inside the Java world, even if the native world still needs it. The solution is to introduce your own form of reference counting for objects that you wish to maintain references to inside non garbage-collected structures.
ARC on iOS
Automatic reference counting (ARC) on iOS 5 and higher places the compiler in charge of counting references, rather than the developer. ARC works the same as garbage collection except that it doesn't handle cycles, for instance in cases where an object contains a reference to another object, which (transitively) contains a reference to the first object. In this case a "dead" cycle of objects would be freed when using garbage collection, but not when using ARC. The solution provided by ARC is to denote some references as "weak" references so that they don't affect the reference count for an object. This can effectively break a cycle and allow these cycles of objects to be correctly freed when they are no longer referenced. See Resources to learn more.