More concretely, I’m asking this: why aren’t applications compiled fully to native code before distribution rather than bytecode that runs on some virtual machine or runtime environment?
Implementation details aside, fundamentally, an Android application consists of bytecode, static resources, etc. In the Java world, I understand that the main appeal of having the JVM is to allow for enhanced portability and maybe also improved security. I know Android uses ART, but it remains that the applications are composed of processor-independent bytecode that leads to all this complex design to convert it into runnable code in some efficient manner. See: ART optimizing profiles, JIT compilation, JIT/AOT Hybrid Compilation… that’s a lot of work to support this complex design.
Android only officially supports arm64 currently, so why the extra complexity? Is this a vestigial remnant of the past? If so, with the move up in minimum supported versions, I should think Android should be transitioning to a binary distribution model at a natural point where compatibility is breaking. What benefit is being realized from all this runtime complexity?
The Androids book by Chet Haase provides a good look at the early history and design decisions of the platform and how they came to be made.
At the time there was a debate inside the team over what language their app development framework should use, with native C++ and Java being the two main options (I think there might have been another option or two, I can’t recall). In the end Java won out, and from memory one of the main reasons was to make it easier to make apps and not need to think about the lower level parts of the platform, i.e. the platform takes on the complexity internally in order to lower the barrier to entry for app developers. The idea being that a lower barrier to entry would result in more apps being developed for the platform. For a brand new platform that lives and dies by the apps available for it, that’s a pretty sensible trade-off.
And yes, Android has a lot of vestigial remnants of the past, the Android framework team has been very particular about maintaining as much backwards compatibility as possible within the framework.
deleted by creator
Thank you; I will definitely add this to my reading list.
First of all, since the very early days Android has always allowed apps to make use of native code using the “NDK”, and in fact most games and most apps that do any sort of AI, image processing, or anything else complex like that make heavy use of native code already, for performance reasons.
Keep in mind that the decision to base Android apps around Java was made back in 2003 when Android was founded. Some of the reasons they picked Java were:
- It’s one of the most widely known languages by developers
- It’s hard to write code in languages like C and C++ without introducing memory bugs and security bugs. Using a higher-level language makes those bugs far less common.
- It’s portable - you note that Android only supports arm64 now, but at the time it was arm32, and Android has actually always had some level of support for x86 - you can run the emulator for x86, and some x86 Android devices exist. Using a bytecode language means Android is future-proof
- It’s not limited to just Java - the JVM has a rich ecosystem of languages that developers can use
Now 20 years later I think it’s worked out pretty well. It’s hard to imagine picking a different language would have worked out better. Java is still just as popular as ever, and Android developers can take advantage of all of the Java tools from any other platform or application.
Apple’s original option for iOS apps was just Objective-C, which is higher-performance, but overall it’s a more obscure, difficult to use language. Developers adopted it despite Obj-C, not because of it. Apple had to invent Swift to provide a more modern alternative, because Obj-C is basically not used anywhere else and it felt very ancient. While Swift is a pretty great language, it’s still somewhat obscure, only used for iOS and Mac apps - while Java and JVM languages are used everywhere.
Anyway, let’s say that Android really did want to switch, for some reason. I’m not sure why you think switching to compiled code would be less complex. How would all of the millions of existing Android apps migrate? What native languages would be supported? It’d be a huge transition for dubious benefits.
As it is, Android is extremely flexible. While the official APIs require a JVM language, because of the NDK you can basically write Android apps in whatever language you want. People have built frameworks enabling you to build Android apps in nearly every language under the sun.
Thank you for this great and detailed answer!
I would also add that today JVM environments support more languages such as Scala, Kotlin, and Clojure (to name a few). So more variety and more modern paradigms are available.
As for native languages, we are more or less left with C, C++, Go and Rust. Also some of them are really awesome, none seem like a good choice for general-purpose app development.
And a counter-intuitive thing is that modern run times are so well optimized that sometimes they can outperform native applications (I’m not talking about very tight calculations such image processing and AI), because JIT has much more information about both the specific hardware and run time introspection that is unavailable at compile time.
JVM environments support more languages such as Scala, Kotlin, and Clojure
Are you aware of a currently supported way to run JVM Clojure on Android? The build tools I’m aware of have been abandoned for years and aren’t really usable anymore due to bit rot.
I actually use Clojure quite a bit on the backend/data pipelines, and was actually curious if it’s used in app development.
Your comment probably answers that question :/ Probably too much of a small community and most applications seem to be backend stuff. Maybe it’s possible to build app with CkojureScript as it complise well to JavaScript.
Hello, and thank you for taking the time to compose this response.
I think that I may have conflated the choice of language with the choice of distribution. I believe the choice of language is independent of the choice to distribute apps as native or not, for at least Java because Java has solutions for AOT compilation not the least of which was actually used before in Android 5 according to another response, and it was used prior to Android 7 according to this resource.
For the sake of discussion, I propose that this existing AOT compiler for Android Java applications (used today in the hybrid solution) be run in its entirety on a server instead of on the user devices. I don’t see a motivating reason to have the compiler on every user device to include a complex profile-guided optimization framework and hybrid JIT compiler (described in my third link in the original post) when we could ship the finished code and be done with it.
The benefit would be lower maintenance of the Android platform through a simpler design. (This benefit might shake out, but I get to that later.)
The migration process would consist of doing nothing for the typical app developer making this change quite cheap. The same languages would be supported as they are now. Indeed, this transition has already happened before and shows that this approach works, except with the build process happening on the device in earlier Android versions. I don’t understand why Google did not go a step further and ship the binaries, instead choosing to take a step back and ship a JIT compiler with the AOT compiler. Why ship the intermediate bytecode representation and insist on a complex on-device build and optimization runtime?
From the responses that I have received so far, I think the true answer as to why distribution isn’t native is likely composed of a combination of the following factors:
- Android’s heritage and if it ain’t broke, don’t fix it mindset (very respectable IMHO).
- Android practically supports more platforms than arm64 even if not officially stated, such as Chromebooks and some x86 tablets. Shipping native would make this cross-arch support a lot more complicated.
- Loose coupling between hardware and software platforms as a good design decision.
- JIT performance can actually exceed AOT because more information is available at runtime.
- Backwards compatibility is very important to Android, and the impacts of not shipping bytecode to these old versions could be profound or practically impossible depending on how far back we wish to consider.
I’m sure that I’m making further assumptions, and surely there are oddball apps out there that really depend on having dynamic optimization to be performant, but I suspect these apps are in the minority. At a glance, the current solution seems too complicated, but I think understanding the history of the platform and the selection of devices that are supported today mostly answers my original question. Briefly, arm64 is absolutely not the end of the story even if it’s listed as the supported CPU architecture, and officially committing to just one now and forever could come home to roost.
I think the main issue is who it’d be simpler for. Let’s say that they switched to AOT compiling. That enables them to “simplify” the way Android works internally.
Who does that actually make things simpler for?
Literally ONE subteam of the Android team at Google. Nobody else.
It wouldn’t make things any simpler for developers. In fact, it’d make things worse because AOT compilation is slower and doesn’t allow things like hot-swapping code while your app is running - something you can do now with Java.
It wouldn’t make things any simpler for OEMs. They don’t have to worry about the Java runtime at all, they just worry about drivers.
It wouldn’t make things any simpler for the other 99% of the Android team that builds new APIs, new drivers, etc.
Basically you’re proposing a radical change that would make the platform worse for almost everyone, just so that one pretty small team at Google that builds the Java runtime portion of Android could make it a little simpler???
You say the current system seems “too complicated”. I agree it’s complex, but for a reason. Actually just about everything in tech is complex if you peek behind the curtain and learn how it works inside. The only difference here is that the code is open so anyone can see how it works. But for the most part these are just hidden details.
I guarantee that if you looked into how video frame compositing on Android works, or how low-latency audio works, or any of a hundred other things, you’d realize they’re incredibly complex too - probably “too complicated” at first. But that complexity is for a reason.
QED, I think this response completely addresses my concerns. I often miss the social aspect of systems that involve people. I can’t think of any further questions.
I reverse native binaries across a few different platforms for a living, but I’m just getting into Android. I will definitely take a look at those systems!
deleted by creator
deleted by creator
deleted by creator
deleted by creator
Yeah but at the same time if you support c++ for example its still extremely relevant language(i like c better tho…) and you could build support onto that stable base instead of using the $hitstorm called java. Well thats my opinion anyways. Maybe ease of use is more important and making phones that are powerfull enough to be used as a laptop is the right path forward. But making android more efficient would take a lot of other things and im not nearly qualified enough for this discussion.
Java performance has rarely been an issue in any environment, Android or otherwise. Recall that one of the most successful mobile OSes running on much slower chips than even the first Android was written entirely in Java - BlackBerry OS. C++ is great too but it requires a lot more competent engineers to do well. Modern C++ is spectacular. Yet often people we interview for C++ positions write C with
cout
in place ofprintf
.
I think there are a couple of things to consider. Number one is that Android at the very beginning never was designed for large touchscreen phones, rather it was supposed to be a small portable Software Stack that would run on digital cameras where Java would be good enough.
Another historic thing that must have played into this is that Android was fighting an uphill battle. At the time iOS, PalmOS, BlackberryOS, Symbian and Windows Mobile all were shipping units. So I think they knew they had to keep the barrier of entry for creating Apps as low as possible and Java was (and still is) an incredible easy to understand language with a very gentle learning curve. Plus it was one of the most widley used language with robust tooling.
Also for implementation - Android actually was AOT compiling Apps during installation during the Android 5 and 6 days however starting with 7 they went away from that by using a hybrid approach. Basically if you download an App and launch it the first time, it would run in JIT mode and collect data and than compile and optimize cirtical parts while the phone is idle and charging. There even is an adb command (adb shell cmd package compile -m speed -f my-package) to manually compile apps. But I have played around with this a lot, trust me, and I can’t notice a difference.
Also more generally I think that Android Apps being mostly made up of intermediate bytecode instead of raw CPU instructions is overall a massive benefit for security and incredible futureproofing. It allows for things like x64 Chromebooks and Asus Zenfones with Intel Atom SoCs (yes that was a thing) and would make a transition to a new ISA like RiscV somewhat managable I think. Also I don’t think Java and its performance overhead matters - usually its badly coded Apps or these god awful wrapped webview “apps” that cause issues. Simple as that. If you today compare a Nexus 5 and an iPhone 5S, the Nexus 5 is faster 8/10 times, and that despite having a slower CPU and the whole java stack.
The hybrid compilation approach is spectacular. Really smart design. It’s as close as having the cake and eating it as possible.
Android switch to AOT due to Oracle law suit right?
There are many benefits. For example great exception handling. We’re paying very little performance cost for having the conveniences of a managed runtime. Let me flip it, we don’t want unmanaged runtimes, we are forced to run in native, because we need the absolute highest performance in some cases. If the hardware could understand Java bytecode and provide all other JVM facilities, that would have been spectacular. Unfortunately that would be very expensive and pointless as we’ve demonstrated over the decades. If anything we’re moving towards reduced instruction set chips - ARM, RISC-V. And so in many ways the complexity you see is well understood by the folks that have to understand it, it’s well managed, it’s worth it and it’s not at all a performance problem worth tackling.
If the hardware could understand Java bytecode
This actually existed on ARM to improve performance of java games on pre-smartphone area phones, it was called jazelle.
This name rings a bell. Was that used along with J2ME?
Probably, but I have never used it. I only found out about it when researching about AoT compiling for Java and thought - hugh, neat idea.
Related - something I learned during a research course at uni - the shifting of some functionality from software to hardware or the other way around has happened on many occasions. It all depends on how the tradeoffs between performance, optimization, cost and so on shake out.
X86 android devices exist, wether they are officially supported or not, I don’t know. C/C++ is prone to memory corruption vulnerabilities, using a higher level language like Java nearly entirely cuts out that class of vulnerabilities. You mention the complexity of Java, but Java is just a lot easier to write than c/c++. So I ask you, why would android switch to the complexity of c/c++ for applications where Java is sufficient?
They must be, plenty of x86 Chromebooks get Playstore verified each year.
deleted by creator