Android devices have become big, but Android is still for small, limited devicesSome Android devices have become capacious and powerful. The processor benchmarks for flagship handsets rival processors commonly found in PCs. The RAM capacity in most phones is still less than half what you get in a typical PC, but that's still quite a lot.
You have cross-trained some of your best Java experts on Android. They know how to engineer big Java projects. But you find unexpected problems. Why don't the same engineering approaches that work for your Web projects work for Android?
The reason is that Android is, still, a clever little OS for clever little devices. Android may run on flagship phones, but it will also, still, run on lo-spec devices with remarkably good performance. It was designed for devices with modest resources. More specifically, and although the minimum specs for Android have gone up-market since then, it was originally designed to enable multiple Java runtime instances run on the same kind of hardware Blackberry was running on 10 years ago. Many of those design elements still permeate Android's architecture and color the engineering approach needed for Android.
Many VM instances in a small systemAndroid enables a multi-process, multiple runtime-instance userland and middleware layer to run on top of a Linux kernel. If you have ever started multiple Java VM instances on a PC and seen the resulting performance hit, this sounds like the opposite of a small Java OS for small systems, but it turns out to be the central paradox of Android. Android enables these characteristics by pre-loading classes in a "zygote" process, efficiently sharing memory with copy-on-write, limiting per-process heap size, dividing applications into small components with potentially short lives, and requiring all components to handle life-cycle events. There are cases where whole processes are "reaped" to free and compact memory. Even though Android's runtime environment implements Java language semantics, it is very unlike the environment your Web server code runs in.
Android is ill-suited to run the product of large software projects. Big libraries. Phalanxes of coders measuring their productivity in klocs. Layers of code. Abstractions. Utilities to help keep junior coders out of trouble. All these activities produce bloat, encourage monolithic applications, and hide problems. Especially so on Android.
Bigger heaps are not the answerAndroid has an aggressive memory recovery strategy built deep inside the Android architecture. Is Android's architecture obsolete in an age of multi-gigabyte, multi-gigahertz flagship phones? The answer is No for at least two reasons:
- Android has a very wide range of hardware targets. For every flagship phone and tablet, dozens of lo-end phones will be sold. Apps will go into embedded devices in cars, home appliances, industrial computing devices, and non-phone consumer electronics devices.
- Java style garbage collection works only within one process. Android's memory recovery strategy implies that the larger the maximum heap size, relative to total RAM, the more often processes get all their components destroyed (and subsequently reconstituted in a new process) in order to recover memory system-wide. There is a sweet spot for maximum heap size and it's a relatively small fraction of total RAM.
Were Android's architects just messing with you?Multiple (dozens!) of processes, each an instance of a Java runtime, each with a limited heap size... is this an environment created specifically to be mind-bending to the typical server-Java engineer? You may very well think so. However, where Android imposes constraints in some dimensions, it offers opportunities, and novel features in other ways: Android has an unusually rich architecture for inter-process communication and remote procedure calls.
Write less code, in smaller modules, and embrace the Android archiectureThe way to avoid unpleasant discoveries, such as Android's per-APK method limit, heap size limits, etc. divide your software into a suite of separate but communicating modules, and divide those modules among multiple APK files. Moreover this is an opportunity to structure your projects along these lines: Smaller projects, smaller teams, and abstractions that follow the contours of the Android system. Avoid libraries that are redundant functionality provided by the Android OS. If the choice is portability-with-bloat vs more porting effort, the porting effort will be the cheapest investment you make to fight bloat.
If you have data that multiple modules might need, use ContentProvider components to share it. If you are rendering large images, say for an e-reader, do that in a process and heap dedicated to that part of your application. Break down your monoliths, and don't try to subvert Android's limitations.