Saturday, December 20, 2014

Telirati Analysis #15 Write Less Android Software


You have a substantial budget. You have an army, perhaps a foreign mercenary army, of developers. You've got experience delivering big Web projects this way. But your Android project might as well be Afghanistan. Unexpected limitations, difficult bugs, poor performance, and bloat plague you for weeks and months.

Android devices have become big, but Android is still for small, limited devices

Some 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 system

Android 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 answer

Android 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:
  1. 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.
  2. 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.
Moreover, Android apps have to work across the whole range of heap sizes in use for the Android devices targeted. Limiting your app to high-end devices just to accommodate your engineering approach isn't a solution.

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 archiecture

The 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.

Facebook's approach to unbundling functionality makes Facebook's products more visible on mobile devices. Keeping your apps simple, relatively small, and having multiple cooperating apps isn't just smart engineering, it's better for the user and better for you commercially.