preface

The importance of package size goes without saying, package size directly affects users’ download, retention, and even some manufacturers’ pre-installation requirements must be less than a certain value. However, as the business develops iteratively, applications get bigger and packages get bigger, so package size reduction is a long-term governance process.

  • Increase the download conversion rate. The smaller the package, the higher the conversion rate.
  • Reduce channel promotion cost.
  • Reduce installation time, file copy, Library decompression, ODEX compilation, signature verification, etc., the larger the package size, the more time consuming.
  • Reduce runtime memory and so on.

The environment

  • Android Studio Arctic Fox | 2020.3.1 Patch 2
  • AGP 7.0
  • Project address: wanAndroid_jetpack

Before optimization

  • 4.7 MB4.2MB is the size of Google Play download, there will be compression.

In addition to the Analyzer that comes with AS, there are tools such AS ApkChecker and ClassyShark.

The composition of the APK

file describe
lib So files, different CPU architectures
res Compiled resource files, drawable, layout, etc
assets Application resources, fonts, audio files, etc
classes(n).dex Dx compiled Java file
META-INF Signature Information Correlation
resources.arsc Binary resource file
kotlin The compiled kotlin file
AndroidManifest.xml The manifest file

APK build process

This is the official version of the packaging process, although some steps are omitted, but the general process is relatively clear.

To simplify again:

Resource file, Java file > dex file > APKCopy the code

Optimization idea

APK is essentially a compressed file that is the product of packaging, and the stages that can be used as a starting point are before and during packaging.

  • Before packaging, reduce the packaging files, such as useless resources, code;
  • In packaging, compress the products in packaging, such as resource files and So files;

Key words: reduce, compress.

Normal operation

1.Lint detects useless resource files

Analyze > Run Inspection by Name > Unused resources
Copy the code

Test results:

Make sure it is useless to delete.

Note: Because Lint is a local static scan, dynamically referenced resource files are not recognized and will also appear in the checklist.

2.Lint checks code

Analyze > Inspect code 
Copy the code

Test results:

Because the project is written in Kotlin, look directly at the test results in the Kotlin directory.

Note: Since Lint is a local static scan, reflected and dynamically referenced classes are not recognized and will also appear in the checklist.

3. Image compression

Tinypng is recommended for online compression.

4.TinyPngPlugin

After all, manual compression is not efficient, so TinyPngPlugin can be used for one-click compression.

Plugins search for TinyPng installation. (Plugin does not need to be restarted after the new VERSION of AS is installed)

Compression results:

Nine pictures, you can see the effect is still very impressive. If there are many pictures, the effect is more obvious.

This results in a 4% reduction in package size, and that’s just for a 4.7MB APK.

5.WebP

Can these 9 images be further optimized? Yes, the WebP format is smaller, but AS also provides one-click conversion support.

Take IC_avatar.png as an example:

ic_avatar.png The optimized
Original size 113.09 KB
TingPng compression 36.85 KB
WebP 8.66 KB

It can be seen that after switching to WebP, the size has been reduced by nearly 93% compared to the original size

6. Turn on obfuscation

MinifyEnabled True. R8 code reduction is enabled by default.

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}Copy the code

Use R8 with caution because:

R8 ignores all ProGuard rules that attempt to modify default optimization behavior, such as -Optimizations and -OptimizationPasses.

Obfuscation can be turned on without R8.

android.enableR8=false
android.enableR8.libraries=false
Copy the code

Confusion Reference: Android confuses from beginner to master

7. Reduce resources

shrinkResources true

What if there are some resource files that you are not sure whether to use or delete, or you are not sure whether requirements will change, so you keep them? ShrinkResources can be used to shrinkResources.

    buildTypes {
        debug {
            minifyEnabled false
        }
        release {
            shrinkResources true
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}Copy the code

It should be used in conjunction with minifyEnabled obfuscation, and the principle is very simple. After the code is removed, the referenced resource becomes useless and can be further reduced.

8. So file reduction

For example, if a three-party live broadcast or browser is integrated, many SO files may be provided, which may be copied into the project in a rush at the beginning, but not all of them can be used.

For example, so for various CPU architectures:

app/build/intermediates/cmake/universal/release/├── Heavy Exercises, ├─ heavy Exercises, ├─ heavy Exercises, ├─ heavy Exercises, heavy exercises, heavy exercises, heavy exercises, heavy exercises, heavy exercises, heavy exercises, heavy exercises, heavy exercises, heavy exercises, heavy exercises, heavy exercises Libgameengine. So │ ├ ─ ─ libothercode. So │ └ ─ ─ libvideocodec. So ├ ─ ─ x86 / │ ├ ─ ─ libgameengine. So │ ├ ─ ─ libothercode. So │ └ ─ ─ libvideocodec. So └ ─ ─ x86_64 / ├ ─ ─ libgameengine. So ├ ─ ─ libothercode. So └ ─ ─ libvideocodec. SoCopy the code

The current market of mobile CPU is arm architecture, so keep one of the ARM (except customized), ArmeabI-V7A or Armeabi can be used, other directly delete.

android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi-v7a'}}}Copy the code

If the development requires emulator debugging, add x86 architecture, remove the formal package, or use a variable in local.properties.

If this piece has not been optimized before, and there are many SO files, it may be reduced by more than 30%, so horrible!

9. Remove unused spare resources

A lot of apps out there are international, but they don’t work in so many languages. In addition to their own app, there are some official, tripartite, can be unified configuration support language.

    defaultConfig {
        resConfigs("en"."zh"."zh-rCN")}Copy the code

Same with resource files

    defaultConfig {
        resConfigs("xxhdpi"."xxxhdpi")}Copy the code

Nodule 10.

Make a summary of the above and see how it works so far.

2MB, 57% reduction in package size, horrible!

If it’s a big project, the benefits are huge.

Author: yechaoa

Advanced operation

This is just some general operations, but let’s look at some more advanced operations.

1. Resources. Arsc resource confusion

Resource obfuscating is shortening an otherwise lengthy resource path, such as changing res/drawable/wechat to R/D/A. Open source tool AndResGuard.

2. Remove useless tripartite libraries

Unused after introduction or not removed after feature removal.

3. Tripartite library integration with repeated functions

Glide and Picasso, for example, are both photo libraries. Keep either one.

4.ReDex

Dex files are packaged, and Redex is facebook’s open-source subcontracting optimization. For example: ReDex.

5. So dynamic loading

The so file has been reduced before, but the proportion of SO files may still be relatively large, so files can be dynamically delivered except for the first startup. That is, the idea of plug-in, loading on demand, but while the benefits are great, the risks are also great, there are many cases to consider, such as the download time, network environment, thread process, loading failure whether there is a downgrade strategy and so on.

See Facebook’s open source SoLoader.

6. The plugin

Load on demand, the greater the return, the greater the risk. Same as above.

Extreme operating

So if I want to do the best, what else can I do? Ok, go ahead.

1. Native switch to H5 or small program and other programs

Some features may be too heavy to do native, such as various promotional activities, which need to load various large pictures. Native features are both heavy and not dynamic enough, so H5 is a good alternative. But if you don’t already support H5 or applets, adding this capability might actually increase the package size for comparison.

2. Cut function

Some functions may be beautiful, but the revenue is not big after the launch. Do you need to rethink the value point? It is better to find the data and fight with the product.

3. Modify the source code of the tripartite library and eliminate unnecessary code

For example, a full-featured tripartite library utils was introduced, but only a few were actually used. Extracting source code also reduces package size and compilation time for network downloads.

The drawback is that the upgrade cost is high.

4. Picture networking

That is, upload the picture to the server and reduce the package volume through dynamic download. The disadvantage is that the first loading depends on the network environment, and the loading speed and traffic need to be balanced. Images can be preloaded, but traffic consumption is unavoidable. If you care about traffic metrics, you need to weigh them.

5.DebugItem

The DebugItem contains two main types of information:

  • Debug information. The parameter variables of the function and all local variables.
  • Troubleshooting information. Mapping of all instruction set line numbers to source file line numbers.

Remove debugging information and line number information. Not recommended if it is not extreme. You can refer to this article alipay App construction optimization analysis: Android package size extreme compression.

6. R Field inline

Inline R fields can solve the problem of too many R fields causing MultiDex 65536, and this step can have an obvious effect on slimming code.

Meituan code snippet:

ctBehaviors.each { CtBehavior ctBehavior ->
    if(! ctBehavior.isEmpty()) {try {
            ctBehavior.instrument(new ExprEditor() {
                @Override
                public void edit(FieldAccess f) {
                    try {
                        def fieldClassName = JavassistUtils.getClassNameFromCtClass(f.getCtClass())
                        if (shouldInlineRField(className, fieldClassName) && f.isReader()) {
                            def temp = fieldClassName.substring(fieldClassName.indexOf(ANDROID_RESOURCE_R_FLAG) + ANDROID_RESOURCE_R_FLAG.length())
                            def fieldName = f.fieldName
                            def key = "${temp}.${fieldName}"

                            if (resourceSymbols.containsKey(key)) {
                                Object obj = resourceSymbols.get(key)
                                try {
                                    if (obj instanceof Integer) {
                                        int value = ((Integer) obj).intValue()
                                        f.replace("\$_=${value};")}else if (obj instanceof Integer[]) {
                                        def obj2 = ((Integer[]) obj)
                                        StringBuilder stringBuilder = new StringBuilder()
                                        for (int index = 0; index < obj2.length; ++index) {
                                            stringBuilder.append(obj2[index].intValue())
                                            if(index ! = obj2.length -1) {
                                                stringBuilder.append(",")
                                            }
                                        }
                                        f.replace("\$_ = new int[]{${stringBuilder.toString()}};")}else {
                                        throw new GradleException("Unknown ResourceSymbols Type!")}}catch (NotFoundException e) {
                                    throw new GradleException(e.message)
                                } catch (CannotCompileException e) {
                                    throw new GradleException(e.message)
                                }
                            } else {
                                throw new GradleException("******** InlineRFieldTask unprocessed ${className}, ${fieldClassName}, ${f.fieldName}, ${key}")}}}catch (NotFoundException e) {
                    }
                }
            })
        } catch (CannotCompileException e) {
        }
    }
}
Copy the code

You can also refer to the Byte open source shrink-R-Plugin and the Didi open source Booster.

7. Image shaders

For the processing of different colors of the same image, tint can be used. For example, the original returned icon was black, and now another page is going to use white, so two images are not needed, but tint can be changed to white.

		<ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_back_black"
                android:tint="@android:color/white" />
Copy the code

8. Reduce the use of ENUM

Each reduction in ENUM reduces the size by approximately 1.0 to 1.4 KB.

Package volume monitoring

Package volume monitoring should be a part of the release process. It is better to be platform-based and process-based, otherwise it will be difficult to continue, and the volume of a few versions of the package will increase again.

General idea: The package size of the current version is compared with that of the previous version. If the package size exceeds 200KB, approval is required. Interim approval needs to give the follow-up optimization plan and so on.

Reference documentation

  • Improve your code with lint checks
  • Shrink, obfuscate, and optimize your app
  • Android App package slimming optimization practice
  • ReDex
  • SoLoader
  • Alipay App construction optimization analysis: Android package size extreme compression
  • AndResGuard
  • Explore Android package volume optimization in depth
  • Android development master class volume optimization