In golf, he wins the one who has less points.
Apply this principle in Android. We're going to play APK golf and create an application of the smallest possible size that can be installed on Android 8.0 Oreo.
A basic level of
Let's start with the default application that Android Studio generates.
Create a keystore , sign the application, and measure the file size in bytes with the
stat -f%z $filename
command
stat -f%z $filename
.
')
Then install the APK on your Nexus 5x smartphone under Oreo to make sure everything works.
Perfectly. Our apk weighs about one and a half megabyte.
APK Analyzer
One and a half megabyte seems too large in size to take into account what our application does (and it does not do anything), so let's examine the project and look for where to save money on volume. This is what Android Studio generated:
MainActivity
, which extends AppCompatActivity
.- Layout file with
ConstraintLayout
for the main window. - Resource files with three colors, one string resource, and a theme.
- Support libraries
AppCompat
and ConstraintLayout
. - One
AndroidManifest.xml
. - PNG files for square, round and background icons.
Perhaps the easiest way to deal with the icons, given that there are a total of 15 images and two XML files under
mipmap-anydpi-v26
. Let's count all this in
APK Analyzer from Android Studio.

Contrary to our initial assumptions, it seems that the largest file is Dex, and resources account for only 20% of the size of the APK.
File | The size |
---|
classes.dex | 74% |
res | 20% |
resources.arsc | four% |
META-INF | 2% |
AndroidManifest.xml | <1% |
Examine separately what each file does.
Dex file
classes.dex
is the main culprit of the bloated APK, it occupies 73% of the total volume and therefore will be the first optimization goal. This file contains all our compiled code in Dex format, as well as a list of external methods in the Android framework and the support library.
The
android.support
package lists over 13,000 methods, which seems unnecessary for an application like "Hello World".
Resources
In the res directory there is a large number of template files, drawables and animations that are not immediately visible in the Android Studio interface. Again, they are pulled from the library of support and occupy about 20% of the size of the APK.

The
resources.arsc
file also contains a list of all these resources.
Signature
The
META-INF
folder contains the files
CERT.SF
,
MANIFEST.MF
and
CERT.RSA
, which are needed for
signing v1 APK . If an attacker changes the code inside the APK, then the signatures will not match, which protects the user from launching an outsider malware.
MANIFEST.MF
lists files from the APK, and
CERT.SF
contains the checksums of the manifest and each individual file.
CERT.RSA
stores the public key that verifies the integrity of
CERT.SF

There are no obvious targets for optimization.
AndroidManifest
AndroidManifest is very similar to our original file. The only difference is that instead of resources like strings and drawables, their integer identifiers are shown here, starting at
0x7F
.
Turn on minification
We have not tried to enable the option of minification and compression of resources in the file
build.gradle
for our application. Let's do it.
android { buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile( 'proguard-android.txt'), 'proguard-rules.pro' } } }
-keep class com.fractalwrench.** { *; }
If you set
minifyEnabled
to
true
, then
Proguard is activated, which clears the application of unnecessary code. It also obfusts the names of characters, making it difficult to reverse engineer an application.
shrinkResources
will remove any resources from the APK that are not directly referenced. There may be problems if you do not directly access resources, but this does not apply to our application.
786 KB (decrease by 50%)
We have reduced the size of the APK by half without any visible changes in the program.
If you have not yet included
minifyEnabled
and
shrinkResources
in your application, this is the main thing that should be learned from this article. You can easily save a few megabytes by spending just a couple of hours on configuration and testing.
Goodbye, AppCompat, we barely recognized you
classes.dex
now occupies 57% of the total APK. The main part of the list of methods from the Dex file belongs to the
android.support
package, so we're going to remove the support library. To do this, do the following:
- Completely remove the dependency block from
build.gradle
.
dependencies { implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' }
- Update MainActivity to extend the
android.app.Activity
class.
public class MainActivity extends Activity
- Update our template to use a single
TextView
.
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="Hello World!" />
- Remove
styles.xml
and the android:theme
attribute from the <application>
element in AndroidManifest
. - Remove
colors.xml
. - Do 50 pushups while Gradle syncs.
108 KB (87% reduction)
Mother of God, the file has decreased almost tenfold: from 786 KB to 108 KB. The only noticeable change was only a change in the color of the toolbar, which was painted in the default OS theme.
The res directory now accounts for 95% of the size of the APK because of all these launcher icons. If these icons were made by our designer, we would try to
convert them to WebP , a more efficient format that is supported in API 15 and later versions.
Fortunately, Google has already optimized our drawables, although otherwise we would have been able to optimize them ourselves and remove unnecessary metadata from PNG using
ImageOptim .
Let's do it unconventionally - and replace all our launch icons with a single, single-pixel black dot in the
res/drawable
. This picture weighs 67 bytes.
6808 bytes (a decrease of 94%)
We got rid of almost all resources, so it’s not surprising that the size of the APK decreased by about 95%. The following resources are still mentioned in the resources.arsc file:
- 1 template file
- 1 string resource
- 1 launcher icon
Let's go from the top down.
Template file (6262 bytes, 9% reduction)
The Android framework
inflates our XML file and automatically creates a
TextView
object that is used as the
contentView
for the
Activity
.
Let's try to do without this broker by deleting the XML file and programmatically setting the contentView. The resource size will decrease because the XML file disappears, but the size of the Dex file will increase as we mention additional
TextView
methods there.
TextView textView = new TextView(this); textView.setText("Hello World!"); setContentView(textView);
Looks like a nice exchange.
Application name (6034 bytes, 4% reduction)
Let's remove the
strings.xml
and replace the
android:label
in the AndroidManifest manifest with the letter "A". This seems like a small change, but deleting a record from
resources.arsc
reduces the number of characters in the manifest and deletes the file from the res directory. Every little thing is good - we just saved 228 bytes.
Launcher Icon (5300 bytes, down 13%)
The documentation for resources.arsc in the Android Platform repository explains that each APK resource is mentioned in
resources.arsc
with an integer identifier. These IDs have two namespaces:
0x01: system resources (pre-installed in framework-res.apk)
0x7f: application resources (in the application .apk file)
So what will happen to our APK if we put a link to a resource in the namespace 0x01? In theory, we will get a more beautiful icon and at the same time reduce the size of your file.
android:icon="@android:drawable/btn_star"

Of course, you
should never trust system resources like icons in a real working application. This method will fail validation on Google Play, and some manufacturers also
define white color in their own way , so proceed carefully.
Manifest (5252 bytes, down 1%)
We have not touched the manifesto.
android:allowBackup="true" android:supportsRtl="true"
Deleting these attributes saves 48 bytes.
Proguard Hack (4984 bytes, down 5%)
It seems that the
BuildConfig
and
R
classes still remain in the Dex file.
-keep class com.fractalwrench.MainActivity { *; }
Refining a Proguard rule will remove unnecessary classes.
Obfuscation (4936 bytes, reduction by 1%)
Obfuscate the name for the class Activity. For normal classes, Proguard automatically does this, but since the class name Activity is called through Intents, it is not obfuscated by default.
MainActivity -> c.java
com.fractalwrench.apkgolf -> cc
META-INF (3307 bytes, down 33%)
At the moment we are signing the application simultaneously with the signatures of v1 and v2. This seems like a waste of resources, because v2 provides
excellent protection and performance by hashing the entire APK.
The signature of v2 is not visible from APK Analyzer, since it is included in the binary block in the APK file itself. The signature v1 is visible, in the form of files
CERT.RSA
and
CERT.SF
Let's remove the check mark for the signature v1 in the Android Studio interface and generate the signed APK. Let's try to do it and vice versa.
Signature | The size |
---|
v1 | 3511 |
v2 | 3307 |
Looks like now we'll use v2.
Where are we going - there are no IDE needed
It's time to edit the apk manually. Use the following commands:
# 1. apk ./gradlew assembleRelease # 2. unzip app-release-unsigned.apk -d app # # 3. zip -r app app.zip # 4. zipalign zipalign -v -p 4 app-release-unsigned.apk app-release-aligned.apk # 5. apksigner v2 apksigner sign --v1-signing-enabled false --ks $HOME/fake.jks --out signed-release.apk app-release-unsigned.apk # 6. apksigner verify signed-release.apk
A detailed overview of the process of signing the APK, see
here . In general, Gradle generates an unsigned archive, zipalign does byte-byte alignment for uncompressed resources to optimize RAM consumption after downloading an APK, and at the end the cryptographic procedure for signing an APK is started.
Unsigned and unallocated APK weighs 1902 bytes, that is, the procedure adds about 1 kilobyte.
File size mismatch (2608 bytes, compression by 21%)
Strange! If you unzip an un-aligned APK and manually sign it, then the
META-INF/MANIFEST.MF
file disappears, which saves 543 bytes. If someone knows why this is happening, then let me know!
Now we have three files in the signed APK. But we can still get rid of the
resources.arsc
file, because we do not install any resources!
After that, only the manifest and the
classes.dex
file
classes.dex
, both of about the same size.
Khaki with compression (2599 bytes, a decrease of 0.5%)
Now we will change all the remaining lines to 'c', updating versions to 26, and then generate the signed APK.
compileSdkVersion 26 buildToolsVersion "26.0.1" defaultConfig { applicationId "cc" minSdkVersion 26 targetSdkVersion 26 versionCode 26 versionName "26" }
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cc"> <application android:icon="@android:drawable/btn_star" android:label="c" > <activity android:name="ccc">
This reduces the size by another 9 bytes.
Although the number of characters in the file has not changed, the fact is that the frequency of the 'c' symbol has increased. As a result, the compression algorithm worked more efficiently.
Hi, ADB (2462 bytes, down 5%)
You can further optimize the manifest by removing the Launch intent filter for the Activity class. From now on, we will launch the application with the following command:
adb shell am start -a android.intent.action.MAIN -n cc/.c
Here is the new manifesto:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cc"> <application> <activity android:name="c" android:exported="true" /> </application> </manifest>
We also got rid of the launcher icon.
Clearing method references (2179 bytes, down 12%)
By initial conditions, we must prepare an APK, which is able to install on the device.
Our application lists methods in the
TextView
,
Bundle
and
Activity
classes. You can reduce the size of the Dex file by removing these links and replacing them with the new
Application
class. Thus, the Dex file will now refer to a single method — the constructor of the
Application
class.
Source files now look like this:
package cc; import android.app.Application; public class c extends Application {}
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cc"> <application android:name=".c" /> </manifest>
We use adb to check that the APK was successfully installed, it can also be checked through the “Settings”.
Dex Optimization (1961 bytes, down 10%)
I spent several hours studying the format of the Dex file for the sake of this optimization, since different mechanisms like checksums and offsets make manual editing difficult.
In short, it turned out that the only requirement for installing an APK is the existence of the
classes.dex
file. Therefore, we simply delete the original file, run
touch classes.dex
in the console and save 10% of the size using an empty file.
Sometimes the silliest solution is the best.
Understanding the manifest (1961 bytes, 0% reduction)
The unsigned APK manifest is a binary XML file that does not seem to be officially documented. You can change the contents of the file using the
HexFiend editor.
Some interesting elements are guessed in the file header - the first four bytes encode
38
, which coincides with the version number of the Dex file. The next two bytes encode
660
, which is the same as the file size.
Let's try to delete one byte by setting targetSdkVersion to
1
, and changing the file size in the header to
659
. Unfortunately, the Android system rejects the new file as an incorrect APK. It seems that everything is more complicated here ...
Misunderstanding of the manifesto (1777 bytes, reduction by 9%)
And we will try to scribble random characters all over the file, and then install the APK without changing the specified file size. This way we will check if the checksum is checked and how our changes will affect the offset in the file header.
Surprisingly, such a manifesto is perceived as a valid APK on Nexus 5X under Oreo:

I think I’ve just heard how the Android framework developer responsible for supporting
BinaryXMLParser.java
screamed very loudly into the pillow.
For maximum benefit, you need to replace all these stupid characters with zero bytes. This will help recognize the important parts of the file in HexFiend, as well as reduce several bytes thanks to the compression hack mentioned above.
UTF-8 Manifesto
Here are the important components of Manifest, without which the APK will not be installed.
Some things are obvious, such as manifest and package tags. In the string pool, the versionCode and the package name are visible.
Hex manifest
Viewing the file in hexadecimal format shows the values ​​in the file header, which describe a pool of strings and other values, such as file size
0x9402
. Strings are also interestingly encoded - if they are larger than 8 bytes, then the total length is indicated in the two previous bytes.
But you can hardly find other options for optimization here.
Is it done? (1757 bytes, reduction of 1%)
Examine the final APK.
Throughout this name in the APK, my name was indicated in the signature of v2. Create a new keystore, which uses a hack for compression.
We saved 20 bytes.
Step 5: Recognition
1757
bytes - this is very little, damn it. And as far as I know, this is the smallest existing APK.
However, I reasonably believe that someone from the Android community is able to perform further optimizations and further improve the result. If you manage to reduce the file from the current
1757
bytes,
send a pull-request to the repository , where the smallest APK is hosted, or
tell on twitter .
(Since the publication of the article, the file has already been reduced to 820 bytes - approx. Lane.)