7.
Optimizing App Size & Obfusticating the APK
Written by Evana Margain Puig
You have two options when releasing an app to your users:
- You can launch it exactly as it was when you, or one of the developers in your team, typed the last line of code.
- You can implement optimizations on your code and app to make it run better and occupy less space in your users’ smartphones.
At this point, you may be thinking the obvious best option is the second one. But it would surprise you how many companies release apps with the default settings because of the time contraints most teams are under.
In the first section of this book, you learned how to release an app with the basics. If you’ve gotten this far, I assume you care for your users. The last chapter was the first step in making a more robust app.
In this chapter, you’ll learn and implement techniques to optimize your app in several ways that will make your app smaller, give it better performance, and overall, provide a better experience for your users.
Why is optimization important?
The answer to this question may come from the word optimization itself. In today’s world, we often talk about optimized things as better or enhanced, but why is it important for your app?
To understand this, take a step back. You probably own a smartphone, and with it, you use several apps. Think about what qualities make you like or dislike an app. Do you dislike apps with a lot of bugs?
When users judge apps and choose which to uninstall, they look for several things. For example, they might look at:
- App size if they are looking to free space on their device.
- Loading times from screens and actions in the app.
- How much battery the app consumes.
These examples pertain to optimization. If you want a happy userbase and want your app to perform well, you’ll want to optimize it.
Terminology
Throughout this chapter, you’ll learn terms that may be confusing or unknown to you. Take a look at some of those terms to establish the base for what you’ll learn.
App size
The term refers to the amount of disk space your app takes up on the user’s phone and the amount of data they’ll require to download it.
It’s important to consider that users connect to 2G or 3G networks in many countries where network providers charge based on the size of the things they download. So, you want to make your app and assets as small as possible.
Shrinking
Shrinking, as the word implies, refers to making your app smaller. There are two types of shrinking, one for code and another for resources. Take a look at each of them.
Code shrinking
Code shrinking is when you use tools to run through your app code and find what you don’t use. This kind of tool removes unused things in your code, like classes and variables. This process generates a thinner version of your app with only the necessary code. It may sound like magic, but you’ll learn how useful this can be.
Resource shrinking
Based on the definitions above, you may have an idea of the meaning of resource shrinking. As with code shrinking, these tools scan your project’s resources, which have a wide range including images, strings and XML files. Then, they remove the unused ones.
Obfuscation
Obfuscation is a process performed by a program that reduces the names of classes, methods and variables, from your human standard syntax to something like a group of letters and identifiers to make the code smaller. After obfuscation, the process of debugging changes since you won’t know exactly what the class refers to.
Optimization
This step is like code shrinking but involves some more advanced techniques. The tools analyze your code and rewrite some things, so your code works faster.
Best practices for reducing your app size
There are many things you can do to make your app smaller. Options range from reducing image quality to letting the Google Play console build your APK. In this part, you’ll take a look at the recommended options for reducing app size.
Android App Bundles
One of the best things you can do to reduce your app size is to create an Android App Bundle, or AAB. You already did this in Chapter Two.
An AAB reduces your app size because once you upload your app in this format, Google Play takes charge of creating an APK for the user’s device. This APK includes only the necessary things for their device and the Android version it has.
For example, if you’re bundling your apps with images of different sizes for specific DPIs, it’ll only bundle the ones that correspond to the user’s device’s size.
If the Google Play Console detects your app may have a download size above 150Mb in any of the versions, it’ll warn you. You won’t be able to release until you get below this hard limit.
Note: If for any reason you decide to upload an APK instead of an AAB this limit will go down to 100Mb.
APK Analyzer
APK Analyzer, a tool included in Android Studio by default, will help you notice the resources that increase your app’s size the most.
Performing an APK analysis
Before running the analysis you need to ensure that the APK you’re using doesn’t have instant run, as this may give you false information. To ensure this, go to Build ▸ Build Bundle(s) / APK(s) ▸ Build APK(s) as shown in the image below.
You’ll get a Gradle Build indicator at the bottom. When it’s finished, you can run the analysis by going to Build ▸ Analyze APK…. Once the analysis finishes, the analysis window will appear on the main screen as in the following image:
First, notice how the analyzer gives you basic data about your app:
-
APK size
-
Download size
-
Version name
-
Version code
-
List of files inside the APK
Then, notice in the image above that you have classes.dex and classes2.dex. This might be different for you, but it’s important to observe. The term for this is MultiDex, which means you have more than one classes.dex, and each has a number.
Android has a rule where you can’t exceed 65,536 method references. To see how many references your project has, click the first classes.dex and take a look at the info message that appears.
In the image above it says there are 57,387 references. If you do the same with classes2.dex, it mentions 1,503 references. Interestingly, this app doesn’t exceed the number of references, but Android Studio decided to automatically separate them since it’s close to the limit.
Since Android 5.0, the build process can decide whether or not to create multiple dex files. Ideally, you would only want to have one. But in today’s world, with apps being more powerful and large every day, this can become difficult.
Understanding APK Analyzer results
What you do with the analysis results will vary greatly depending on your app. Despite that, there’s a couple of important things to observe here:
-
By clicking on classes.dex you can see how much space each of the libraries you included uses. In this case, notice the AndroidX libraries are the ones with more references.
-
You can explore your resources and see particular ways to optimize your app that Android Studio may not have identified.
The rest will depend on the app itself.
Finally, notice there’s a button at the top that says Compare with previous APK. Once you finish with your optimizations, you can compare the new APK and see what improvements you got.
Other improvements you can do with the analysis
Besides the app specific things that you may get from the analysis, here are some best practices to improve your app size.
Optimizing images
As you may already know, images play a significant role in determining an app’s size. Experienced designers have a variety of non-coding techniques they will do to reduce these sizes. But, as this book is about coding, we are going to take a look at what you can do code-wise to reduce the size of images.
Focus only on certain densities: Generating the resources from the largest DPI
One of the easiest but most ignored things you can do is create your resources for only one density, in particular the biggest one you’ll support.
When Android gives you the option of providing resources for every Screen Density, you may think the best thing to do is to provide them all to make a complete app. Although that is a solution that will work, it’s important to know that Android itself can downsize resources to lower densities whenever needed. So the only reason you would need to provide a resource for another density is when the smaller one changes in dimensions or is a different resource. But if all you’re doing is downsizing them, let Android do it for you.
Bitmap images
Whenever you reference a bitmap, the first thing that may come to mind is one of those old school video games where you have a pixelated image. The reality is most of the images you use on your computer are bitmap formats, such as JPG, PNG and GIF.
Note: For handling bitmaps, Google recommends using Glide (https://github.com/bumptech/glide), which is a library that works together with your app to optimize these images at runtime. Glide won’t help reduce your app size, but it will help your app perform better at runtime and make your users happy.
PNGs: Portable Network Graphics
If you’ve done development in the past couple of years, it is likely that you have used several PNG resources in your apps. That said, this is a heavy format that contains more detail than you need for an Android application. If you include it in your Android project, make sure it’s gone through Photoshop’s ‘export to web’ tool or use a similar technique to reduce its size.
JPG or PNG
JPG is a bit older than PNG. Generally, there’s no noticeable difference from other formats if your app is not exploring images in detail.
Often, for web and mobile, many developers prefer using PNG over JPG becuase PNG uses lossless compression.
Beyond the image quality improvement, another significant advantage PNGs have over JPGs is that PNGs support transparency. If you’ve downloaded an image from the internet in JPG format and ended up having a white square surrounding the image you understand why this is important. From a size perspective though, it is still larger than we would like it to be.
Now that we’ve criticized the two most common image formats, you are probably wondering if there is a better option.
WebP
In 2013, Google introduced this new image type. Don’t worry if you haven’t heard of it. It’s relatively new and not as widespread as it should be. In short, WebP allows you to use lossless compression like you do for PNG, but with savings of 24% to 35% in size. There is also an option to apply lossy compression to a WebP image to make it even smaller.
While this may sound like a small amount of savings, think a little deeper. Imagine you uploaded an app within the 150Mb size limit, of which 100Mb are JPEG images. When you transform them to WebP, your app is at least 24Mb smaller. You turn a 150Mb app into a 126Mb app. That’s significant for someone with a limited data plan.
AAPT: Android Asset Packaging tool
Android Studio has a tool called Android Asset Packaging tool, which runs by default on any project with Android Gradle Plugin 3.0.0 and above.
This tool runs tests on your images and optimizes them accordingly through algorithms. It’s important to remember AAPT only optimizes resources under res/drawable, and nothing in the asset/ folder.
Vector graphics
You may have noticed that Android offers a wide range of possibilities when using images. Vector graphics, also known as SVGs, are another trending image format often used in Android apps. Android has its specific sub-version of SVGs called Vector Drawables.
Vectors are fantastic because you can resize them to any dimension needed. Designers have used them for a long time.
However, they have a particular disadvantage in Android: They are slow to draw on the screen. That said, it’s not unusual to see them used, and abused, in many Android apps. Even though they’re a great tool, users dislike slow apps as much as they dislike large apps, so be mindful when using Vector Graphics.
Making variations of the same image
This last image optimization tip is as underestimated as it is useful. The simplest example of this is an arrow. Say your app needs a set of arrows as assets, each pointing in a different direction. The most common thing to do is export each of the arrows and upload them as assets. But what if you upload one arrow and reuse the asset by using code to rotate it?
This is a simple memory-saving technique you can use while coding. Your users will thank you for being mindful of such details.
The same thing goes on altering the color of an asset through code. If you need a red and a blue version of the same assest, leave the color change to code instead of uploading several assets.
Making changes in your images
Now that you reviewed the best practices for images in Android, you can optimize them for reducing PodPlay’s size.
Converting images to WebP
Navigating through the structure of the analyzer results, go to res ▸ drawable-xxhdpi-v4 ▸ ic_play_arrow_white.png. You will get a preview of the image at the bottom of the APK analyzer.
Of course this image doesn’t have a very large size, but this is just for you to learn how to do it. Right click on the image preview and you will get a list of options.
Click on Convert to WebP and you will see a dialog open like in the image below.
As a general recommendation, the default options are a good start, but you may need to play a little with the options depending on the desired image quality. This is mainly an exercise of balancing the reduction of the image size with the level of quality to come up with a combination that works for you.
Click on the OK button and you will get a comparison of both images and a slider that you can adjust to preview the change. Play with the slider and see if you notice any difference. In this kind of basic images you will rarely see a change, which is good news! You can have a smaller file without any visible changes.
Note: In the current version of Android Studio at the time of writing, Android Studio 4.2, there is a bug where the image doesn’t change to webP. Another option is to do the same process but through Android Resource Manager, in the exact same way, right-click the image and select convert to WebP, you will get the same dialogs as above. The image below shows you where to locate Android Resource Manager
Large files
The next suggestion is to review large files. A common recommendation is to add the images through a dynamic-feature. You’ll learn how to do this in the next chapter.
Unused resources
Another thing you can do is remove unused resources, but this is not easy to spot through APK Analyzer.
The PodPlay app, has no unused resources because it was created for another book, so add one empty method by going to app ▸ java ▸ ui ▸ PodcastActivity.kt and at any point in the file add the following code:
fun myUnusedMethod() {
// this is a useless method
}
Note: In a production app, ensure the code isn’t used before deleting it. The technique you will see below may not catch all references to files, particularly if any library uses them through code generation.
Once you have that method added, in the top bar of Android Studio select: Analyze ▸ Inspect Code.
The process will take a couple of minutes and eventually an Inspection Results screen will appear in the bottom part of your screen.
You will see a lot more suggestions that you may want to address and will be unique to each project, but for now focus on identifying the unused method. Navigate in the results to Kotlin ▸ Redundant Constructs ▸ Unused symbol. There you will find a message showing you that the method you just created isn’t being used.
If you click on the message, you will get the function in the code editor to the right. There is also a Safe delete option there.
Click on Safe delete. You will get a message which will show you two checkboxes that can run the checks again to ensure there wasn’t a mistake.
In this case you are sure this method isn’t used so click on OK.
Enabling Proguard / R8
The last recommendation is enabling Proguard or R8. You’ll learn how to do this later in this chapter, so don’t worry about it for now.
That’s it for automatic recommendations. As you may have noticed, most are pretty straightforward, and Android Studio will help you reduce your app’s size.
Size reductions to make directly in your code
Besides the changes mentioned above, developers can do some recommended optimizations to reduce the size of the APK:
-
Look for code generated by libraries that may not be necessary.
-
Some libraries include code intended for web or other platforms. Make sure this isn’t part of your APK.
-
Enums require a lot of space, so beware of abusing them.
Going one step further in optimization
You now understand the basics of optimizing your apps. At the beginning of the chapter, there was a mention of a so-called ProGuard or R8. In this section, you’ll learn about ProGuard, and its successor R8, which will let you configure your optimization even further.
ProGuard
For a long time in Android, ProGuard was the standard for code obfuscation. If you’re using Android Gradle plugin 3.4.0 or higher, you’re probably not using ProGuard anymore. Even when the loaded file is from ProGuard, you’re using R8.
What is ProGuard?
In short, it’s a code optimizer for Java Bytecode. Even when you’re using Kotlin, the compiler will transform your code into Java, and Google recommends using a code optimizer. ProGuard and R8 are similar, but ProGuard is a bit more complicated.
Note: It’s important to note that you can still use ProGuard in your projects by adding the following code to gradle.properties:
android.enableR8=false
android.enableR8.libraries=false
Using R8: Shrinking, obfuscation and optimization
Shrinking, obfuscation and optimization aren’t enabled by default in newer versions of Android Studio for two main reasons:
-
The compilation time is longer, which may be inconvenient for developers.
-
They may cause some bugs, particularly when dealing with third-party libraries.
Enable the features by adding the following code to the buildTypes block inside your project-level build.gradle:
android {
buildTypes {
// 1
debug {
//2
minifyEnabled true
//3
shrinkResources true
//4
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
...
}
Here’s a code breakdown:
-
Enables the features for your project’s debug build type.
-
This line enables code shrinking, obfuscation and optimization.
-
As the name implies, it enables resource shrinking.
-
Enables the default ProGuard rules, which have the most commonly used rules for optimizing your code.
Once you have this in your code, make sure you’re building the debug version by clicking on the left panel. Then click the dropdown to select debug as in the image below:
Now repeat the steps you did with the APK analyzer to create an APK.
Next, trigger the APK Analyzer again to see how much of a difference enabling these three features made.
In the case of PodPlay you can see the changes in the screenshots above:
-
The app size reduced from 5.1MB to 2.7MB, which is a reduction of almost half the app’s size.
-
The download size has also reduced from 4.7MB to 2.3MB, in this case, more than half.
-
In this case, the classes.dex is only one file and not multiple.
Now, look at obfuscation. Click classes.dex. You’ll get a list of the files and folders inside the APK. You will see packages and files named with the letters a, b, c and so on.
These folders have those names because of the obfuscation process discussed earlier. Names of classes, variables and other things in your projects will change to letters that the compiler can understand, but are difficult for a human to trace.
Key points
-
App size is one of the most important features for your users.
-
It’s better to bundle your app as an AAB rather than using an APK.
-
Google Play won’t allow Android App Bundles larger than 150MB in size.
-
PNG is a great format, but you should take care of the size of your images.
-
WebP is the recommended image format by Android.
-
You can use APK Analyzer to understand the format of your APK and find ways to optimize it further.
-
It’s important to enable shrinking, obfuscation and optimization in the final builds of your app.
-
R8 is the recommended tool for cleaning up your project and reducing your app’s final size.