Nine points like, a point of dignity, give up you, also let go of their own, I wish you well, after many years do not remember I love you deeply.

Pour past

The work is neither long nor short, and the depth has not been considered before, but it is perfect when it is written.

Now we are in an awkward position, just in time. I put my bags on my back and continue to move forward.

The 5 GIGABytes are now in the spotlight, and Android has had its fair share of controversies in recent years.

Everyone wants something different, so try your best.

How to reduce the size of Apk, has been in a wait-and-see state, too lazy to toss, in fact, still not a batch of Low.

Today, let’s do something about it

Welcome to correct ~

Learn now, sell now

A brain map, see all

The exquisite pagoda town everything

First, attach a current Apk size map:

The original package size was 10 MB without any processing, and after hardening it was nearly 11 MB.

Take this as an example, let’s see how much essence remains after our exquisite pagoda is upgraded.

1 Tier Town Demon (reduced 4.1MB)

Coming to the first layer, let’s start with a simple analysis of what makes Apk packages so “huge”?

As you can see, lib is compatible with the full CPU architecture. Imagine a future with short video, live streaming, map navigation, and so on (without the bar).

As you can see from the above picture, 89 languages are supported by default. The current application is not internationalized at the moment. This application can be directly set to be compatible with Chinese.

Source code, resource files, lib.

Let’s pick a soft target first.

1.1 Setting the Supported Language (reduce 0.2MB)

About this, the individual feels although occupies relatively small, but uses what to play what, does not use the direct kill.

Build. Gradle:

defaultConfig { ... // Only Chinese resConfigs "zh"}Copy the code

This part is mainly determined according to the needs of the existing project, the central idea is only one, which is compatible with where set where the national language, other directly ignored.

Pack it when you’re done and see if anything changes.

As you can clearly see from the figure above, the package size was reduced by 0.2MB by setting only supported national languages. Then let’s look at what happens to strings in the resource mapping file.

The default language is set to Chinese, and the application only supports Chinese, a lot of things less, cool very ~

1.2 Setting supported CPU Architecture Types (reduced by 1.5 MB)

Why is lib compatible with so many CPU architecture types?

Just in time for a refresher on this, let’s take a look at Google’s explanation:

Different Android phones use different cpus, and different cpus support different instruction sets. Each combination of CPU and instruction set has its own application binary interface, the ABI. The ABI defines very precisely how the application’s machine code interacts with the system at run time. You must specify an ABI for each CPU architecture that your application will use.

It seems that Google Store now supports the corresponding architecture pattern to distribute the corresponding Apk package, which is cool because each package only needs to be compatible with one. But, ummm.

At present, there are few real So libraries used in the project, and all compatibility is too wasteful. It is said that ARM is universal, So it is the same as the language setting, only ARM can be supported.

defaultConfig { ... NDK {// set the supported SO library framework abiFilters "armeabi"}}Copy the code

After the package runs, continue to view the current package size:

This has always been a worry. The So library alone occupied a large part of the space in the previous project, which was very wet and painful.

1.3 Enabling Compression and Obfuscation (reduce 2.4 MB)

When using Android Gradle 3.4.0 or higher, the R8 compiler is enabled by default for compression, obliquation, and optimization. The main features and functions are as follows:

  • Code optimization: Detect and safely remove unused classes, fields, methods, and properties;
  • Resource compression: Removing unused resources from an application. This process involves removing unused resource files from library dependencies. This is often used in conjunction with code compression;
  • Obfuscation: Shorten class and member names to reduce the Dex file size;
  • Optimization: Review and rewrite the code to further reduce the Dex file size. For example, if R8 detects that the ELSE {} branch of a given if/else statement was never used, R8 removes the code for the else {} branch.

Here are a few things to note:

  • Compression, obfuscation, and code optimization are not enabled by default. This function takes a long time to compile in Debug mode.

Here’s a lesson about obfuscating files.

What is the significance of the confusion? (Introducing an official explanation)

  • The purpose of obfuscation is to reduce the size of an application by shortening the names of its classes, methods, and fields

Confusion effect (from official) :

androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
    androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
        android.content.Context mContext -> a
        int mListItemLayout -> O
        int mViewSpacingRight -> l
        android.widget.Button mButtonNeutral -> w
        int mMultiChoiceItemLayout -> M
        boolean mShowTitle -> P
        int mViewSpacingLeft -> j
        int mButtonPanelSideLayout -> K
Copy the code

Confusion should be noted:

  • The four components of Android should not be confused;
  • Reflection, annotations, and enumerations should not be confused;
  • Methods called by JS and Native should not be confused;
  • The base Bean class and the serialized entity class should not be confused;
  • Custom controls should not be confused;
  • Resource files should not be obfuscated (of course, there are slutty operations);

Common confusion rules (syntax) are then listed:

  • Keep a class -keep public class com.hlq.love
  • -keep class com.hlq.** {*; }
  • Do not display the specified class warning dontwarn com.hlq.**

Specific rules can be found in the official manual at the end of this article.

Next follow the official website to practice a wave of ~

BuildTypes {release {// Enable shrinkResources true // Enable obfuscation minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'Proguard -rules.pro' signingConfig signingConfigs.config} debug {// Disable resource compression with obfuscation operation shrinkResources false minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.config } }Copy the code

Note that resource compression and obliquation must be disabled in Debug mode. Otherwise, compilation time will be increased. You can enable this function when releasing official packages.

Attached here is the obfuscation file used by the current project, partially modified based on the obfuscation file provided by Dafen:

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # confuse basic instruction # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # code mixed compression ratio, Optimizationpasses 5 # OptimizationPasses 5 # optimizationPasses 5 # Blend to class called lowercase - dontusemixedcaseclassnames # specified not to ignore the public library classes - dontskipnonpubliclibraryclasses # this sentence can make us generate mapping file # after the confusion of the project Contains the name of the class - > confusion after the mapping relationship between the name of the class - verbose # specified not to ignore the class members of the public library - dontskipnonpubliclibraryclassmembers # don't do the divverify, preverify is one of the four steps of proguard, Android does not require PreVerify, and removing this step can speed up obfuscation. - DontPreVerify # Preserve annotations without obfuscating - KeepAttributes *Annotation*,InnerClasses # Avoid obfuscating generics - KeepAttributes Signature # Throw an exception when keep code line number - keepattributes SourceFile, LineNumberTable # specified confusion is to use an algorithm, the parameter is a filter behind the # this filter is recommended by the Google algorithm, generally do not change - optimizations. code/simplification/cast,! field/*,! Class/done / * # ignore the warning - ignorewarnings # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # the public portion of the need to keep # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # to retain the four major components, we use -keep public class * extends Android.app. Activity -keep public class * extends android.app.Appliction -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public class * extends android.view.View -keep public class com.android.vending.licensing.ILicensingService # -keep class android.support.** {*; ** -keep public class * extends Android.support.v7.** -keep Public class * extends android. Support. The annotation. * * # retain R the following resources - keep class * * * R ${*; } # retain local native method is not be confused - keepclasseswithmembernames class * {native < the methods >; } # Keep the method arguments in the Activity as view methods, Keepclassmembers class * extends Android.app. Activity{public void *(android.view.view); {public static **[] values();} # keepClassMembers enum * {public static **[] values(); public static ** valueOf(java.lang.String); } # public class extends android.view.View{*** get*(); void set*(***); public <init>(android.content.Context); public <init>(android.content.Context, android.util.AttributeSet); public <init>(android.content.Context, android.util.AttributeSet, int); } # keep class * implements android.os.Parcelable {public static final android.os.Parcelable$Creator *; } # keep Serializable classes from being confused. keepClassmembers class * implements java.io.Serializable {static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; ! static ! transient <fields>; ! private <fields>; ! private <methods>; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); Keepclassmembers class * {void *(**On*Event); keepclassMembers class * {void *(**On*Event); void *(**On*Listener); } # webView handling, project did not use to the webView can be ignored - keepclassmembers class FQCN. Of the javascript. Interface. For. WebView {public *; } -keepclassmembers class * extends android.webkit.webViewClient { public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); public boolean *(android.webkit.WebView, java.lang.String); } -keepclassmembers class * extends android.webkit.webViewClient { public void *(android.webkit.webView, jav.lang.String); } # remove the Log class to print all levels of the Log code, the official package can be used as a ban Log. This can be used to disable log printing # remember not to add -dontoptimize to proguard-Android. TXT for this to work # Another implementation is to use the buildconfig.debug variable -assumenosideeffects class android.util.Log { public static int v(...) ; public static int i(...) ; public static int w(...) ; public static int d(...) ; public static int e(...) ; } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # processing project in our part of the # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -- -- -- -- -- -- -- -- -- -- - processing entity class -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- # at the time of development we can put all the entity classes in a package, so we have to do is write a confusion - keep public class entity class. {* * *. } # Js - keepClassMembers {public *; } - JavascriptInterface keepattributes * * # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # of disposal of the third party dependent libraries # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # here according to the actual project is used in the official to confused to find the corresponding code blockCopy the code

Then we continue packing to see the obfuscation, the size of the Apk after resource compression, and some of the changes:

Dex reduced from 3 to 2. The files not kept have been confused, while the files of Keep are still standing proudly, as shown below:

The confusion operation increases the difficulty of cracking to a certain extent. Of course, there is no absolute security.

R8 creates a mapping.txt file each time it runs, which lists the mappings between the confused class, method, and field names and the original names. This mapping file also contains information for mapping line numbers back to the original source file line numbers. R8 saves this file in /build/outputs/mapping//.

The online version must be obfuscated, so what should we do about exceptions reported in the online version? After all, the key content becomes meaningless characters, and the meaning of authentication is gone.

ITerm 2 open:

Click on the ReTrace:

The steps are as follows:

  • Importing a Mapping File
  • Copy and paste the Obfuscated error log to Obfuscated Stack Trace
  • Click ReTrace in the bottom right corner!

1.4 Enable Zipalign optimization

This one I see very wet meng force, estimated only the chicken is running smoothly. A brief excerpt from the official explanation:

Zipalign is an archive alignment tool that makes important optimizations for Android application files. The goal is to ensure that the beginning of all uncompressed data performs a specific alignment with respect to the beginning of the file. Specifically, it aligns all uncompressed data in APK (such as images or raw files) on 4-byte boundaries. This way, all parts can be accessed directly using mmap(), even if they contain binary data with alignment restrictions. This has the advantage of reducing the amount of RAM consumed when running your application.

How to use it? It is easy ~ very

BuildTypes {release {// enable Zipalign optimization zipAlignEnabled true} debug {zipAlignEnabled false}}Copy the code

Take a look at the results:

It doesn’t seem to work. I’ll add it if I can.

2 layer Town Fairy (reduced by 1.5MB)

At the second layer, let’s open the image section of the resource mapping file again:

In fact, for the image, it is really a pain in the operation, but fortunately, some simple small background, small effects, now most of the direct use of shape, selector and so on, how much also avoid the introduction of some images.

For picture optimization, it is mainly divided into the following points:

  • Optimization of the graph -SVG
  • Optimization of sets – application of Thit shaders
  • The use of webp

2.1 Optimization of the graph – SVG

What is a set?

For example, an Icon in an application, generally speaking, THE UI will provide us with N sets of images, so that we can adapt to different resolutions of memory, the general catalog is as follows:

For example, it is painful to copy and rename the following ICONS one by one:

This is where SVG comes in handy.

Scalable Vector Graphics (English: Scalable Vector Graphics (SVG) is a Graphics format based on extensible Markup Language (XML) for describing two-dimensional Vector Graphics. SVG, developed by the W3C, is an open standard.

Advantage of SVG:

  • Save space and memory

SVG disadvantage:

  • Transparency and gradients are not supported

Ummm, need to specify a point, Android 6.0 + support, 6.0 below need to do compatibility processing. But now there is no need to be compatible with that low version, right?

How do I create an SVG image in Android Studio? As follows:

The following page is displayed. On this page, you can choose to directly import the Android built-in Icon library image or manually load the SVG or PSD format.

Put an operation diagram to save trouble:

It’s also easy to use:

    <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/iv_toolbar_back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:scaleType="fitXY"
            app:srcCompat="@drawable/ic_arrow_back" />
Copy the code

Don’t forget to set this in build:

defaultConfig { ... / / forced Gradle does not automatically generated at compile time compatible with the low version of the bitmap resources vectorDrawables. UseSupportLibrary = true / / generate specified types of image resources vectorDrawables.generatedDensities = ['xhdpi', 'xxhdpi', 'xxxhdpi'] }Copy the code

It is recommended to download SVG directly and import SVG, which is really cool.

I would like to recommend a conscience enterprise.

  • www.iconfont.cn/

After a lot of trouble, Ali downloaded SVG, and then Android Studio imported SVG

2.2 Optimization of sets – Tint shader

For example, the same picture is displayed differently in different states, such as success green, failure red and so on. As is customary, it must require at least a picture for each state, otherwise what am I going to do?

But there is a real problem, that is, the image is the same, but the color has changed, assuming five states, we need to introduce at least five images, so, can we only need one image, for different states, we render different colors?

Sure, that’s Tint shaders for today. With it, the most concise point, at least can help me leave a lot of “useless” pictures, greatly save a lot of space, our Apk more “dry”.

Take another common example in our project, the Tab bar on the home page, as shown below:

Tab switch, font color, picture color, this is not strange. Give me at least eight images, four by default, four selected, and then set it by selector, you can’t do it without giving it to me. Right? That was the most realistic thought I had before, well, I felt dei.

Let’s review the previous low notation:

Step 1: After providing at least eight graphs, set the icon to reference the selector file:

<? The XML version = "1.0" encoding = "utf-8"? > <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/nav_home" android:icon="@drawable/selector_menu_home" android:title="@string/nav_home" /> ... </menu>Copy the code

Step 2: Define Selector:

<? The XML version = "1.0" encoding = "utf-8"? > <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_checked="true" android:drawable="@drawable/ic_tab_home_sel"/> <item android:drawable="@drawable/ic_tab_home"/> </selector>Copy the code

This writing is no problem, but there are many pictures for no reason, in today’s view, I must not tolerate, only four pictures today, dry him ~

Here is a joke, because the previous bottom navigation using the BottomNavigationView, thrashing out for a long time to step thrashing out, the middle countless times want to give up. But turn head to think, I somehow also follow my chicken big of, besides smoke when also want to communicate with text elder brother. Emma, not easy, let me have a cigarette, ummm, no cigarettes 😅😅😅.

Let’s take a look at the image, after all the hard work I’ve done.

The first is the layout:

<com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/nav_bottom_menu" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorWhite" App :itemBackground="@null" app:itemIconTint="@color/tint_selector_menu_color app:itemTextColor="@color/tint_selector_menu_color" app:labelVisibilityMode="labeled" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:menu="@menu/nav_bottom_menu" />Copy the code

Write the render color picker:

<? The XML version = "1.0" encoding = "utf-8"? > <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@color/comm_app_color" android:state_checked="true" /> <item android:color="@color/color_tab_def" /> </selector>Copy the code

Finally, modify the icon in our menu file and use the default figure directly:

2.3 Image upgrade – WebP

Introducing official description:

WebP is an image file format developed by Google that provides lossy compression (such as JPEG) and supports transparency (such as PNG), although it provides better compression than JPEG or PNG. Android 4.0 (API level 14) and later support lossy WebP images, While Android 4.3 (API level 18) and later support lossless and transparent WebP images.

Note: Since only Android 4.3 and later support lossless and transparent WebP images, your project’s declared minSdkVersion must be 18 or higher to create lossless or transparent WebP images using Android Studio.

The operation steps are as follows:

Click on the image to Convert and select Convert to WebP…

Use the default configuration:

Preview results:

Down from over 200 KB to over 20 KB, it’s really cool to use WebP if you have a lot of these images in your project

Of course, about the conversion can be single, many pieces, look at the individual mood.

Through the worst of it, let’s pack together and see how much we’ve streamlined through two layers of filtering?

Apk size decreased by 1.5 MB and RES ratio decreased from 28 % to 15.5 %. In fact, there is still a lot of space to optimize, but lazy cancer upper body, hypocritical began to work.

3 layer town all Things (steal a lazy, sleepy, reduced 0.4 MB)

After the first two layers of play strange experience, we are finally going to see the big Boss. Come on, the space is too long, grey often thanks to be able to see here, than a heart ~

3.1 AndResGuard — wechat Resource Compression Application (minus 0.4 MB)

Before officially playing wechat resource compression, we first review the confusion before, to put it bluntly, confusion not only optimized the code, but also replaced some of the key information with meaningless markers, which further deepened the difficulty of decompression cracking, just deepened. However, our layout and pictures are still naked, as shown below:

For some safety requirements are relatively high or there is so a lost pursuit of small partners, is not naked to you, how to do?

AndResGuard comes on the scene.

Step 1: Add dependencies to the project root directory build

dependencies { ... Classpath 'com. Tencent. Mm: AndResGuard - gradle - plugin: 1.2.17'}Copy the code

Create a new weike-resGuard. gradle file in your app directory.

apply plugin: 'AndResGuard' andResGuard { // mappingFile = file("./resource_mapping.txt") mappingFile = null use7zip = true useSign = KeepRoot = false // This will confuse the arSC name column with the same name. FixedResName = "arg" // This will merge all resources with the same hash value, MergeDuplicatedRes = true whiteList = [// App Logo: "r.map.ic_launcher ", "R.mipmap.ic_launcher_foreground", "R.mipmap.ic_launcher_round", // for fabric "R.string.com.crashlytics.*", // for google-services "R.string.google_app_id", "R.string.gcm_defaultSenderId", "R.string.default_web_client_id", "R.string.ga_trackingId", "R.string.firebase_database_url", "R.string.google_api_key", "R.string.google_crash_reporting_api_key" ] compressFilePattern = [ "*.png", "*.jpg", "*.jpeg", "*.gif", = 'com] sevenzip {an artifact. Tencent. Mm: sevenzip: 1.2.17' / / path = "/ usr/local/bin / 7 za"} / * * * optional: FinalApkBackupPath = "${project.rootDir}/ finalApkBackupPath = "${project.rootDir}/final.apk" // / digestalg = "SHA-256"}Copy the code

Step 3: Add dependencies at the end of build under APP

apply from: 'wechat-ResGuard.gradle'
Copy the code

Step 4: Pack

Finally, let’s see if the corresponding resources in the package are confused.

Apk decreased by 0.4 MB, resources were confused ~

Look at the double 11 data, really rich…

3.2 check the Link

Analyze select Run Inspection by Name…

Enter unused re and select unused resources:

Use the default:

Modify as prompted:

Sleepy, don’t change, another day to say.

Unused Resources (not recommended, unless you’re as thick-skinned as I am)

Physically delete unused resource files.

Make sure you click the one in the middle, or you delete it

One by one, and then start deleting ~

Select the one you want to Remove, and simply Remove it

Take a look at your bag.

Ummm, I don’t think I did anything. O. Whoosh…

conclusion

After several days of ink, I finally took a small step.

Refueling ah ~

The resources

  1. ABI management
  2. Localize the interface using the Translations Editor
  3. Compress, obfuscate, and optimize your application
  4. ProGuard manual
  5. zipalign
  6. Add multi-density vector graphics
  7. Android support library 23.2
  8. Create WebP images
  9. ImageView
  10. AndResGuard