preface

July was really crazy and I had no time to write my blog. I will try to output more posts in August.

In this section, we will talk about some knowledge points of shrinking the package. Reducing the SIZE of APK is a very important optimization task in App development. From the perspective of users, the smaller the memory occupied by the installation package, the better. From the perspective of the application itself, too large an installation package will affect the loading speed and power consumption of the App. Therefore, when an application reaches a certain stage, it is necessary to consider reducing the size of the package.

APK structure

Before shrinking the package, it is customary to take the structure of the APK out to dry:

  • META-INF/: There are cert. SF, cert. RSA, and manifest.mf files. RSA is a signature file that the developer uses the private key to sign the APK. Cert. RSA and manifest.mf record the SHA-1 hash of the file.
  • assets/: contains configuration files and resource files, which can be obtained using AssetManager.
  • resources.arsc: compiled binary resource file. Includes all XML configuration files, language strings, and styles for the res/values/ folder, as well as the path to content (such as layout files and images) not directly contained in the resources.arsc file.
  • res/: Some resource files that are not compiled into resources.arsc, such as drawable, Layout, anim, etc.
  • lib/: contains library files for each platform type.
  • classes.dex: A dex file that is compiled from a Java bytecode file and understood by the VIRTUAL machine
  • AndroidManifest.xml: The Android manifest file, which includes the application name, version, access rights, and referenced library files.

The build process

With the APK structure out of the way, let’s look at the build process. How does an APK compile? Let’s start with a scary flow chart from our website:

As shown in the figure above, Manifest, Resources, Assets Resources are processed by AAPT to generate R.Ava, Proguard Configuration, and Compiled Resources. Among them, R. Ava is more familiar to everyone, so I will not introduce more here. Proguard Configuration, Compiled Resources, etc.

  • Proguard Configuration is a Proguard Configuration generated by the AAPT tool for the four components declared in the Manifest and the Views used in the XML layouts. This file is usually stored in ${project.builddir}/${Androidproject.fd_intermediates}/ proguards-rules /${flavorName}/${buildType}/aapt_rules.txt, Here’s a screenshot of this file from the project, with the red box indicating the ProGuard configuration for androidmanifest.xml and XML Layouts related classes.

  • Compiled Resources is a Zip file, The path to this file is usually ${project.buildDir}/${Androidproject. FD_INTERMEDIATES}/res/resources-${flavorName}-${buildType}-stripped. Ap_. As you can see from the Zip unzipped screenshot below, this file contains files or folders for res, Androidmanifest.xml, and resources.arsc. This file (resources-${flavorName}-${buildType}-stripped. Ap_) is packaged into the APK package by the ApKBuilder. This is essentially APK’s “resource bundle” (res, AndroidManifest.xml, and resources.arsc).

More can go to meituan technology team -App package slimming optimization practice.

Based on the flow chart and the structure of the final APK, let’s first think about what AAPT (AAPT2), ProGuard (R8) and APKBuilder are used for.

Shrink package

Compression, obfuscation, and optimization

When building a project with Gradle plugin 3.4.0 or higher, instead of using ProGuard for compile-time code optimization, you use the R8 compiler for the following tasks:

  • Code reduction: Detects and safely removes unused classes, fields, methods, and properties from the application and its library dependencies (this effectively circumvents the circumventing 64K reference limit). For example, when we use only a few apis for a library, the downsizing task can identify library code that is not used by the application and remove only that part of the code from the application.

  • Resource reduction: Remove unused resources from the application, including unused resources in the application library dependencies. This feature can be used in conjunction with code reduction, so that once you remove unused code, you can safely remove all resources that are no longer referenced.

  • Confusion: Shorten class and member names to reduce the size of the DEX file.

  • Optimization: Review and rewrite the code to further reduce the size of the DEX file for the application. For example, if R8 detects an else {} branch that never took a given if/ ELSE statement, it removes the code for the else {} branch.

AS for faster compilation, some optimization tasks are disabled by default, so we can enable them in Gradle:

    buildTypes {
        release {
            // Obfuscation and code reduction
            minifyEnabled true
            // Enable resource reduction
            shrinkResources true}}Copy the code

When actually using the shrinkResources configuration, be aware that, for example, a resource is fetched by resources.getidentifier, but the resource is incorrectly removed by the compiler. In this case, you can manually reserve the resources using the Tools :keep property:

<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used_a,@layout/l_used_b*,@layout/l_used*_c"
    /* It is also possible to discard certain resources */
    tools:discard="@layout/unused2" />
Copy the code

By default, resource reduction under R8 removes only resources that are not referenced by the application code. This means that it does not remove alternate resources for different device configurations, so we can use the resConfigs property to remove some irrelevant resources:

    android {
        defaultConfig {
            /* Retain only Chinese language resources */
            resConfigs "zh"."zh-rCN"}}Copy the code

Now, what does the R8 compiler do? It converts the Java bytecode in the project into a dex file that runs on the Android platform. So at compile time, it can do the optimization tasks listed above on the original bytecode to produce an optimized.dex file.

Image optimization

Android resource image formats are generally: PNG, JPG, and WebP, and for each format, you can take steps to reduce the image size.

Vector of the bitmap

Using vector graphics to create resolution-independent ICONS and other scalable media can greatly reduce the space taken up by APK. Vector images are represented in Android in the form of VectorDrawable objects, with which a 100-byte file can produce a sharp image of the same size as the screen.

However, it takes a considerable amount of time for the system to render each VectorDrawable object, and larger images take longer to appear on the screen. Therefore, the official recommendation is to use these vector graphics only when displaying small images.

Again, do not use AnimationDrawable when creating frame-by-frame animations, because doing so requires adding a separate bitmap file for each frame of the animation, which greatly increases the size of the APK. The alternative is to use AnimatedVectorDrawableCompat create vector animation. Methods can refer to my Android training series (16), do you know these animation methods? .

WebP

WebP is an image format supported by Android 4.2.1 (API level 17). This format provides excellent lossless and lossy compression for images. With WebP, we can create smaller, richer images. WebP lossless image files are on average 26% smaller than PNG files. These image files also support transparency by adding only 22% bytes.

WebP lossy images are 25-34% smaller than JPG images using equivalent SSIM quality index. For cases where lossy RGB compression is acceptable, lossy WebP also supports transparency, and the resulting file size is typically 3 times smaller than PNG.

So WebP is an ideal alternative to PNG and JPG. The only catch is that WebP is supported natively only on Android 4.2.1 and later devices.

WebP is no longer supported by Android 4.2.1. We can still encode/unpack WebP using the third-party libraries.

AndroidStudio can now convert BMP, JPG, PNG or still GIF images directly to WebP format, not only single images, but also folders containing multiple images. Specific is not said here, you can directly see file WebP format conversion.

PNG and JPG

JPG and PNG compression processes are very different and produce significantly different results. The choice between PNG and JPG often depends on the complexity of the image itself. The two images shown below show very different results due to different compression schemes. The image on the left contains many small details, so it is more efficient to compress using JPG. The image on the right contains consecutive identical colors and is more efficient when compressed using PNG.

For JPG and PNG resources, we can filter out larger images and directly use Tinypng for compression. By combining similar colors in the images, the 24-bit PNG images are compressed into 8-bit color values, and the image matadata information is eliminated, which can reduce the image by about 30%. For an opaque PNG image, we can convert it to a JPG image, which is very effective.

To compress JPEG files, use packJPG and Guetzli. These tools can compress image files such as Guetzli and reduce file size by 35% without compromising image quality.

JPG can use scalar values to balance image quality and file size, 75% quality is fine for most images, and 35% quality for thumbnails is recommended.

How to choose?

So different formats of pictures, how should we choose? Google has already given us a solution: Use WebP if you support it, PNG should be used for transparent or simple images, and JPG should be used for other scenarios. Of course VectorDrawable is preferred if we can use VectorDrawable.

It is also important to note that the AAPT tool itself can optimize PNG images placed in res/drawable/ by lossless compression during compilation. For example, the AAPT tool can convert true-color PNGS that do not require more than 256 colors to 8-bit PNGS using the color palette to produce images of the same quality but with a smaller memory footprint.

But the AAPT tool may extend the PNG files we have already compressed. To prevent this, we need to use cruncherEnabled in Gradle:

    aaptOptions {
        // Disable this process by marking the PNG file
        cruncherEnabled = false
    }
Copy the code

Resource confusion

Unlike R8’s obfuscation optimization, which deals with class and member names, resource obfuscation only deals with resources.

It is recommended to use AndResGuard, a tool provided by wechat. AndResGuard works like Java Proguard, but only for resources. It will shorten the originally lengthy resource path, such as res/drawable/wechat to R /d/a.

AndResGuard does not involve a compilation process, just enter an APK (whether signed or not, debug version, release version, will directly delete the original signature during the process), you can get a implementation of resource confusion APK (if you enter the signature information in the configuration file, can automatically re-sign and align, Apk) and the corresponding resource ID mapping file. The specific use method can directly see gitHub, very detailed, here is not much to introduce.

apply plugin: 'AndResGuard'

buildscript {
    ...
    dependencies {
        classpath 'com. Tencent. Mm: AndResGuard - gradle - plugin: 1.2.21'}}Copy the code

If you use getIdentifier to obtain resources, add them to the whitelist.

So optimization

In real projects, so library files are always hard to reduce package size. The above flowchart shows that the SO file will be packaged into apK file by APKBuilder. In this process, the system will compress the SO file. The algorithm uses the level 8 ZIP Deflate compression algorithm. Regarding the optimization of SO, there are mainly several commonly used methods:

  1. For so files, we can reduce support for the CPU platform as needed. One thing to note is that Google now requires the APP to adapt to V8A, vivo, Xiaomi and other domestic stores have begun to promote the adaptation of V8A:
    ndk {
        abiFilters 'armeabi-v7a'.'arm64-v8a'
    }
Copy the code
  1. For the size optimization of SO, we can also adopt dynamic delivery, but this scheme has a certain amount of development and difficulty, and the specific process will be separately introduced in a subsequent section.

  2. Compared with ZIP compression, we can adopt 7z LZMA algorithm with better compression rate, and the compression rate of a single file can be improved by about 10%. However, it needs to be noted that 7z compression so needs to be decompressed before loading, which will take time. It is recommended that the project load SO in asynchronous mode at this time. (There is a lot of content here and I plan to do it in a future gradle plugin module article).

Assets to optimize

For resources in assets folder, such as RAW file, font package and plug-in file, dynamic download can be considered. For resources that must be preset, 7ZIP compression can be adopted.

Dex optimization

ReDex is an Android bytecode (DEX) optimizer developed by Facebook. It provides a framework for reading, writing, and analyzing.dex files, as well as a set of optimization channels for improving bytecode using the framework.

For details, see gitHub Redex and tutorials.

other

  • Specific density only: Android supports a wide range of devices, covering a wide range of screen densities. In Android 4.4 (API level 19) and later, the framework supports a variety of densities: LDPI, MDPI, TVDPI, HDPI, XHDPI, XXHDPI and XXxHDPI, which we can support only on demand.

  • Reuse resources: We can reuse the same set of resources and customize them as needed at runtime, such as the official website example: rotate the resource image from “thumb up” to “thumb down”. This has the advantage of eliminating the need for two sets of image resources. The same goes for image tone adjustment, shadow setting, image rendering, etc. Android provides a number of utilities to change the color of resources. On Android 5.0 (API level 21) and later, use the Android: Tint and tintMode properties, and use the ColorFilter class for earlier platforms.

    <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" />
Copy the code
  • Use vector graphics to animate images: Do not use AnimationDrawable to create frame-by-frame animations, as doing so requires adding a separate bitmap file for each frame of the animation, which greatly increases the size of the APK. We should be changed to use AnimatedVectorDrawableCompat create animated vector can map resource.

  • Remove unnecessary generated code: Make sure you know how much space is being taken up by any code that is automatically generated. For example, many protocol buffer tools generate too many methods and classes, which can double or triple the size of an application. There are compile-time annotations, bytecode manipulation utility classes, and be sure to know what classes are generated automatically.

  • Avoid enumerations: a single enumeration can increase the size of your application’s classes.dex file by about 1.0 to 1.4KB. These increased sizes can quickly add up to complex systems or shared libraries. If possible, consider using the @intDef annotation and code reduction to remove enumerations and convert them to integers. This type conversion preserves various security benefits of enumerations.

  • Remove debug symbols: When compiling a release, you can use the arm-Eabi-strip tool provided in the Android NDK to remove unnecessary debug symbols from the native library.

  • Native library: avoid decompression in building an release, can be in the application list < application > element in the set the android: extractNativeLibs = “false”, will be uncompressed. So packaged in the APK file. Disabling this flag prevents PackageManager from copying.so files from APK to the file system during installation, and has the added benefit of minimizing application updates. (But this is contrary to the above so optimization point 3, it depends on which is the higher yield). When building with Android Gradle plugin 3.6.0 or later, the plugin will set this property to “false” by default.

Reference:

Developer.android.com/topic/perfo…

Developer.android.com/studio/buil…