Why optimize package volume

  • Download conversion rate: The smaller the installation package, the higher the conversion rate;
  • Promotion cost: channel promotion cost and the unit price pre-installed by the manufacturer
  • App markets: Both the App Store and Google Play have limits on install pack sizes;
  • Application performance:
    • Installation time: file copy, Library decompression, ODEX compilation, signature verification
    • Running memory: Resource resources, Library, and Dex class loads all use a lot of memory
    • ROM space: Insufficient flash memory space, very easy to write to enlarge the situation

Package volume optimization

APK analysis

  1. ApkTool decompile tool was used to analyze APK.
  2. Use the Analyze APK provided after AS 2.2;
  3. Use NimbleDroid to analyze APK performance

Proguard

  • Confusion, the default will be in the project directory app/build/outputs/mapping/release generated under a mapping. TXT file, this is the rule of confusion;
  1. Function:
    • Thin: It detects and removes unused classes, methods, fields, and instructions, redundant code, and deep optimization of bytecode.

    Finally, it changes the names of fields, methods, and classes in the class to short, meaningless names.

    • Security: Increases the difficulty of code being decompiled to ensure the security of the code to some extent.
  2. function
    1. Shrinking: Enabled by default to reduce application size and remove unused classes and members
    -dontshrink disables compressionCopy the code
    1. Optimization: Enabled by default, optimizations are performed at the bytecode level to make your application run faster
    - Dontoptimize Disables optimization. - Optimizationpasses n Specifies the number of times ProGuard has optimized code iterations. On Android, the average is 5Copy the code
    1. Obfuscation: Enabled by default, making decompilation more difficult. Classes and class members are named randomly
    -dontobfuscate close confusionCopy the code
  3. Optimize the details
1) Optimized the use of Gson library. 2) Mark all classes as final. 3) Simplify enumeration types to constants. 4) Vertically merge some classes into the current class structure. 5) Merge some classes horizontally into the current class structure. 6) Remove the write-only field. 7) mark the class as private. 8) Pass field values across methods. 9) Mark some methods as private, static, or final. 10) Remove the synchronized flag from the method. 11) Remove method parameters that are not used.Copy the code
  1. configuration
BuildTypes {release {// 1, enable minifyEnabled true // 2, enable zipAlign to align resources in the installation package by 4 bytes, ZipAlignEnabled True // 3. Remove useless resource files: // When ProGuard removes unwanted code, the resources referenced by the code are also marked as useless, and the system removes them through resource compression. // The resource compressor does not currently remove resources (such as strings, sizes, styles, and colors) defined in the values/ folder. // When enabled, the Android build tool uses ResourceUsageAnalyzer to check which resources are useless. When useless resources are detected, the resource is replaced // with a predefined version. PNG,.9.png, and.xml provide predefined versions of TINY_PNG, TINY_9PNG, and TINY_XML byte arrays. // The compression tool works in safe compression mode by default. You can use strict compression mode to achieve better weight loss. ShrinkResources true // 4, confuse the location of the file, where proguardandroid. TXT is the SDK's default confuse configuration, / / it is located the location of the android SDK/tools/proguard/proguard - android. TXT, / / in addition, proguard - android - optimize. TXT for the SDK also confused the default configuration, // But it turns on the optimization switch by default. Also, we can set android.util.Log to an invalid code in the config obfuscation file, // to remove the code that prints the Log in apK. Proguard-rules.pro is the obfuscating configuration under this module. proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } }Copy the code
  1. Ground rules for confusion
Pro # Add project specific Proguard rules here. # By default, the flags in this file are appended to flags specified # in C:\sdk\android-sdk-windows/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the  proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # code obfuscates compression ratio, Between 0 and 7 - OptimizationPasses 5 # Remove compile-time warnings -ignorewarnings # Do not compress entered class files - Dontshrink # Do not optimize entered class files - Dontoptimize # Do not confuse entered class files #- Dontobfuscate # Do not mix case when blending, Blend to class called lowercase - dontusemixedcaseclassnames # specified not to ignore the public library classes - dontskipnonpubliclibraryclasses # specified not to ignore the class members of the public library - dontskipnonpubliclibraryclassmembers # the confusion in the class method name also confused - # useuniqueclassmembernames optimization allows access and modify the modifier class and a member of the class Preverify is one of the four steps in ProGuard. Android does not require preverify. Removing this step will speed up confusion. -verbose #apk -dump class_files. TXT # Unobfuscated classes and members -printseeds seeds.txt Printmapping mapping. TXT # Avoid confusing generics - KeepAttributes Signature # Google recommendation algorithm -optimizations ! code/simplification/arithmetic,! code/simplification/cast,! field/*,! Class/done / * # avoid confusion Annotation annotations, inner classes, generics, anonymous classes - keepattributes * * an Annotation, InnerClasses, Signature, EnclosingMethod # js call a Java method - JavascriptInterface keepattributes * * # will source file renamed "SourceFile string" - renamesourcefileattribute SourceFile # keep line number - keepattributes SourceFile, LineNumberTable # processing support package - dontnote android. Support. * * - dontwarn android. Support. * * # -keep public class extends android.support.v7.** -keep public class extends Android.support.v7.** -keep public Class * extends android. Support. The annotation. * * # retain R the following resources - keep class * * * R ${*; } < AndroidManifest > < AndroidManifest > < AndroidManifest > < AndroidManifest > < AndroidManifest > < AndroidManifest > < AndroidManifest > < AndroidManifest > < AndroidManifest > -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 test related code - dontnote. Junit framework. * * - dontnote. Junit runner. * * ** -dontwarn android.support.test.** -dontwarn org.junit.** -KeepClassMembers class * extends android.app.Activity{public void *(android.view.View); Keepclassmembers class * {void *(**On*Event); void *(**On*Listener); } # retain local native method is not be confused - keepclasseswithmembernames class * {native < the methods >; Public static **[] values(); public static **[] values(); public static ** valueOf(java.lang.String); } # keep class * implements android.os.Parcelable {public static final;} # keep class * implements android.os.Parcelable {public static final android.os.Parcelable$Creator *; } # Keep keepClassMembers * implements Java.io.Serializable {static final long} # Keep keepClassMembers * implements Java.io serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } # Assume nosideeffects: Delete android.util.Log - AssumenosideEffects class Android.util.Log {public static *** v(...) ; public static *** d(...) ; public static *** i(...) ; public static *** w(...) ; public static *** e(...) ; } # Keep Keep the name of the class and method annotations - Keep, allowobfuscation @ interface. Android support. The annotation. Keep - Keep @android.support.annotation.Keep class * -keepclassmembers class * { @android.support.annotation.Keep *; } #Fragment does not need to be registered in androidmanifest.xml, Need additional protection - keep public class * extends android. Support. The v4. App. The fragments - keep public class * extends android. The app. # fragments 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); Keepclassmembers class * {void *(**On*Event); void *(**On*Listener); }Copy the code
  • Classes in AndroidMainfest are not confused by default, so all classes under the four components and Application subclasses and Framework layer are not confused by default,

And custom views are not confused by default. Therefore, we don’t need to manually add it in proguard-rules.pro;

Optimize the way

Business Sorting:

  • Removing useless or low-value services is always the most effective way to optimize performance.

Development mode upgrade:

  • If all functions can not be removed, it may need to force the change of development mode, more small program, H5 such development mode;

code

  • For most applications, Dex is the largest part of the package volume.
  • Moreover, the number of Dex is also a great challenge to the user’s installation time
  • Let’s see what we can do to reduce this space without cutting functionality.
1. ProGuard
  1. Carefully check the final merged ProGuard profile for overkeep.
    • The final configuration of the ProGuard can be output by: -printConfiguration configuration.txt
  2. Generally, applications keep the four components and some of the View’s methods so that they can be referenced in code and XML layouts
    -keep public class * extends android.app.Activity
    -keep public class * extends android.app.Application
    -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.view.View
    Copy the code
    • In fact, you can confuse the four non-exported components with the View, but you need to do a few things:
      • XML replacement: After code obfuscation, you need to modify both the AndroidManifest and the names referenced in the resource XML.
      • Code substitution: You need to iterate through other code that has already been obfuscated and modify strings defined in variables or method bodies as well.
        • ASM is recommended
        • Ele. me used to open source a component Mess, which can realize the four components and View confusion, for reference;
        • It is important to note that the computed class name cannot appear in the code, which would cause the replacement to fail.
        Public String activityName = "com.sample.testActivity "; StartActivity (new Intent(this, "com.sample.testActivity ")); StartActivity (new Intent(this, "com.sample" + ".testActivity "));Copy the code
    • Android Studio 3.0 has a new Dex compiler, D8, and a new obfuscation tool, R8
      • Optimization effect of D8:
      1) The compilation time of Dex is shorter. 2) the. Dex file is smaller. 3).dex files compiled by D8 have better runtime performance. 4) Processing that includes Java 8 language support.Copy the code
      • Enable android.enableD8 = true in D8: gradle.properties
      • Android Studio 3.1 or later D8 will be used as the default Dex compiler.
      • R8 is an alternative to Proguard’s compression and optimization section,
      • R8 will be the default compiler for Android Studio 3.4 or Android Gradle plugin 3.4.0 or later.
      • Otherwise, gradle.properties is configured to support R8:
      android.enableR8=true
      android.enableR8.libraries=true
      Copy the code
  3. The debugging message or line number is deleted
    • Using the same ProGuard rule to generate a Debug package and Release package, but the size is different, is the difference in DebugItem;
    • DebugItem include:
      • Debugging information. The parameter variables and all local variables of the function.
      • Rectify the problem information. Correspondence between all instruction set line numbers and source file line numbers.
      • Accounting for about 5.5% of the DEX
    • In the ProGuard configuration, the following methods are used to save line numbers: -keepAttributes SourceFile, LineNumberTable
    • Can debugItem be directly removed? Obviously not. If it is removed, then all crash messages reported will have no line number, and all line numbers will become -1, and it will be sprayed and cannot be found.
    • Here are two schemes of Alipay:
      • Solution 1: Take line number search offline
      The corresponding relationship between the line numbers originally stored in the App is extracted and stored on the server in advance. When the crash is reported, the row number table extracted in advance is used for reverse solution of the row number to solve the problem that the crash information has no line number and cannot be located. Main steps are as follows: 1. Modify proGuard: Use ProGuard to delete debugItem (remove -keep lineNumberTable) and dump a temporary dex before deleting the row number table. 2. Modify dexdump: Dump the temporary line number table in dex into a DexpcMapping file (the mapping between the line number of the instruction set and the line number of the source file), and store the dexpcMapping file on the server. 3. Hook the crash handler of the App Runtime to report the line number of the instruction set in the crash to the reverse solution platform. 4. The reverse solution platform generates the correct line number by reporting the line number of the instruction set and preparing the DexpcMapping file in advance.Copy the code
      • Solution 2: Modify the dex file directly
      Keep a small piece of debugItem, let the system find the line number of the instruction set line and the source file line number is the same, so nothing to do, any monitoring report line number is directly changed to the instruction set line number, just modify the dex fileCopy the code
    • Use RxDex (Facebook’s open source compiler ReDex)
      {
          "redex" : {
              "passes" : [
                  "StripDebugInfoPass",
                  "RegAllocPass"
              ]
          },
          "StripDebugInfoPass" : {
              "drop_all_dbg_info" : false,
              "drop_local_variables" : true,
              "drop_line_numbers" : false,
              "drop_src_files" : false,
              "use_whitelist" : false,
              "cls_whitelist" : [],
              "method_whitelist" : [],
              "drop_prologue_end" : true,
              "drop_epilogue_begin" : true,
              "drop_all_dbg_info_if_empty" : true
          },
          "RegAllocPass" : {
              "live_range_splitting": false
          }
      }
      Copy the code
2. Dex subcontracting
  • Redundant information caused by cross-dex calls:
    • For example, Class A and Class B are compiled into different Dex. Since method A calls Method B, the id of method B needs to be added to classes. Dex
    1. Cause method ID to explode:
      • The method ID of each Dex needs to be less than 65536, because the large number of redundant method ids leads to fewer classes that can be put in each Dex, which leads to an increase in the number of Dex finally compiled.
    2. Resulting in information redundancy
  • To further reduce the number of Dex, we want the number of methods for each Dex to be full, i.e. 65536 methods allocated.
  • How to improve the efficiency of Dex information?
    • It is necessary to allocate the classes and methods that have the calling relationship to the same Dex, that is, to reduce the case of cross-dex calls;
    • After analyzing the class calling relationship, ReDex uses the greedy algorithm to calculate the local optimal value. For the specific algorithm, see CrossDexDefMinimizer.
    • The Redex configuration is as follows
    {
        "redex" : {
            "passes" : [
                "StripDebugInfoPass",
                "InterDexPass",
                "RegAllocPass"
            ]
        },
        "StripDebugInfoPass" : {
            "drop_all_dbg_info" : false,
            "drop_local_variables" : true,
            "drop_line_numbers" : false,
            "drop_src_files" : false,
            "use_whitelist" : false,
            "cls_whitelist" : [],
            "method_whitelist" : [],
            "drop_prologue_end" : true,
            "drop_epilogue_begin" : true,
            "drop_all_dbg_info_if_empty" : true
        },
        "InterDexPass" : {
            "minimize_cross_dex_refs": true,
            "minimize_cross_dex_refs_method_ref_weight": 100,
            "minimize_cross_dex_refs_field_ref_weight": 90,
            "minimize_cross_dex_refs_type_ref_weight": 100,
            "minimize_cross_dex_refs_string_ref_weight": 90
        },
        "RegAllocPass" : {
            "live_range_splitting": false
        },
        "string_sort_mode" : "class_order",
        "bytecode_sort_mode" : "class_order"
    }
    Copy the code
3. Dex compression
- Facebook App's classes.dex is just a shell, the real code is under assets. They merge all Dex into the same secondary.dex.jar.xzs file and compress it via XZ. - XZ compression algorithm and 7-zip, the internal use of LZMA algorithm. For Dex format, XZ compression rate can be about 30% higher than Zip. - First startup decompression: When the application starts for the first time, it needs to decompress secondary. Dex.jar. XZS. According to the configuration information above, there should be a total of 11 dex. -Odex File generation: Facebook used another super-hardcore ReDex solution to this problem, and it's the OatmealCopy the code

Native Library

  • The various tripartite libraries have led to the increasing size of Native Library in APK
  1. Deleting Debug messages
  2. Using c + + _shared
  3. The Library compression
    • Like Dex compression, the most effective way to optimize the Library is to use XZ or 7-zip compression.
    • Only a few of the libraries associated with the startup process need to be loaded, and the others we unzip the first time we start.
    • Facebook has an open source so-loaded library, SoLoader, that works with this solution
  4. Library merge and crop
    • Buck, Facebook’s build and build tool, also has two more hardcore high-tech features
    1. The Library of consolidation. Prior to Android 4.3, there was a limit to the number of libraries a process could load. During compilation, we can automatically merge parts of the Library into one.
      • For detailed ideas, refer to the article “Android Native Library merging” and the Demo.
    2. The Library tailoring. Buck has a relinker function that analyzes JNI methods and method calls from different libraries to find any exported symbols that are not useful and delete them.

    In this case, Linker will also delete the code when it compilers, which is equivalent to implementing The Library ProGuard Shrinking feature.

  5. So removal scheme
    • Currently, there are seven different types of CPU architecture supported by Android. The x86 architecture does not have a real machine, but the simulator uses the so library of this architecture.
    • Configure this abiFiliters in build.gradle to set the So architecture supported by the App
    // Production release {resValue "string", "app_name", "@string/app_name_release" NDK {abiFilters "armeabi", "armeabi-v7a", "Arm64-v8a" // There are some ideas that just leave armeabi. So in armeabi is compatible with So on other platforms. }} // Debug {resValue "string", "app_name", {resValue "string", "app_name", "@string/app_name_debug" ndk { rootProject.ext.ndkAbis.each { abi -> abiFilter(abi) } } }Copy the code

Package volume monitoring

  1. The size of the monitor
    • If the volume of a version increases too much, you need to analyze the specific cause and determine whether there is room for optimization.
  2. Depend on the monitor
    • When adding open source libraries, you need to be mindful of their size. You can strip them of their functionality and introduce only the required code
  3. Rule monitoring: for example, useless resources, large files, duplicate files, R files, etc., refer to ApkChecker of wechat Matrix

Resource optimization

Use the AndResGuard tool
  • AndResGuard has two main features
  1. Resources to confuse
    • The core optimizations of ProGuard are three: Shrink, Optimize, and Obfuscate, which are tailoring, optimizing, and Obfuscate.
    1. Resources.arsc: Since the resource index file resources.arsc needs to record the name and path of the resource file, using the confused short path res/s/a can reduce the size of the entire file
    2. Metadata signature files: Both MF and SF signature files need to record the paths of all files and their hash values. Using short paths can reduce the size of these two files
    3. ZIP file index: Records the Entry path, compression algorithm, CRC, and file size of each file in the ZIP file format. Using the short path itself reduces the size of the string that records the file path;
  2. Limit of compression
    • Higher compression ratio. Although we are still using the Zip algorithm, the overall compression rate of APK can be improved by about 3% by using the large dictionary optimization of 7-ZIP.
    • Compress more files. During Android compilation, files in the following formats are specified as uncompressed; In AndResGuard, support for forced compression of resources. Arsc, PNG, JPG, and GIF files.
    /* these formats are already compressed, or don't compress well */
    static const char* kNoCompressExt[] = {
        ".jpg", ".jpeg", ".png", ".gif",
        ".wav", ".mp2", ".mp3", ".ogg", ".aac",
        ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
        ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
        ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
        ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"
    };
    Copy the code
    • Why does Android specifically choose not to compress these files?
      • The compression effect is not obvious. Most of the files in these formats are themselves compressed
      • Read time and memory considerations. If the file is not compressed, the system can use the MMAP method to read directly, rather than the need to decompress and put in memory
    • AndroidManifest does not compress Library files, so you do not need to decompress the Library files when installing APK. The system can directly install the Library files in the Mmap package.
    Android: extractNativeLibs = "true"Copy the code
  • The use of AndResGuard
apply plugin: 'AndResGuard' buildscript { repositories { jcenter() google() } dependencies { classpath 'com. Tencent. Mm: AndResGuard - gradle - plugin: 1.2.18'}} AndResGuard {/ / mappingFile = file (". / resource_mapping. TXT ") MappingFile = null use7zip = true useSign = true mappingFile = null use7zip = true FixedResName = "arg" fixedResName = "arg" fixedResName = "arg" MergeDuplicatedRes = true whiteList = [// for your icon" r.drable.icon ", // 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.18' / / path = "/ usr/local/bin / 7 za"} / * * * optional: FinalApkBackupPath = "${project.rootdir}/final.apk" /** * Optional: * Default value is "SHA-1" **/ // digestalg = "SHA-256"}Copy the code
Advanced optimization method
  1. Resource merge: All resource files are merged into one large file

    • In fact, most skin peels are based on this idea, with this large resource file acting as a set of skins. Therefore, we can popularize this scheme, but it still needs to solve a lot of problems.
    1. Resource resolution. We need to simulate the system to parse the resource files, such as PNG, JPG and XML files into Bitmap or Drawable, so the method of obtaining the resource needs to be changed to our own custom method.
    Drawable Drawable = getResouces().getDrawable(r.drawable.loading); Drawable Drawable = getResouces().getDrawable(r.drawable.loading); / / new way of obtaining Drawable Drawable. = CustomResManager getDrawable (R.d rawable. Loading);Copy the code
    1. Resource management. Considering memory and startup time, all resources are also loaded when used, so we only need to use mmap to load the “Big Resource File”.

    At the same time, we also need to implement their own ResourceCache pool ResourceCache, release no longer used resource files, this part of the content you can refer to similar Glide image library implementation.

  2. Useless resources

    1. Lint: Use the Lint static code scan tool, which supports Unused Resources scan.
    2. ShrinkResources: Indicates that resources are compressed. Use this function together with the minifyEnabled function of ProGurad
    // If ProGuard removes some useless code, the resources referenced by the code will also be marked as useless, and then the resource compression function will remove them. Arsc and r.java files will change the resource ID of the Android {... buildTypes { release { shrinkResources true minifyEnabled true } } }Copy the code
  3. There are a variety of references to Assets in the code, and it’s not easy to accurately identify useless Assets. An attempt is made to provide a simple implementation in Matrix, see UnusedAssetsTask;

  4. Avoid generating Java Access methods

    1. Take care during development to make adjustments where access methods might arise, such as dropping private and replacing them with package visibility.
    2. Use ASM to remove generated Access methods at compile time.
    • It is recommended to use ByteX’s Access_inline plug-in directly
    • In addition to Access_inlie, there are four useful Gradle plugins in ByteX to help reduce the Dex file size
    Inline constant field during compilation: const_inline Remove redundant assignment code during compilation: field_assign_opt Remove Log code during compilation: method_call_opt Getter-setter-inline-plugin = getter-setter-inline-plugin = getter-setter-inline-pluginCopy the code
  5. Optimization of duplicated resources

    • Duplicate Resources are removed by modifying Compiled Resources before the Android build tool executes the package${flavorName}Task
    1. First, duplicate resources are screened out by the CRC-32 checksum of each ZipEntry in the resource bundle.
    2. Then, modify resources.arsc with android-chunk-utils to redirect these duplicate resources to the same file.
    3. Finally, the duplicate resource files are removed from the resource bundle, keeping only the first resource.
    The implementation code is as follows: variantData. Outputs. Each {def apFile = it. PackageAndroidArtifactTask. GetResourceFile (); it.packageAndroidArtifactTask.doFirst { def arscFile = new File(apFile.parentFile, "resources.arsc"); JarUtil.extractZipEntry(apFile, "resources.arsc", arscFile); def HashMap<String, ArrayList<DuplicatedEntry>> duplicatedResources = findDuplicatedResources(apFile); removeZipEntry(apFile, "resources.arsc"); if (arscFile.exists()) { FileInputStream arscStream = null; ResourceFile resourceFile = null; try { arscStream = new FileInputStream(arscFile); resourceFile = ResourceFile.fromInputStream(arscStream); List<Chunk> chunks = resourceFile.getChunks(); HashMap<String, String> toBeReplacedResourceMap = new HashMap<String, String>(1024); Iterator< map.entry <String, ArrayList<DuplicatedEntry>>> iterator = duplicatedResources.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, ArrayList<DuplicatedEntry>> duplicatedEntry = iterator.next(); For (def index = 1; def index = 1; index < duplicatedEntry.value.size(); ++index) { removeZipEntry(apFile, duplicatedEntry.value.get(index).name); toBeReplacedResourceMap.put(duplicatedEntry.value.get(index).name, duplicatedEntry.value.get(0).name); } } for (def index = 0; index < chunks.size(); ++index) { Chunk chunk = chunks.get(index); if (chunk instanceof ResourceTableChunk) { ResourceTableChunk resourceTableChunk = (ResourceTableChunk) chunk; StringPoolChunk stringPoolChunk = resourceTableChunk.getStringPool(); for (def i = 0; i < stringPoolChunk.stringCount; ++i) { def key = stringPoolChunk.getString(i); if (toBeReplacedResourceMap.containsKey(key)) { stringPoolChunk.setString(i, toBeReplacedResourceMap.get(key)); } } } } } catch (IOException ignore) { } catch (FileNotFoundException ignore) { } finally { if (arscStream ! = null) { IOUtils.closeQuietly(arscStream); } arscFile.delete(); arscFile << resourceFile.toByteArray(); addZipEntry(apFile, arscFile); }}}}Copy the code
  6. Image compression

    1. Via tingpng.com/
    2. McImage
    3. TinyPngPlugin
    4. TinyPIC_Gradle_Plugin
    • Note that in the Android build process, AAPT uses the built-in compression algorithm to optimize PNG images in the res/drawable/ directory,

    However, this can cause the optimized image to become larger, so you can disable cruncherEnabled in build.gradle to optimize PNG images

    aaptOptions {
        cruncherEnabled = false
    }
    Copy the code
    • Image format selection: VD (solid color icon) ->WebP (non-solid color icon) ->Png (better) -> JPG (if no alpha channel)
  7. Inline optimization of R Field

    • The code is further slimmed down by inlining R fields. In addition, it also solves the problem of MultiDex 65536 caused by too many R fields.

    To implement inline R fields, we need to use Javassist or ASM bytecode tools to inline R fields in the build process.

    • You can use or refer to Mogujie’s ThinR Gradle plugin
  8. Minimum configuration of resource files

    • Choose the appropriate language resource based on the language version currently supported by the App. For example, if you use AppCompat, if you do not do any configuration,

    The final APK package contains all the translated language strings in AppCompat, regardless of whether the rest of the application is translated into the same language. To do this, we can use resConfig to configure which languages to use, allowing the build tool to remove all resources outside the specified language. Similarly, you can use resConfigs to configure the image resource classes your application needs, such as “xhdpi”, “xxhdpi”, and so on

    android { ... defaultConfig { ... resConfigs "zh", "zh-rCN" resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi" } ... } // The value of that value will be missing when your application is missing. splits { density { enable true exclude "ldpi", "tvdpi", "xxxhdpi" compatibleScreens 'small', 'normal', 'large', 'xlarge' } } ... }Copy the code
  9. Online resources

    • Put some image resources on the server, and then combined with the image preloading technology, can meet the needs of the product, at the same time can reduce the package size.
  10. Unified application style

    • Set the same font, size, color, button press effect, shape, selector background, etc
  11. pluggable

    • The code structure should be adjusted by means of plug-ins. If every function in our App is a plug-in and can be sent from the server, the package size of the App will definitely be much smaller.

Refer to the article

  • Package size optimization (part 1) : How to reduce package size?
  • Knowledge about The Android installation package
  • AndResGuard– wechat Android resource obfuscation package tool
  • AndResGuard
  • Alipay App construction optimization analysis: Android package size extreme compression
  • ReDex: An Android Bytecode Optimizer
  • Matrix-apkchecker — Apk analysis package reduction tool
  • Package volume optimization (part 2) : An advanced practice of resource optimization
  • Android App package slimming optimization practice
  • Principle of Android APK signature
  • An in-depth look at Android package size optimization
  • Android performance optimization: Use Lint to optimize code and remove redundant resources
  • Use the Simian tool to scan for duplicate code
  • Watermelon video APK slim Java Access method delete
  • access-inline-plugin

I am Jinyang, if you want to advance and learn more about dry goods, welcome to follow the wechat public account “jinyang said” to receive my latest articles