APK Files

All Android apps are deployed and contained in so called APK files. APK files are really just ZIP archives that contain a few mandatory folders and metadata files in addition to the application binary and application resource files such as images, XML layout files, arbitrary binary data, etc.

Good And Folly

APK files are a good medium for distributing Android apps as they cram the myriad pieces of an app into one convenient archive which is easy to copy and distribute. The APK folly is that installed apps are never extracted from the archive and placed in a simple folder. Instead they are kept in the archive.

Why

Ok, so you ask why is keeping installed apps in APK files an incredibly foolish design choice? Simply, because it requires all code that accesses a resource file to deal specially with the APK file format.

This may not seem like much of an issue since the SDK provides a couple of mechanisms to use and access resources. Nearly all classes that use resource files accept resource IDs defined in the auto-generated R.java file and the SDK takes care of loading the resources for you. Even better, there is the Resources class for accessing resources directly by obtaining InputStream instances to them and even AssetFileDescriptors for them.

These mechanism don’t really solve the problem. In the first instance, the SDK is simply hiding the underlying problem by accepting the resource IDs and accessing the APK file behind the scenes. Obviously, the SDK still has special code to extract resources from the APK file. Special code that exists only for specific file types (images, XML layouts, etc.) explicitly handled by the SDK in this way.

The second instance is not really a solution, it is a manifestation of the problem. The resource files are buried in the APK file and we need the Resources class to access them. All the standard methods of opening a file, in Java or native C, are useless. You can’t use new FileInputStream() in Java or open() a file in C. We need special code that is aware of the APK format and uses the designated APIs to extract files from it.

At least in theory. In practice the Resources class does not provide a real solution for accessing APK resource files from C. There is an extremely convoluted method of obtaining the APK’s file descriptor in Java from an undocumented member of an AssetFileDescriptor instance, obtaining the offset and length of the resource in the APK file and passing the triplet through JNI. You can read about it here. The alternative is to compile your native code with libzip support and to process the APK file yourself, assuming you somehow pump the file path from Java over JNI. This massive edifice in lieu of using open().

The Folly

The clunkiness of the API and the non-existent native support are not the big APK folly. The unmitigated disaster of this arrangement is that “legacy” code that expects to be able to open a file given a path – a huge number of non-trivial codebases – needs to be modified to work on Android. Legacy code could be anything from your previous projects to essential third party libraries – big or complex codebases you don’t want to modify for a variety of reasons. For example, something like SQLite.

There are two methods to get around the APK folly. The first is by modifying the file handling code in the codebase you don’t want to modify. As an example, imagine having to modify SQLite.

The second method is to copy the resource files out of your APK to somewhere else in the filesystem. Aside from the wasted storage and the tediousness of copying files as a workaround, the file copy introduces two problems, both of which are proportional to the size of the resources in question. The first is a degradation in user experience where the user has to wait for the app to complete the copy.

The second is the introduction of a whole new set of failure modes (where non existed) in your app’s initialization stage – a usually sensitive stage where errors are tricky to deal with. Given that you are copying essential resources these files could be critical to the proper execution of the app. What do you do when a file copy fails?

The disk could be full or a previous copy could have been interrupted and only some of the files copied or perhaps even only part of a filecopied. All these failures can and will eventually happen when an app is widely deployed and they need to be handled gracefully.

Even if your app is insensitive to these failure, notifies the user when they occur, provides instructions on resolution and recovers correctly when it can. Many users will be faced with seemingly arbitrary and mysterious failures of the app when they could have been spared the ordeal to begin with.

SQLite

SQLite is interesting because it is part of the Android SDK in the form of the SQLiteDatabase class. Despite the obvious utility (and many mailing list questions about it), the ability to open a resource file is noticeably absent from the class. This is not a coincidence. The ongoing engineering effort required to understand and modify the SQLite codebase to support accessing a database in a ZIP file even if only for R/O access is huge and impractical. If apps were installed in a folder instead of an APK file, adding this functionality to SQLiteDatabase would have been trivial, an afterthought that would have never elicited any special attention.

Because of the impracticality of modifying SQLIite to handle files in an APK file, the accepted gospel is to copy bundled database resource files at app initialization time.

Unreasonable

The cost of the APK folly is clear. The question is then, is there a benefit, a tradeoff, that makes the considerable costs worthwhile. Why is this being done?

One benefit could be that the ZIP compression saves storage space. However, for most apps the benefit is minimal. Most large SDK handled resources, like images, tend to be compressed anyway. Most other large resources are going to be your own resources unsupported by the SDK. Resources requiring a file copy to be used by your code, thus costing more storage space.

Beyond this one tiny excuse of a benefit, I can’t think of anything.

Reinventing The Square Wheel

In essence, the designers of Android felt it necessary to discard the already present, proven and universally used filesystem available in the underlying Linux in favor of implementing a clunky pseudo-filesystem that is incompatible with everything else in existence.