APK Golf: Reducing an Android APK's Size by 99.99%
fractalwrench.co.uk
fractalwrench.co.uk
In a similar vein, there's a classic article on creating a tiny ELF executable.[1]
1: http://www.muppetlabs.com/~breadbox/software/tiny/teensy.htm...
$ cat > myapp.aml
<!doctype android>
<head><title>My App</title></head>
<app activity="MainActivity.java"></app>
^D
$ cat > MainActivity.java
import android;
class MainActivity extends Activity {
void main() {
android.toast("hello world");
}
}
^D
$ android compile myapp.aml
Could not find a keystore in your home directory. Would you like me to create one for you (y/n): y
Enter a keystore password: ********
Confirm password: ********
Thanks!
Compiling Java code ... done!
Zipping it up ... done!
Signing ... done!
Byte-aligning ... done!
Wrote myapp.apk. You're ready to upload to the Play Store!
$
But no. We must over-complicate things. AppCompatActivity, manifestations of manifests, layers upon layers of gradle BS, keystores, lots of stackoverflow lookups just to deal with byte aligning, signing, etc. And then Android Studio updates itself, breaking your entire project's 79+ files that for a blank app. What has the world come to?What does this app do on phones? On tablets? On laptops? Where is the header bar (the original has one, yours doesn't). What color is it? What font? What placement? What about users who don't read English? What about users who's languages don't fit in ASCII. What about users who's languages aren't read left-to-right. How should the header bar even be drawn? What about devices that can't draw the header bar as defined? What about devices that were invented before header bars even existed? What about devices whos OEMs fundamentally redefined what 'drawing' even means. MainActivity.java does what? Is it Java 6? 7? 8?. Which of the 26 'Activity' classes are you extending from, and how do you know it exists on the device? How do you know it behaves identically? The .toast method does what? For which phones? On which API release? How long or short should the Toast display for? Your keysigning the app? Which method of keysigning are you using (old or new, they aren't the same). Who's authoritative for the signing (yourself, or Google Play. Those aren't the same either.)
The sample app, with it's 1.5 million wasted bytes that 'do nothing' handles those problems. It's easy to say "I saved 5k bytes by dropping RTL" but... you dropped all RTL language support. People use that stuff. The example steals a pre-made asset from system resources to save space, which is clever until system resources you need aren't there, which happens in the real world. AppCompat is heavy, but it isn't bytes-for-nothing. Some poor souls at Google waste a lot of time making sure developers get a large library of useful layouts and controls that all mostly-just-work mostly-consistently across a huge spectrum of devices and API releases.
No one intentionally sits down and builds over-complicated code by choice (except perhaps Spring). Every layer of the BS here may be annoying and wasteful, but it attempts to solve a real problem a real person had to deal with.
I love the simplicity of your example. But I'm not sure I'd want to deal with the consequences of living in that world.
If all apps have to include 1.5MB of code to handle the same set of problems, then it sounds like the perfect place for that code is in a shared library, perhaps distributed with the OS.
Alternatively, it seems the root of the problem is that all the different versions of Android don't have enough commonality for compatibility. I have apps which were originally written for Win95 and continue to work on Win10. Microsoft hasn't always made great decisions, particularly recently, but seeing a single small binary run unchanged over 20+ years of platform differences is something that IMHO other companies can learn much from.
(I know Android devices, despite mostly being ARM, cover a wider range of architectures... but isn't that what basing everything on Java was supposed to solve?)
The support libraries which the parent referred to are optional utilities. They're often the most convenient way to implement something like a snackbar (https://material.io/guidelines/components/snackbars-toasts.h...), but they're by no means required. Just like on the Windows side, WPF is probably the most convenient way to create a ribbon, but you could build your own with the old win32 APIs if you wanted to, or just not use ribbons.
However, to take the parent's example of an HTML doc with an associated script...the browser does do this, and a rebuttal has to reckon with that. My Chrome web browser runs on thousands of different devices, countless OSs. A small Hello World file can run _with_ a header that supports RTL languages, can very simply say what version of HTML it's using (doctype yadda yadda...I grant that the situation is considerably more complex for JS nowadays!), and can run in any browser size imaginable. It doesn't have a "large library" of useful layouts—the developer just includes what they need. Yes, I'm sure running native on the phone brings many difficulties browser apps don't have to deal with, but from the comment alone I'm not sure what those are beyond signing.
The same thing, by default. Scale pictures and text automatically. <img src="some-image.svg" style="width:0.1cm;height:0.1cm"> for universal compatibility without those godawful hdpi mdpx xxhdpi qxfhdpi directories and whatnot. Use something like CSS3 selectors or code if you want different layout on phones/laptops/tablets. The default should be to just display the same thing. Different layouts usually happens several months into an app's development cycle, and that's only if they actually gain traction. For most apps, targeting only phones is plenty good.
> Where is the header bar (the original has one, yours doesn't).
There isn't one. If you want one, add it:
<header-bar style="background:#ff00000;color:#ffffff">
Hello World
<hamburger id="mHamburger" style="float:right;"/>
</header-bar>
> What color is it?The default, unless the developer styles it otherwise.
> What font?
The default, unless the developer styles it otherwise.
> What placement?
Left, unless the developer styles it otherwise.
> What about users who don't read English?
Don't worry about it and just display the text. When I have enough traction in my initial market, be that English or Japanese or French, I'll consider internationalizing later. Until then, everyone gets the same thing. In a lot of apps, until the content being offered for consumption is internationalized, it makes very little sense to waste time internationalizing the UI. And even if your app doesn't offer any content it probably still makes more sense to build and ship quickly (read: make building easy!) and worry about internationalizing later.
> What about users who's languages don't fit in ASCII.
Just default to UTF-8 already for all source files. End of story.
> Some poor souls at Google waste a lot of time making sure developers get a large library of useful layouts and controls that all mostly-just-work mostly-consistently across a huge spectrum of devices and API releases.
Great! I appreciate all that. But they should live on the system. An full-blown HTML5 app can be a few tens of kilobytes and support RTL languages and everything you mentioned. Chrome takes care of all that consistency stuff. Android should do the same for native. I shouldn't have to go through hell just to draw a button on the screen.
> Where is the header bar
Why should there be one if I haven't defined one? If I define it, then I will tell you where to put it and how to render it. Better yet, if it's a standard, I expect the system to already include the libraries necessary to facilitate its usage.
> What color is it? What font? What placement
If it's implemented in the standard library, these can all have default values (like html pages do). Even if I define these, they should not take more than a few bytes. Not even a kilobyte.
> What about users who don't read English? What about users who's languages don't fit in ASCII.
Why do I need to include extra bytes to tell the system to use UTF-8? It should be the default. At most, deciding which unicode encoding to use should not take more than one byte. I don't think there are more than 4 or so options anyway. UTF-8. UTF-16, UTF-32. Did I miss something?
> What about users who's languages aren't read left-to-right.
What about them? Doesn't the system come with text rendering facilities? Don't they support international languages? What kind of OS is this? Haven't the OS designers heard about HarfBuzz?[0]
> How should the header bar even be drawn?
Did you just repeat a previous question to make the list bigger?
> It's easy to say "I saved 5k bytes by dropping RTL" but... you dropped all RTL language support.
No, because adding or dropping "RTL" does not require any bytes.
The sample app does not even include any i18n so there is no text in any RTL language.
You only need to support RTL if you are writing an OS or a text rendering library. This is not an issue that applications need to deal with.
> Some poor souls at Google waste a lot of time making sure developers get a large library of useful layouts and controls that all mostly-just-work mostly-consistently across a huge spectrum of devices and API releases.
The Android API is notorious for being terrible. I know Google spends a lot on it, but damn IT SUCKS!
Just because someone wastes a lot of time on something does not mean they managed to do it well.
(Not that _I_ would do it any better; So spare me the ad-hominem criticism).
You may rebuke it with "but it's 4KB on top of hundreds of MB or GB of OS and drivers", but so is this 1.5MB do-nothing app.
Also sorry for the shameless plug, but the startup I work for is currently hiring: https://www.bugsnag.com/jobs/
in the section named "Where we’re going, we don’t need IDEs" the sequence of commands creates a file named app.zip at one point:
zip -r app app.zip
but app.zip doesn't seem to be used later. am i missing something or should that be:
zip -r app app-release-unsigned.apk
7za a -tzip -mpass=15 -mfb=257 -ba -bd app.zip app
What's more interesting to me is when the app is extremely small, yet still does something useful.
Actually, this is incredibly hard to do, since it would require creating a near-perfect vacuum.
But I may want my app to be installed to the sd storage.
On a separate note: can we not recalc the apk v1 signatures via a javascript crypto function?
(sorry)
That said, Google could have (and probably should have) done a better job for Play store downloads with common libraries, especially common libraries they provide. They also could and should do a better job on translation strings -- the translation file must be stored uncompressed in the apk, because it wants to be memory mapped.
If you can tell what strings need to be in the old way and what can be done differently, you could do any number of easy things to compress the strings that don't need to be accessible by the system.
WTF? This Hello World app is 1.5 MB, but MS Paint, a program with much richer functionality, is not even a third of that! I know that MS Paint is ancient, but if it takes 1.5 MB by default to put text on a screen, multiple levels of something have failed, especially when it can squeeze down to <100 KB without too much trouble.
There is definitely a bit of bloat when it comes to Android apps that use the support libraries, as the OP article mentions, but the benefit you get for that bloat is worth it if you're writing full featured real-world applications. (The support libs make it easy to support newer Android features backward compatibly to very old handsets).
This isn't hypothetical, this is reality for many of your customers with low end to mid range phones, or even aging flagships. Same goes on the desktop in terms of storage and memory, 64GB total and 4GB of RAM or less are still very common. Developers need to start using low end devices.