📜 ⬆️ ⬇️

Compress the APK, trying to keep it working


/ Pxhere / pd


Weight Optimization APK is a non-trivial, but very relevant in the Instant App, task. The inclusion of proguard will save you from unnecessary code, if your dependencies can be determined at the compilation stage, but the APK has several other types of files that can be excluded from the assembly.


Under the cut on how to make the dependencies - defined at compile time, which files can be excluded from the assembly and how to do it, and also, let us analyze how to exclude unused components from the assembly if you have several applications with a common code base.


Before reading



The weight of our Google- optimized APK has been 4.4 .


Extra files


Let's start with the simple. If you are not using kotlin-reflect , you can exclude meta-information about kotlin-classes from the assembly. You can do this as follows:
In build.gradle (Module: app)


 android { packagingOptions { exclude("META-INF/*.kotlin_module") exclude("**.kotlin_builtins") exclude("**.kotlin_metadata") } } 

For Java reflection, no *.kotlin_module , *.kotlin_builtins and *.kotlin_metadata files are needed. Determining which reflection you use is very simple. If you write obj::class.<method> , then you use kotlin-reflection, if obj::class.java.<method> , then java-reflection.


Total optimization for us: -602.1 kb


Dependencies


Sometimes libraries pull dependencies for cases that never happen in your application. For example, ktor-client pulls kotlin-reflect along with it (0.5 mb!).
I struggled with such cases as follows: I collected an APK with minifyEnabled = true , threw it into the Android Studio analyzer, downloaded mapping.txt and searched for packages that, in theory, should not be present in the assembly. For example, kotlin.reflect . After running ./gradlew app:dependencies in the project folder for searching dependencies (do not forget to increase the length of the history in the terminal. The dependency tree can be large!). On this tree it is easy to understand that it refers to unnecessary dependencies and eliminate them. In your module's build.gradle :


 dependencies { implementation("io.ktor:ktor-client-core:$ktorVersion") { exclude(group: "org.jetbrains.kotlin", module: "kotlin-reflect") } implementation("io.ktor:ktor-client-okhttp:$ktorVersion") { exclude(group: "org.jetbrains.kotlin", module: "kotlin-reflect") } } 

This code removes the dependency of the ktor-client library on kotlin-reflect . If you want to exclude something else, substitute your values.


!!! Use this tip very carefully! Before excluding dependencies, make sure you do not need them. If you do not do this, the application may start to fall in production !!!


The result of optimization for us: -500.3 kb


Check your XML


Unfortunately, proguard does not remove unnecessary XML markup files from the layout folder. Unused XML can use heavy widgets and proguard cannot exclude them from the build too! To avoid this, remove unused resources using Refactor -> Remove unused resources...


Check your DI


If you, like us, use runtime DI, then check if you have providers for dependencies that you do not use. Proguard cannot exclude them from the assembly because they are not unused from the point of view of the compiler. You use them when building a dependency graph.


Exclude debug dependencies from the release build


Debugging tools can take up unexpectedly a lot of space. For example, stetho weighs about 0.2 after compression! In any case, it is better to exclude the entire debug infrastructure from the release build so that no one can learn too much about your application just by downloading it from Google Play.


You can make debugging and release use different versions of the same files. To do this, in the src folder, next to main , create debug and release folders. Now you can write the initStetho function, which initializes Stetho in the src/debug/java/your/pkg/Stetho.kt and the initStetho function, which does not do anything, in the src/release/java/your/pkg/Stetho.kt .


Just in case, make it so that this dependency is included only in debug builds. You can do this by replacing implementation with debugImplementation in build.gradle . Most often, proguard eliminates unnecessary files even without this step, but not always. The answer to the question "why?" below in the text of the article .


Platforms


Sometimes several different versions of an application are released on the same code base. These may be different versions for different countries or regions, or, as in our case, for different clients. Below are tips on how to unload the platform.



/ Pxhere / pd


Our experience


We are developing mobile application designer E-SHOP . We have several dozen customers and each has its own individual set of components. Some components are used by all customers, some only by part. Our task is to include in the client's assembly only those components that he needs.


Flag component exclusion


For each client, we create a separate productFlavor . This is convenient because it is easy to make different resources for different clients, the IDE provides a graphical interface to switch between flavors, and caches work well. You can also generate your own BuildConfig.java for each client. The values ​​of the fields of this class are known at compile time. That's what we need! Create a boolean field for each component.


 android { productFlavors { client1 { buildConfigField("boolean", "IS_CATALOG_ENABLED", "true") } client2 { buildConfigField("boolean", "IS_CATALOG_ENABLED", "false") } } } 

This is a simplified version of the configuration. This is difficult because of the integration with our CI.


Now it is known if the component is active at compile time, and proguard can exclude it from the assembly!


XML again


Now the problem with unused XML layouts takes on a new dimension! You can’t just take and remove the markup of any component simply because some customers don’t need it.


In our application in XML of one of the rarely used components, we used a widget that referred to the image recognition library firebase.ml.vision . It weighs about 0.2 mb, which is quite a lot. It was decided to add this widget code instead of declaring it in the markup. After that, proguard was able to exclude vision from the build for customers who do not need it.


Total optimization for us: -222.3 kb for medium APK


Abstract @Keep


There are 2 ways to tell proguard that your class cannot be minified: write a rule in the file proguard-rules.pro or put the @Keep annotation. In the play-services-vision library, this annotation is on the root class. Therefore, 0.2 mb was a dead weight, even in those client applications that do not need image recognition.


I did not find a simple and safe way to remove this annotation. If you know how - please write in the comments.


Fortunately, the firebase.ml.vision library, which is a newer version of play-services-vision , does not use this annotation and we solved the problem by switching to it.


And again DI


Last but not least item. DI with disabled components. Everything is simple: for each component we use our own container, and we connect the general dependencies through a separate module.


Total optimization for us: -20.1 kb for medium APK


findings



All the optimizations presented in the article are "low fruit". They are quite easy to implement and get results quickly. Up to -43% for an already optimized APK in our case. I hope I saved your time by listing everything in one place.


Thanks to all!


')

Source: https://habr.com/ru/post/452524/


All Articles