[Front-end tutorial] Douyin packet size optimization-resource optimization

1 Overview

With the rapid iteration of the business, the package size of Douyin Android has exploded. The package size directly affects factors such as download conversion rate, promotion cost, running memory, and installation time, so slimming down the apk is a necessary and very profitable thing. The apk is mainly composed of dex, resource, asserts, native libraries and meta-data. For each part, the package size can be optimized specifically.
After a period of hard work on Douyin Android, the optimization of packet size has achieved staged results. It is still under continuous optimization.

- Before optimization Optimized percentage
Vibrato 73MB 61.5MB 15.7%
Vibrato lite 10MB 4.9MB 51%

Among them, resources account for a large proportion of apk package volume, and optimizing resources is a very important part of package size optimization. In the pursuit of the ultimate principle, this article will elaborate on the optimization measures of the Douyin Android side for resources.

2. Picture compression

2.1 Image compression principle

Without compression, the picture size calculation formula: picture size = length x width x picture bit depth. An original image (1920x1080), if each pixel represents 32bit (RGBA), then the storage size required for the image is 1920x1080x4 = 8294400Byte, about 8M, such a large picture is unacceptable. Therefore, the pictures we use are all compressed.
Image compression uses the principles of spatial redundancy and visual redundancy:

  • Spatial redundancy utilizes the spatial coherence that exists between the colors of the sampling points on the image. The data that should be stored one pixel at a time is merged and compressed for storage, and decompressed and restored when loaded. Usually lossless compression utilizes the principle of spatial redundancy.
  • Visual redundancy refers to the fact that the human visual system is limited by physiological characteristics, and the attention to the image field is non-uniform, and people do not feel subtle color differences. For example, the general resolution of human vision is 26 gray levels, while the general image quantization uses 28 gray levels, that is, there is visual redundancy. Usually lossy compression utilizes the principle of human visual redundancy, erasing redundant information for human eyes.

2.2 Advantages

The Douyin Android R & D team developed the Gradle plugin McImage, which hooks resources during compilation and uses the open source algorithm pngquant / guetzli for compression, which supports webp compression. Compared with some known solutions such as tinypng, there are the following advantages:

  • McImage now supports webp compression, the compression ratio is higher than tinypng, but webp on Android needs to be compatible, which will be described in detail below;
  • Tinypng is not open source, and each account can only compress 500 sheets per month for free; the compression algorithm used by McImage is based on open source algorithms;
  • McImage can not only compress the pictures in the module, but also compress the pictures in the jar and aar;
  • McImage supports the expansion of compression algorithms, and it is convenient to expand when a better compression algorithm is selected;
  • Compared with other solutions in the industry, McImage can also support compression of webp images containing transparency, and is compatible with aapt2 resource hooks.

2.3 Benefit

McImage supports two optimization methods, which cannot be used at the same time:

  • Compress, pngquant compressed png pictures, guetzli compressed jpg pictures;
  • ConvertWebp, webp compressed png \ png pictures.

The compression ratio of webp is higher than pngquant and guetzli, so now the conversion method of ConvertWebp is more recommended.

McImage has also been applied to the optimization of image compression for multiple products of ByteDance. The benefits are as follows:

description income
Vibrato-Compress 9.5MB
Douyin-ConvertWebp 11.6MB
Volcano-ConvertWebp 3.6MB
Vigo-ConvertWebp 4MB
Vigo aab-Compress 1.2MB
vigo aab-ConvertWebp 3.2MB
Multi-flash-ConvertWebp 3.5MB

2.4 Other

In addition to compressing and optimizing images, McImage also provides the following functions:

  1. Large picture detection. In the app / build / mcimage_result directory, the mcimage_log.txt log file will be generated. In addition to the log of the conversion result, large pixel images and large volume images are also output at the end. The threshold can be set in McImageConfig, which is convenient for the resumption of large images. Optimize the package size; also support the detection of the compilation stage, detect the big picture directly block compilation, you can find the big picture submission in time;
  2. The compression algorithm is easy to expand. If you want to access other compression algorithms, you only need to inherit AbstractTask and implement the work method in the ITask interface;
  3. Support multi-thread compression. Put the execution of all tasks into the thread pool for execution, greatly reducing the execution time of mcimage;
  4. Increased picture cache cache, further shorten packaging time. In the case of multi-threading + image caching, when all the caches are hit, the entire mcimage process is less than 10s; the cache path can be configured;
  5. The compression quality can be configured to meet different compression quality requirements, and cache files will also be saved and hit according to different compression quality;
  6. Scan the image without the transparent channel to the app / build / mcimage_result directory.

3.webp non-intrusive compatibility

3.1 The choice of tinypng and webp

Which compression ratio is higher between tinypng and webp? There is no direct comparison of the compression ratios of the two compression algorithms on the Internet, and a more intuitive comparison is needed, so the following experiment was done:

  1. Scan 1960 pictures in the project and compare them with different algorithms:
description size
Original image 13463.07KB
webp compression 4177.18KB
tinypng compression 6732.18KB
  1. Find 490 pictures from the project, create a new demo, compare the size of the packaged apk after compressing the pictures with different algorithms:
description size
Original image APK 9617.53KB
webp compression APK 3924.06KB
tinypng compression APK 5386.80KB

Through the comparison of these two sets of experiments, we can see that the compression ratio of webp is better than tinypng. I used to manually use the webp tool to compress all the pictures in the vibrato project, and the package size was reduced by about 1.6MB. Therefore, the Webp compression algorithm was selected.

3.2 Scheme selection

Webp compression algorithm, compared to pngquant, guetzli, tinypng, webp compression ratio is higher, so webbp compressed image should be a better choice. However, Android devices have compatibility issues with webp support, and they are only fully supported after 4.3. Through the official website, we know that if you want to use webp with transparency directly in the application, minSDK needs to be at least 18.image

The headlines including Douyin and Toutiao are Android apps. Most of the minSDK is 16, which cannot directly use webp pictures, and needs to be compatible with lower versions. Through extensive research, three compatible methods were found:

- advantage Disadvantages
Provide specific API compatibility Simple to implement It is too intrusive and must be loaded with a specific interface or a specific view
LayoutInflater setFactory for compatibility Simple to implement Need to deal with all ImageView and sub-View, and must have a unified Activity, Fregment base class processing
Key method of hooking system during operation Method replacement, can be non-invasive More complicated to implement

3.3 Scheme realization

If you want to achieve non-intrusive compatibility, runtime hooking is a best choice. However, the runtime hook scheme needs to solve the following problems:

  • The selected hook scheme should be stable and reliable;
  • The hook points should be sufficiently converged to ensure that all operations for parsing pictures can meet expectations.

3.3.1 Hook scheme should be stable and reliable

Through the comparison of common Android Java hook solutions such as Xposed, AndFix, Cydia Substrate, and dexposed, dexposed has the characteristics of not requiring root and hooking the system method, and finally chose dexposed:

  • dexposed is relatively stable on Dalvik, only need to hook for the mobile phone version below 4.3, do not need to consider version compatibility issues and system upgrade issues;
  • According to internal data, there are not many users below Douyin 4.3. At the 100,000 level, they account for a few ten thousandths of the total users, and the risk is low.

3.3.2 Hook points must be sufficiently convergent

Through reading the source code, I found that all the pictures were loaded and parsed into Bitmap, and finally the methods in BitmapFactory were called. For example, the ImageView setImageResource()call path as follows:

image.gif

ImageView's setImageResource process, Bitmap creation is achieved through BitmapFactory. The View of the setBackgroundResource(int resid)source code as follows:

image.gif

Looking at all the APIs that load pictures, you will go through the process of Resources calling getDrawable. Will call the related methods of Drawable, and then use BitmapFactory to parse different resource types (File \ ByteArray \ Stream \ FileDescriptory) into Bitmap. It can be inferred that BitmapFactory is the unified interface of the Android system loaded into Bitmap through different resource types. This can also be seen from the class annotation of BitmapFactory:

image

BitmapFactory annotation

Since the process of loading and parsing Bitmap by the system has been sufficiently converged, it is implemented through BitmapFactory, so BitmapFactory is a very good hook.

With a stable Hook scheme and a sufficiently convergent Hook point, the implementation of the scheme is within reach. Use dexposed to replace the key methods in BitmapFactory.

4. Multi DPI optimization

In order to adapt to devices of different resolutions or modes, Android has designed resource paths for developers with multiple configurations of the same resource. When the app obtains image resources through resource, it automatically loads the adapted resources according to the device configuration, but these configurations accompany The problem is that high-resolution devices contain low-resolution useless pictures or low-resolution devices contain high-resolution useless pictures.

In general, for the domestic application market, in order to reduce the package size, App will choose the set with the highest market share dpi (google recommends xxhdpi) compatible with all devices. For apps in overseas application markets, most of them will be packaged and uploaded to Google Play through AppBundle, which can enjoy the function of dynamically distributing dpi. Mobile phones with different resolutions can download image resources with different dpi, so we need to provide multiple sets of dpi to meet all devices. . In the project, some of our pictures have only one set of dpi, and some have multiple sets of dpi. For the above two scenarios, we combined resources and copied resources when packaging, which reduced the package size.

4.1 DPI replication (bundle packaging)

In domestic projects, in order to reduce the occupancy of pictures, dpi with high market occupancy rate is generally adapted, for example, only xxhdpi resolution pictures are retained. This leads to two problems. One is that there are more and more 2k resolution mobile phones on the market. If the mainstream mobile phone resolution is xxxhdpi in the future, the cost of modifying thousands of pictures in the project will be very high. Another problem is that many of the company's overseas products are packaged and uploaded to Google Play through AppBundle, which can deliver different dpi resources to different device users. However, there are only xxhdpi in the project, and xxhdpi images are still distributed. You cannot reduce the package size by lowering the dpi. In Brazil, 80% of our users use xhdpi and hdpi mobile phones. Xxhdpi images take up twice as much as hdpi, and this part of the revenue is quite high.

Therefore, we reduce the high-resolution pictures to low resolution by compressing the resolution. The project business only stores the highest dpi pictures, and copies and filters as needed when packaging. We hooked up the task of image compression. Before the image compression, we obtained all PNG images including dependent libraries, and used Graphics2D to reduce the image resolution and placed it in the corresponding resolution folder. Then execute the image compression task to prevent some images from increasing in size after resampling.

We only scale the resolution of the picture and do not reduce the picture sampling rate, so there is no difference in the display effect. How much resolution should be adjusted to different dpi, we have made a table according to the definition of Google:

image.gif

We copy a xxhdpi default logo to all dpi, the process is as follows, there is no corresponding picture in the xhdpi and mdpi folders, copy; there is a corresponding picture in hdpi, skip; there is no corresponding picture in xxxhdpi, but in order to avoid reducing the accuracy of the picture , Can't copy to higher resolution folder, skip.

image

The final income is shown in the figure. When the company's domestic and overseas product TikTok R & D team uses this solution to optimize, ldpi reduces the package size by 2.5M compared to xxhdpi. At the same time, when loading pictures on a low-resolution mobile phone, the corresponding dpi picture resources are directly loaded, and it is no longer necessary to zoom the high-resolution pictures, which improves performance.

image

When copying, you need to pay attention to these issues: In order to process all the pictures including the dependent library, the copy is performed during the resource merge stage, which will cause a large number of image resources under many paths in the .cache directory, so this plugin is on CI Open to avoid adding a lot of new pictures locally and submit them to the code warehouse. At the same time, because multiple pictures are copied in the .cache, multiple dpi deduplication is required in the assemble packaging process. There will be concurrent scenarios on CI. Simultaneous copying and compression will cause a.png and a.webp to exist in the .cache directory at the same time, and there will be a Duplicated error. Therefore, you need to scan and delete the .png file with the same name.

4.2 Multi-DPI deduplication (assemble and package)

For the normal packaging mode (direct output apk, such as vibrato package), we can choose to keep only one high-resolution image, so that high-resolution devices can get suitable pictures, low-resolution devices can be obtained through Resource It will automatically zoom when it is still able to ensure a reasonable running memory.

Multi-dpi images can be deduplicated by resConfig that comes with Android, but this configuration only deduplicates the qualifier of the resource. For example, pixel density and screen size will not be deduplicated at the same time. Vibrato uses the method based on AndResguard to deduplicate the drawable , You can define the priority and scope of different configurations. According to the optimized configuration, make sure to reserve a resource. The optimized method is as shown in the following figure (gray data means it will be deleted):

image

5. Merging of duplicate resources

With the iteration of the project, it is inevitable that the same resources will be added to the resource path repeatedly. For such files, manual processing is definitely not feasible, and it can be automatically removed during the packaging phase.

Douyin chooses to analyze all resources in the AndResguard stage, keep a copy of the same md5 resource file, delete the remaining duplicate files, and then when AndResguard writes the arsc file, the resource path corresponding to the deleted resource file points to the unique A reserved resource document. The optimization method is as follows:

image

The following figure is the optimization result of the Douyin 511 version accessing the multi-dpi deduplication and duplicate resource merge function:

image

6.shrinkResource strict mode

6.1 Background

With the development iteration of the project, we will have many resources that are no longer used, but still exist in the project. Although we can use the company's open source bytecode plug-in development platform ByteX development plug-in to scan out some useless resources before ProGuard, but because this step has not been deleted by useless code, the scan results are not complete. ShrinkResources is a method provided by Google to optimize such useless resources. It runs after Proguard and can mark all useless resources and optimize them.

6.2 Benefit

Douyin Android turned on shrinkResources strict mode, the number of shrink resources was 600+, and the profit size was 0.57MB.

6.3 Access method

shrinkResources is an official tool provided by Google, so please refer to the documentation on Google Developer for the detailed access method.

6.4 ShrinkResources principle

By default, Resource shrink is in safe mode, that is, it will help us identify val name = String.format("img_%1d", angle + 1)``val res = resources.getIdentifier(name, "drawable", packageName)code similar to this mode, so as to ensure that we can safely return resources when we call resource files in reflection. From the source point of view, Resource shrink will help us identify the following five situations:

image

Resource shrink uses one of the most stupid but safest methods to obtain matching prefix / suffix strings, that is, consider all strings in the application as possible prefix / suffix matching strings.

image

Therefore, in safe mode, resources accidentally matched by a certain string will be retained even if they are not used. With our project, for example, in the com.ss.android.ugc.aweme.utils.PatternUtilsmiddle, we have the following code:

image

In safe mode, all of which resulted in a ttuseless resource in the beginning will not be out of shrink (which is why strict mode opened, ttlive_so many useless resources at the beginning of the cause).

After strict mode is turned on, its function is to forcibly close the character matching process of this paragraph:

image

Of course, this also resulted in the use of our getIdentifier()time is not safe, because strict mode is not going to match any string, so after strict mode is turned on, be sure to strictly check all resources shrink, is there they need Reflecting resources!

6.5 shrinkResources 兼容 Dynamic Feature

AppBundle is a feature that Google has been pushing for in recent years. It allows our apk to generate delivery according to different dimensions, and also provides a dynamic feature delivery method, Dynamic Feature. But if we use shrinkResources after starting the Dynamic Feature, the following error will be prompted:

image

From this, Google officially does not support shrink resource when App Bundle uses Dynamic Feature. On Google Issue Tracker, I found that some people have submitted Issues on this issue, and related Issues. And Google's reply is also simple and rough-planned, but there is no time:

image

But normally speaking, if done well, the Dynamic Feature module of our App Bundle will rarely refer to the resources of the Master. Even if there are, the use of keep.xml can keep this kind of resources. Therefore, in theory, there is not much problem with shrinkResource on the Master module alone and paying attention to the reflection call. Check the shrinkResources configuration under Dynamic Feature is in the Configuring stage

image

Therefore, our idea is not to turn on the shrinkResources switch during the configuration phase, but to insert the Task of shrinkResources when performing resource processing tasks later:

image

In this way, the Task of shrinkResources can be opened under the Dynamic Feature. The whole code is very simple to write and can be completed in less than 50 lines:

image

7. Resource confusion (compatible with aab mode)

The mapping relationship between the resource id and the full path of the resource is recorded in the arsc file, and the app obtains the corresponding resource through the resource id and then through the Resource, so the name confusion for the resource path in the mapping relationship can achieve the effect of reducing the package size.

Douyin enabled WeChat open source AndResguard for resource obfuscation. On the basis of open source, MD5 was added, and multiple DPIs kept only one resource and other optimizations. Since there are many overseas products in the company, you need to go to aab when listing Google Play, so the team made aab compatible with resource confusion-aabResguard ( open source | AabResGuard: AAB resource confusion tool ), which has been open source.

image

8. ARSC slimming

8.1 Background

resources.arsc This file takes up a lot of space in many projects. A common optimization method is to use AndResGuard to obfuscate and reduce the file name and directory length, 7z compression, and if there are overseas products, the language can be dynamically distributed. After we finished these optimizations, because there are many overseas products in the company and the relationship between multiple languages ​​is involved, ARSC is still very large, and we decided to try to optimize further.
After research, we finally optimized three aspects, namely, deleting useless Name, merging duplicate strings in the string pool, and deleting useless copy, and the final benefit was 1.6MB. Prior to this, we also completed the merge of duplicate MD5 files on the basis of AndResGuard, the principle is the same.

8.2 Principle

First post a picture of the arsc structure. The data structure of this binary file is quite complicated. AndResGuard actually only modified a small part of this file. As for more modifications, it is powerless, so we parsed this file for analysis. There are also a lot of instructions on this file format on the Internet, so I won't go into details here. Recommend Lao Luo and Nicholas' blog and aapt2 source code. The code of android-arscblamer and apktool provided by google is also worth a look.

image

The following is a brief description of the modification process with a picture:

image

As shown in the figure, the strings are actually obtained by indexing. All strings are stored in two string pools (single package), one is the global string pool, and the other is the string pool under the package. We only You need to modify the offset value that points to the global string. The binary positions of name and value are shown in the figure below.

image

8.3 Plan

8.3.1 Delete useless Name

AndResGuard also added this feature in July this year, let's take a look at the implementation principle. The string pool corresponding to Name is the package string pool. Since this string pool contains only all Names, we can operate a little bit more violently, first make a backup, then empty the string pool and add a string for replacement , Assigned the value [name_removed].

image

The first step is to determine which names are called by getIdentifier and configured as a whitelist. Traverse the name item, if it is not in the white list, then replace the offset of this name with 0, so that it points to [name_removed]. If name is in the white list, it should not be deleted. We find the string corresponding to this name through the backup string pool, add it to the string pool, and point the offset to the corresponding subscript.

Douyin reduced the packet size by 70k through this optimization.

8.3.2 Merge duplicate strings

The value corresponds to the global string pool. Although the name does not sound like a duplicate value, but after we scan and sort, we find that there are actually a lot of duplicate strings (the AppBundle package will not have this problem). In the vibrato project, There are 1k + duplicate strings in this string pool, and it is necessary to merge these strings.

image

We first traverse all the data, then merge the repeated strings in the string pool, record the modification of the offset, and finally point the reference of the value that needs to be modified to the new offset. This process needs to operate ResValuel and ResTableMap of the arsc data structure to ensure that all string type values ​​can be replaced.

Through this optimization, Douyin reduced the packet size by 30k.

8.3.3 Delete useless copy

In the packaging process, in fact, all the strings saved in strings.xml will not be optimized. As the project gradually becomes larger, some abandoned copy or the copy that is only useful in the next version is introduced into the apk. We are in Proguard After scanning again, 3000+ useless strings were found. In some overseas projects within the company, some copy was translated into the languages ​​of more than 100 countries, occupying a huge space.

image

The method of deletion is similar to the above, all pointing to the offset of the replaced string. As shown in the figure, there may be two different names pointing to the same string. You need to determine whether there are other references to the string to be deleted.

The income of different projects may be different. The internal overseas projects of the company have replaced these useless copywriting, reducing the size of the 1.5M package.

8.4 Implementation

If it is an ordinary assemble package, obtain the arsc file in the ap_ file directly in the ProcessResources process and modify it with our tool.

If it is packaged in AppBundle mode, it is useless to modify ap_, because the final product is the resources.pb file generated by aapt in the proto format, and the modification can only hook the aapt process. The structure of this file is not the same as the arsc file. Fortunately, we can use the officially provided Resources class to parse and generate the pb file and modify it in a similar way.

image

The modification effect is as follows:

image

8.5 Further optimization

The offset array in arsc has room for optimization, and we will try to optimize it in the future. Open the arsc file with a binary editor, you can find that such FF values ​​exist in a large number of files.

image

What caused such a waste of space? We can see the blank box selected in the figure below, each of which represents the offset value where the string is located. There is no value here. Assigning FF FF FF FF as the default offset value wastes 4 bytes of space. Some columns (configuration) may have only a few grids with values. As shown in the drawing of Douyin, there are 4k + pictures and 24 columns. Most configurations have only a few pictures, so 4k 23 4≈380k is wasted. It is roughly estimated that the vibrato can reduce the volume of 1M. (Before compression)

image

As shown in the following figure, Facebook's processing of arsc files, we can extract an id with only one value in a row, and put it into a Resource Type separately, each id has only one value, to avoid the above waste of space. However, this modifies the ID, so the ID in the corresponding code also needs to be modified, which involves reverse xml and dex, which increases the modification cost. Another idea is to modify the aapt source code, without directly changing the arsc flexibility.

image

9. Summary

The above are some attempts and accumulations made by our Douyin Android terminal in terms of packet size optimization to strive for the ultimate.

We have done many optimization measures for package size optimization in other areas: for so optimization, we have done so merge, unified stl version, streamlined export symbol table and so compression, etc .; for code optimization, refine confusion rules, develop bytex The plugin performs optimization measures such as useless code scanning, incess method inlining, getter / setter method inlining, and line number deletion.

In addition to optimization measures, a good packet size monitoring system is the most important tool to prevent packet size degradation. Otherwise, the benefits of packet size optimization measures will not be equal to the increase in packet size brought by rapid business iteration. Douyin ’s Android terminal combined with CI and Cony platforms has developed a set of code integration pre-check system. Each branch increment exceeds the threshold and is not allowed to enter; also developed a tool to monitor the size of the line of business to facilitate the monitoring of each The package size of each business line grows and sets the package size index for each business line.

Service recommendation

Published 0 original articles · liked 0 · visits 337

Guess you like

Origin blog.csdn.net/weixin_47143210/article/details/105659197