App Size Optimization#
Uploading Apps Using Android App Bundle#
The simplest way to immediately reduce app size when publishing to Google Play is to release the app as an Android App Bundle, a new upload format that contains all the compiled code and resources of the app, while APK generation and signing are deferred to the Google Play Store.
Google Play's new app delivery model is called "Dynamic Delivery," which uses your App Bundle to generate and deliver optimized APKs tailored to each user's device configuration, so users only download the code and resources needed to run your app. You no longer need to compile, sign, and manage multiple APKs to support different devices, and users can receive smaller, optimized download packages.
Please note that Google Play enforces a compressed download size limit of no more than 150 MB for apps published using App Bundles, so we still recommend following the guidelines presented on this page to minimize the download size of your app as much as possible.
For apps published to Google Play via uploaded signed APKs, the compressed download size limit is no more than 100 MB.
Understanding APK Structure#
Before discussing how to reduce app size, it is helpful to understand the structure of an APK. An APK file consists of a Zip archive containing all the files that make up the app. These files include Java class files, resource files, and files containing compiled resources.
An APK contains the following directories:
-
META-INF/
: Contains theCERT.SF
andCERT.RSA
signature files, as well as theMANIFEST.MF
manifest file. -
assets/
: Contains the app's resources; the app can retrieve these resources using the AssetManager object. -
res/
: Contains resources that are not compiled intoresources.arsc
. -
lib/
: Contains compiled code specific to the processor software layer. This directory contains subdirectories for each platform type, such asarmeabi
,armeabi-v7a
,arm64-v8a
,x86
,x86_64
, andmips
.
The APK also contains the following files. Among these files, only AndroidManifest.xml
is required.
-
resources.arsc
: Contains compiled resources. This file includes the XML content from all configurations in theres/values/
folder. The packaging tool extracts this XML content, compiles it into binary form, and archives the corresponding content. This content includes language strings and styles, as well as paths to content not directly included in theresources.arsc
file (such as layout files and images). -
classes.dex
: Contains classes compiled in the DEX file format understandable by the Dalvik/ART virtual machine. -
AndroidManifest.xml
: Contains the core Android manifest file. This file lists the app's name, version, permissions, and referenced library files. The file uses Android's binary XML format.
Reducing Resource Count and Size#
The size of the APK affects the app's loading speed, memory usage, and power consumption. One simple way to reduce APK size is to decrease the number and size of the resources it contains. Specifically, you can remove resources that the app no longer uses and replace image files with scalable Drawable objects. This section will discuss these methods and several other ways to reduce resources in the app to decrease the total APK size.
Removing Unused Resources#
The lint tool is a static code analyzer included with Android Studio that can detect resources in the res/
folder that are not referenced by code. When the lint
tool finds potentially unused resources in the project, it displays a message, as shown in the following example.
res/layout/preferences.xml: Warning: The resource R.layout.preferences appears to be unused [UnusedResources]
Note: The lint
tool does not scan the assets/
folder, resources referenced via reflection, or library files linked to the app. Additionally, it does not remove resources; it only alerts you to their presence.
The libraries you add to your code may contain unused resources. If you enable shrinkResources in your app's build.gradle
file, Gradle can automatically remove resources on your behalf.
android { // Other settings buildTypes
{
release
{
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
To use shrinkResources
, you must enable code shrinking. During the build process, ProGuard first removes unused code but retains unused resources. Then, Gradle removes unused resources.
For more information on ProGuard and other ways Android Studio helps you reduce APK size, see Shrinking Code and Resources.
In Android Gradle Plugin version 0.7 and higher, you can declare the configurations supported by your app. Gradle passes this information to the build system using the resConfig
and resConfigs
styles and the defaultConfig
option. The build system then prevents resources from unsupported configurations from appearing in the APK, thereby reducing the APK size. For details on this feature, see Removing Unused Alternate Resources.
Minimizing Resource Usage in Libraries#
When developing Android apps, you often need to use external libraries to enhance the app's usability and functionality. For example, you might reference the Android Support Library to improve the user experience on older devices, or use Google Play Services to retrieve automatic translations of text within the app.
If a library is designed for server or desktop devices, it may contain many objects and methods that the app does not need. To include only the parts of the library that your app requires, you can edit the library files (if the corresponding license allows you to modify the library). You can also use other libraries that are more suitable for mobile devices to add specific features to your app.
Note: ProGuard can clean up some unnecessary code imported with libraries, but it cannot remove large internal dependencies of the library.
Supporting Only Specific Densities#
Android supports a wide range of devices (with various screen densities). In Android 4.4 (API level 19) and higher, the framework supports various densities: ldpi
, mdpi
, tvdpi
, hdpi
, xhdpi
, xxhdpi
, and xxxhdpi
. Although Android supports all these densities, you do not need to export rasterized resources for every density.
If you know that only a small portion of users have devices with a specific density, consider whether you need to bundle those densities into your app. If you do not add resources for specific screen densities, Android will automatically scale existing resources originally designed for other screen densities.
If your app only needs scaled images, you can save more space by using a single variant of the image in drawable-nodpi/
. We recommend that each app include at least one xxhdpi
image variant.
For details on screen density, see Screen Sizes and Densities.
Using Drawable Objects#
Some images do not require static image resources; the framework can dynamically draw images at runtime instead. Drawable objects (represented as <shape>
in XML) occupy little space in the APK. Additionally, XML Drawable objects generate monochrome images that comply with Material Design guidelines.
Reusing Resources#
You can add separate resources for variants of an image, such as versions of the same image with different hues, shadow settings, or rotations. However, we recommend reusing the same set of resources and customizing them at runtime as needed.
Android provides several utilities to change the color of resources, each utilizing the android:tint
and tintMode
attributes on Android 5.0 (API level 21) and higher. For lower platform versions, the ColorFilter class is used.
You can also omit resources that are merely another resource's rotated equivalent. The following code snippet provides an example of rotating an image 180 degrees to turn "thumbs up" into "thumbs down":
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_thumb_up"
android:pivotX="50%"
android:pivotY="50%"
android:fromDegrees="180" />
Rendering from Code#
You can also reduce APK size by rendering images programmatically. Programmatic rendering can free up space since you no longer store image files in the APK.
Compressing PNG Files#
The aapt
tool can optimize images placed in res/drawable/
through lossless compression during the build process. For example, the aapt
tool can convert true color PNGs that do not require more than 256 colors into 8-bit PNGs using a palette. This results in images of the same quality but with a smaller memory footprint.
Keep in mind that aapt
has the following limitations:
- The
aapt
tool does not compress PNG files contained in theassets/
folder. - Image files need to use 256 colors or fewer for the
aapt
tool to optimize them. - The
aapt
tool may increase the size of already compressed PNG files. To prevent this, you can disable this process for PNG files in Gradle using thecruncherEnabled
flag:aaptOptions { cruncherEnabled = false }
Compressing PNG and JPEG Files#
You can use tools like pngcrush, pngquant, or zopflipng to reduce the size of PNG files without losing image quality. All these tools can decrease the size of PNG files while maintaining visually perceived quality.
The pngcrush
tool is particularly effective: it iterates through PNG filters and zlib (Deflate) parameters, using each combination of filters and parameters to compress the image. It then selects the configuration that produces the smallest compressed output.
To compress JPEG files, you can use tools like packJPG and guetzli.
Using WebP File Format#
If targeting Android 3.2 (API level 13) and higher, you can also use images in the WebP file format (instead of using PNG or JPEG files). The WebP format provides lossy compression (like JPEG) as well as transparency (like PNG), but offers better compression than JPEG or PNG.
You can convert existing BMP, JPG, PNG, or static GIF images to WebP format using Android Studio. For details, see Creating WebP Images with Android Studio.
Note: Google Play will only accept APKs if the launcher icon is in PNG format.
Using Vector Graphics#
You can create resolution-independent icons and other scalable media using vector graphics. Using these graphics can significantly reduce the space occupied by the APK. Vector images are represented in Android as VectorDrawable objects. With VectorDrawable objects, a 100-byte file can generate a clear image that matches the screen size.
However, the system takes considerable time to render each VectorDrawable object, and larger images take longer to display on the screen. Therefore, consider using these vector graphics only for displaying small images.
For details on using VectorDrawable objects, see Using Drawable Resources.
Using Vector Graphics for Animated Images
Do not use AnimationDrawable to create frame-by-frame animations, as this requires adding separate bitmap files for each frame of the animation, which significantly increases the size of the APK.
Instead, you should use AnimatedVectorDrawableCompat to create animated vector drawables.
Reducing Native and Java Code#
You can use various methods to reduce the size of Java and native code libraries in your app.
- Remove unnecessary generated code
Make sure you understand the space occupied by any automatically generated code. For example, many protocol buffer tools generate excessive methods and classes, which can double or triple the size of the app.
- Avoid using enums
A single enum can increase the size of the app's classes.dex
file by about 1.0 to 1.4 KB. These size increases can quickly accumulate, creating complex systems or shared libraries. If possible, consider using @IntDef
annotations and ProGuard to remove enums and convert them to integers. This type conversion can retain the various safety advantages of enums.
- Reduce the size of native binaries
If your app uses native code and the Android NDK, you can also reduce the size of the release version of the app by optimizing the code. Removing debug symbols and not extracting native libraries are two practical techniques.
- Remove debug symbols
If the app is in development and still needs debugging, using debug symbols is very appropriate. You can use the arm-eabi-strip
tool provided in the Android NDK to remove unnecessary debug symbols from native libraries. After that, you can compile the release version.
- Avoid extracting native libraries
When compiling the release version of the app, you can package uncompressed .so
files in the APK by setting android:extractNativeLibs="false"
in the element of the app manifest. Disabling this flag prevents the PackageManager from copying .so
files from the APK to the file system during installation, providing the additional benefit of reducing app updates.
Maintaining Multiple Slim APKs#
APKs may contain content that users download but never use, such as resources for other languages or screen densities. To ensure that users receive the smallest download file, you should upload your app using Android App Bundle to Google Play. By uploading an App Bundle, Google Play can generate and deliver optimized APKs tailored to each user's device configuration, so users only download the code and resources needed to run your app. You no longer need to compile, sign, and manage multiple APKs to support different devices, and users can receive smaller, optimized download packages.
If you do not plan to publish the app on Google Play, you can break the app into multiple APKs and differentiate them based on factors such as screen size or GPU texture support.
When users download your app, their devices receive the correct APK based on the device's capabilities and settings. This way, the device does not receive resources for features it does not possess. For example, if a user has an hdpi
device, they do not need the xxxhdpi
resources you may provide for devices with higher density displays.
For details, see Configuring APK Splits and Maintaining Multiple APKs.
Reducing Native Size#
android {
...
defaultConfig {
...
// Configure the externalNativeBuild block under the defaultConfig node
externalNativeBuild {
// For ndk-build, instead use ndkBuild {}
cmake { // Specify cmake compilation options
// Sets optional flags for the C compiler.
cFlags "-fvisibility=hidden -ffunction-sections -fdata-sections"
// Sets optional flags for the C++ compiler.
cppFlags "-fvisibility=hidden -ffunction-sections -fdata-sections -std=c++11"
arguments "-DANDROID_TOOLCHAIN=gcc"// Set to use gcc for compilation; cmake defaults to clang
}
}
}
buildTypes {...}
}