Hacker News new | comments | show | ask | jobs | submit login
APK Golf: Reducing an Android APK's Size by 99.99% (fractalwrench.co.uk)
279 points by jbergstroem 43 days ago | hide | past | web | 70 comments | favorite



This is code golf. I mention that because apparently some people are missing the point, which is purely to get the smallest size. This isn't directly applicable to most work you might do, but like many exercises that explore the extremes, useful knowledge can be learned from the attempt.

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



Also, The Infinitely Profitable Program: https://web.archive.org/web/20160304014157/http://peetm.com/...


Jeez, that article ends like The Lord of the Rings movies...


1.5 MB for an app that does nothing. 1.5 million bytes for nothing. How inefficient we are these days. I really wish it were as simple as this:

    $ 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?


I totally agree with you. But as an Android developer, I know why we won't get to this...

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.


One of my favorite things is to see a problem I think is trivial and learn its hidden complexities, like the "X things developers think about Y" series of articles.

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 sample app, with it's 1.5 million wasted bytes that 'do nothing' handles those problems.

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.


A large amount of that 1.5 MB of code is to shim around the incompatibilities between different versions of the shared libraries that ship with Android. The root of the problem is that not everyone is on the same version of Android.


The root of the problem is that not everyone is on the same version of Android.

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?)


I don't really see a difference. Android's API changes are almost always backwards compatible. There are a few exceptions, like AudioManager.setRouting (now a no-op), but they're very rare. For the most part, apps built for for early versions of Android still work fine on modern devices.

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.


Not everyone is on the same version of Chrome either. It works out just fine. If you want a feature that the user's version of Chrome doesn't have, you detect that and inform the user.


That shim is still same in all apps bundling the same version, though. Thus it is possible to distribute it out of band as a shared component just like system webview.


These things don't justify wasting over a million bytes. Many of them are not even real issues.

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

[0] https://freedesktop.org/wiki/Software/HarfBuzz/


> What does this app do on phones? On tablets? On laptops?

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.


For some amazing contrast, here's a 4 kilobyte(!) binary which renders a 215-second-long realtime 3D scene, complete with music: https://news.ycombinator.com/item?id=11848097

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.


Doesn't work on my windows 10 machine.


Author here - happy to answer any questions anybody might have.

Also sorry for the shameless plug, but the startup I work for is currently hiring: https://www.bugsnag.com/jobs/


It is possible to get zip archive with slightly better compression rate using 7zip instead zip command:

7za a -tzip -mpass=15 -mfb=257 -ba -bd app.zip app


Are you guys based out of Bath, UK? Visited it this past summer and loved it! :)


Our main HQ is in San Fransisco, but we also have an office in Bath as both the founders went to university there (as did I). It's a really great city to see on a good Summer's day - the Roman Baths are well worth a visit.


Where is the screenshot of the app at the end?


By the end, the app no longer has any Activities, so there's nothing to be shown on the screen. The exercise is simply to create the smallest possible (valid, installable) APK.


I wish the requirement would have included a UI. I feel like that could have taken them in more interesting directions.


thank you for an interesting/educational article!

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


Yep, that's a typo, thanks for letting me know.


I got slightly nerd-sniped on this and started optimizing (along with a few others)...we’re under 1K now (922 bytes from 1757 bytes) and still going. https://github.com/fractalwrench/ApkGolf/pull/4


Kinda cool.. though it also seems pointless or hollow of meaning? "Let's make a package as small as possible while removing everything. Our next feat will be the world's most empty room."

What's more interesting to me is when the app is extremely small, yet still does something useful.


What made me post the link was that it – disregarding its debatable pointlessness – showed a diverse set of methods and tools that are useful in android packaging.


> Our next feat will be the world's most empty room

Actually, this is incredibly hard to do, since it would require creating a near-perfect vacuum.


My room is doughnut shaped and resides in the relative space between a hydrogen atoms proton and electron ... do I win?


I measured the size of your room and it seems to have collapsed.


You do, but a little formality first: can you please prove, definitely, the size of the proton? There are conflicting opinions floating around...


pix or it didn't happen


Beware of all the photons passing by.


The 'Golf' part in 'APK Golf' or more broadly 'Code Golf' indicates it's an exercise in trying to see how small you can get the code.


It's golf.



I'd like to see how this is done on iOS, since the OS is much pickier about what it will let you install.


So... it's a signed binary manifest file? That's hilarious and fascinating


Do 50 push-ups while gradle syncs. touche


Beautiful. Someone should do the same for all the Javascript-framework-driven web apps clogging the web.


Given that the app only displays "Hello world" I'm sure an equivalent Javascript library wouldn't be too difficult.


<script></script>hello world


android:allowBackup="true"

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?


What, is it too big for your internal storage? ;)


If you depend on shared library or external state, then in the zen sense, it cannot be "whole" in "one"

(sorry)


removing support library, layouts...please.


Back when they introduced them, I was pretty strongly opposed, and you can see the effect. We now have half the Android UI shoved in each app package. Back when I started using Android, apps were rarely over 1 MB in size. Now, with no real improvement in functionality, apps are regularly over 50 MB.


These packages certainly add bloat, and make it hard to fit under 1MB (can't really do it if a hello world is 1.5MB), but developers are adding a lot of their own bloat to get to 50 MB.

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.


Last year I was invited to interview at Google Aarhus for a job on exactly this topic. We discussed fun, very simple ways to avoid including loads unused library code, and I was told that they already had some ridiculously efficient, practical way of compressing translation strings. I was there last November, and I had the impression that a lot of low hanging fruit would be picked off very quickly. Not sure what parts of this work actually shipped in these last 11 months. But work may still be ongoing.


At this point, I don't think there is anything simple you can do for translations in Android. Some of the translations are loaded outside of app context, and app developers may not know which ones those are. Allowing the files to be compressed in the apk would require support in the Android system, and there are a billion devices already shipped that don't know how to do that.

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.


Unfortunately, that seems to be the only way to get a reasonable baseline, given that Google has no way to actually update Android devices.


No, the libs could be shared between apps, with at most one per API level, and shipped through the Play Store. If you want your app to work without Play Store, bundle the libs like today.


So, basically the Visual C++ Redistributable[1] but for Android? Seems to work well for Windows, from what I can tell.

1: https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B


They already have one distributed through the Play Store. That's the proprietary bits. The Support libs are open source, they can work without the Play Store, and different apps can have different versions of it.


Yes, and they can ship a shared version of the open source libs (like the system webview). And as I specified, with one per API level (different versions). Maybe that would force them to have better QA of those libs as well.


Either I'm whooshing some sarcasm, or you're missing the point.


Indeed. I thought the fact that he removed the Launcher Intent would be of more concern.


yeah, i had the same thought. this post cannot be taken as practical advice. interesting to see just how much bloat an apk contains though.


What made you think this article was meant to be taken as practical advice? It's code golf, which has never been about practical advice.


Is it bloat if it is required to make good app?


Bloat would imply that stuff does not serve a purpose.


> Beautiful. Our APK weighs in at approximately 1.5Mb.

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.


I don't necessarily disagree, but MS Paint on my Windows 10 machine weighs in at 6.5Mb. Notepad++ is just 2.5Mb though. And the Windows Media Player is just 163k.


Aren't there also dll dependencies that allow modern Windows apps to be so small?


Yes. The Windows Media Player referenced is a perfect example, since it is basically just a shell app where all the UI functionality is pulled in from plugins and all of the codec support is handled through external codec libraries.

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


Often yes. Sometimes not. I don't know about the specific ones I mentioned. You can definitely build a self-contained WinForms app in under a meg, though.


Thing is, space and bandwidth are way cheaper now compared to the age of MS Paint. 1Mb, 10Mb, even 40 or 50 don't do much difference to most users but they can make things easier to develop/maintain/whatever. Which in the end could save a lot of money to a company.


Sure 50MB is nothing, until everyone thinks like that and now I'm out of space on my phone. I'd love to install your app, but first I'd have to remove another app by another developer that optimized for their development speed instead of my resources. Doesn't matter how much money the company saved on development if users can't install it.

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.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: