App包体积全面瘦身

汇总:Android小白成长之路_知识体系汇总【持续更新中…】

为什么要进行包体积优化?

  • 包体积越大下载耗费流量越大,使用流量的用户可能会因耗费流量大而放弃下载
  • 包体积足够小可以给用户更短的思考时间,用户在短暂的思考是否要取消下载时已经完成了下载,减少用户反悔
  • 更小的包体积在同类app竞争中更占优势,用户更倾向于轻量级app
  • 倘若app需要预装在渠道合作商的合作手机上,包体积更小更能让合作商接受并且单价成本更低
  • 在用户手机存储空间不足时,包体积更小的app更能避免遭受卸载的风险

Apk的组成

想要对包体积大小进行优化,首先需要了解apk的组成,apk实际上是一个压缩包,把apk拖进Android Studio中可以看到如下结构:

在这里插入图片描述

  • classes.dex: 主要包含能够被 Dalvik/ART虚拟机理解的 DEX格式的 class文件
  • res: 主要包含apk的资源文件
  • resources.arsc: 一个二进制文件,存储Values类型的资源和图片、raw、xml资源的路径,R.java文件就是对这些资源进行索引
  • AndroidManifest.xml: 该文件主要是 Android 应用清单文件
  • META-INF: 主要包含 CERT.SFCERT.RSA 签名文件, 以及MANIFEST.MF清单文
    • MANIFEST.MF:列出了apk所有文件和它所对应的base64-encoded SHA1 哈希值
    • CERT.SF:和MANIFEST.MF类似,但是列出的却是MANIFEST.MF中每一条信息的hash值
    • CERT.RSA:包含了对CERT.SF文件的数字签名以及签名时所用的platform.x509.pem这个数字证书
  • lib: 如果使用了so库,就会生成这个目录,包含一些平台的 so 库, 可能有 armeabi, armeabi-v7a, arm64-v8a, x86, x86_64这些分类等等,通常占着大量的空间,急需优化
  • assets: 如果工程中用到了assets文件夹,才会打包进去,主要包含一些app需要内置的资源文件,通常为各种mp3文件、lottie动画的图片资源和json文件

优化方案

dex大小优化

删除无效代码

无效代码包括:

  • 已注释代码
  • 未使用方法
  • 未使用类
  • 重复工具类、重复功能代码块
  • 已下架功能代码模块

操作方法:使用Android StudioAnalyze -> Inspect Code进行代码检测,可以查找未使用的代码,对于重复工具类和已下架功能代码等需要手动检查

优化效果:无效代码体积本身占比不大,优化很小

注意事项:

  • 已注释代码如果是暂时屏蔽某个功能,或者是个公用的工具类方法,可以不清除,后续可能会再次用到
  • 已下架功能代码确认之后不再使用,避免删除以后再次上架再次编写功能代码

D8编译

GoogleAndroid Studio 3.1版本之后使用D8作为版本开发工具默认的Dex 编译器,D8 的 优化效果总的来说可以归结为如下四点:

  • Dex的编译时间更短
  • Dex文件更小
  • D8 编译的 .dex 文件拥有更好的运行时性能。
  • 包含 Java 8 语言支持的处理。

操作方法:在Android Studio 3.0 需要主动在gradle.properties文件中新增:

android.enableD8 = true

Android Studio 3.1 或之后的版本D8 将会被作为默认的Dex 编译器

优化效果:Dex略微变小

参考链接:新一代Dex编译器:D8

Proguard混淆

Proguard是一个免费的 Java 类文件压缩、优化、混淆、预先校验的工具,主要作用可以概括为 两点:

  • 瘦身:它可以检测并移除未使用到的类、方法、字段以及指令、冗余代码,并能够对字节码进行深度优化。最后,它还会将类中的字段、方法、类的名称改成简短无意义的名字
  • 安全:增加代码被反编译的难度,一定程度上保证代码的安全

操作方法:

buildTypes {
    
    
    release {
    
    
        // 1、是否进行混淆
        minifyEnabled true
        // 2、开启zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗
        zipAlignEnabled true
        // 3、移除无用的resource文件:当ProGuard 把部分无用代码移除的时候,
        // 这些代码所引用的资源也会被标记为无用资源,然后
        // 系统通过资源压缩功能将它们移除。
        // 需要注意的是目前资源压缩器目前不会移除values/文件夹中
        // 定义的资源(例如字符串、尺寸、样式和颜色)
        // 开启后,Android构建工具会通过ResourceUsageAnalyzer来检查
        // 哪些资源是无用的,当检查到无用的资源时会把该资源替换
        // 成预定义的版本。主要是针对.png、.9.png、.xml提供了
        // TINY_PNG、TINY_9PNG、TINY_XML这3个byte数组的预定义版本。
        // 资源压缩工具默认是采用安全压缩模式来运行,可以通过开启严格压缩模式来达到更好的瘦身效果。
        shrinkResources true
        // 4、混淆文件的位置,其中 proguard-android.txt 为sdk默认的混淆配置,
        // 它的位置位于android-sdk/tools/proguard/proguard-android.txt,
        // 此外,proguard-android-optimize.txt 也为sdk默认的混淆配置,
        // 但是它默认打开了优化开关。并且,我们可在配置混淆文件将android.util.Log置为无效代码,
        // 以去除apk中打印日志的代码。而 proguard-rules.pro 是该模块下的混淆配置。
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        signingConfig signingConfigs.release
    }
}

优化效果:如果全部功能开启,优化较好

参考链接:混淆工具:Proguard

R8优化

GoogleAndroid Studio 3.2 中引入R8 作为 ProGuard的替代工具,用于代码的压缩(shrinking)和混淆(obfuscation),ProGuardR8 都应用了基本名称混淆:它们都使用简短,无意义的名称重命名类,字段和方法。他们还可以删除调试属性。但是,R8inline 内联容器类中更有效,并且在删除未使用的类,字段和方法上则更具侵略性。例如,R8 本身集成在ProGuard V6.1.1 版本中,在压缩 apk 的大小方面,与 ProGuard 的 8.5% 相比,使用 R8 apk 尺寸减小了约 10%。并且,随着 Kotlin现在成为 Android 的第一语言,R8 进行了 ProGuard 尚未提供的一些 Kotlin的特定的优化。

操作方法:如果我们当前使用的是Android Studio 3.4Android Gradle 插件3.4.0及其更高版本,R8 会作为默认编译器。否则,我们 必须要在 gradle.properties中配置如下代码让 App 的混淆去支持 R8:

android.enableR8=true 
android.enableR8.libraries=true

优化效果:Proguard的升级版本,优化较好

资源优化

删除无用资源

APK 的资源主要包括图片、XML,由于版本更新迭代,会遗留很多旧版本使用但新版本不再使用的资源文件,这些资源文件也会占用空间,需要删除

操作方法:选中菜单项Refactor,然后点击Remove Unused Resource => preview可以预览找到的无用资源,点击 Do Refactor 可以去除冗余资源

优化效果:无用资源越多优化越好

注意事项:可能需要手动再次确认资源真实未使用,不能使用一键删除,否则会导致某些特殊情况下使用的资源找不到

使用shrinkResources压缩资源

Proguard混淆自带的资源压缩

操作方法:在Proguard混淆中开启

shrinkResources true

优化效果:优化效果一般

png转webp

WebP 是 Google 的一种可以同时提供有损压缩(像JPEG 一样)和透明度(像 PNG 一样)的图片文件格式,不过与JPEGPNG 相比,这种格式可以提供更好的压缩。Android 4.0(API 级别 14)及更高版本支持有损 WebP 图片,Android 4.3(API 级别 18)及更高版本支持无损且透明的 WebP图片

操作方法:在Android Studio中右键点击某个图片文件或包含一些图片文件的文件夹,然后点击 Convert to WebP

优化效果:如果png没压缩过,优化效果很大,如果png已经压缩过了,也依然有一定的优化作用

注意事项:

  • 由于只有Android 4.3及更高版本支持无损和透明的webp图片,因此minSdkVersion必须为18或者更高
  • 9-patch 文件无法转换为WebP 图片,转换器工具会自动跳过9-patch图片
  • 转换过程中需要查看图片是否存在异常
  • Google play不允许app图标使用非png格式,因此不能对app图标进行转换

TinyPng压缩png

TinyPNG使用智能有损压缩技术将PNG文件的文件大小降低。 通过选择性的减少图片中的颜色,只需要很少的字节数就能保存数据。 对视觉的影响几乎不可见,但是在文件大小上有非常大的差别

操作方法:

  1. Android Studio下载TinyPng Image Optimizer插件
  2. 选中需要压缩的png图片,右键选中Optimize Image Size
  3. 点击Optimize即可

优化效果:较大,但比不上转webp

参考链接:TinyPng

So优化

及时移除无用so

在功能迭代后,会有一些so库不再使用,必须尽快移除,因为so占体积比很大,特别是分多个abi的情况下

优化效果:so越大,优化效果越大

精简so库

提取so库功能,精简so库中无用代码,可以进一步减小so库的大小

优化效果:一般

过滤so库

目前,Android 一共 支持7种不同类型的 CPU 架构,比如常见的 armeabi、armeabi-v7a、X86等等。理论上来说,对应架构的 CPU 它的执行效率是最高的,但是这样会导致 在lib 目录下会多存放了各个平台架构的 So 文件,所以 App 的体积自然也就更大了,因此需要对lib目录进行精简。一般情况下,应用都不需要用到 neon指令集,我们只需留下 armeabi目录就可以了。因为armeabi 目录下的 so 可以兼容别的平台上的 so,相当于是一个万金油,都可以使用。但是,这样 别的平台使用时性能上就会有所损耗,失去了对特定平台的优化,如果不考虑性能,可以直接配置:

defaultConfig {
    
    
    ndk {
    
    
        abiFilters "armeabi"
    }
}

但这样做会影响性能,所以可以考虑一个折中的方案:对于性能敏感的模块,它使用到的 so,即使是属于其他abi,也都放在 armeabi目录当中随着 Apk 发出去,然后在代码中来判断一下当前设备所属的 CPU 类型,根据不同设备 CPU 类型来加载对应架构的 So 文件:

String abi = "";
// 获取当前手机的CPU架构类型
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
    
    
    abi = Buildl.CPU_ABI;
} else {
    
    
    abi = Build.SUPPORTED_ABIS[0];
}
if (TextUtils.equals(abi, "x86")) {
    
    
    // 加载特定平台的So
     
} else {
    
    
    // 正常加载
     
} 

综合优化

booster

滴滴在 Github 上开源了一个 Android App 的质量优化工具BoosterBooster是一款专门为移动应用设计的易用、轻量级且可扩展的质量优化框架,其目标主要是为了解决随着 APP 复杂度的提升而带来的性能、稳定性、包体积等一系列质量问题。Booster 提供了性能检测、多线程优化、资源索引内联、资源去冗余、资源压缩、系统 Bug 修复等一系列功能模块,可以使得稳定性能够提升 15% ~ 25%,包体积可以减小 1MB ~ 10MB。只为了优化包体积大小,可以选择性集成部分功能

操作方法:

  1. 在项目根目录的build.gradle中配置:

    buildscript {
          
          
        ext.booster_version = '3.1.0'
        repositories {
          
          
            google()
            mavenCentral()
        }
        dependencies {
          
          
    		//booster基础插件
            classpath "com.didiglobal.booster:booster-gradle-plugin:$booster_version" 
    		//采用 cwebp 对资源进行压缩
    		classpath "com.didiglobal.booster:booster-task-compression-cwebp:$booster_version"
    		//ap_ 文件压缩
    		classpath "com.didiglobal.booster:booster-task-compression-processed-	res:$booster_version"
    		//去冗余资源
    		classpath "com.didiglobal.booster:booster-task-resource-deredundancy:$booster_version"
    		//资源索引内联
    		classpath "com.didiglobal.booster:booster-transform-r-inline:$booster_version"
        }
    }
    }
    
  2. 在主工程模块的build.gradle中引入插件:

    apply plugin: 'com.didiglobal.booster'
    
  3. gradle.properties中配置:

    android.precompileDependenciesResources=false
    

优化效果:很大,建议使用

注意事项:采用这个插件会增加编译时间,因此在调试开发阶段不应该开启,可以添加以下判断,只在打publish包的时候才使用:

ext.isPublish = gradle.startParameter.taskNames.any {
    
     it.contains('publish') || it.contains('Publish') }
classpath "com.didiglobal.booster:booster-gradle-plugin:$boosterVersion"
if (isPublish) {
    
    
    classpath "com.didiglobal.booster:booster-task-compression-cwebp:$boosterVersion"
    classpath "com.didiglobal.booster:booster-task-compression-processed-res:$boosterVersion"
    classpath "com.didiglobal.booster:booster-task-resource-deredundancy:$boosterVersion"
    classpath "com.didiglobal.booster:booster-transform-r-inline:$boosterVersion"
}

参考链接:质量优化框架:booster

Android App Bundle

Android App Bundle是一种发布格式,其中包含应用的所有经过编译的代码和资源,它会将 APK 生成及签名交由 Google Play 来完成。Google Play 会使用 app bundle 针对每种设备配置生成并提供经过优化的 APK,因此只会下载特定设备所需的代码和资源来运行应用。不必再构建、签署和管理多个 APK 来优化对不同设备的支持,而用户也可以获得更小且更优化的下载文件包,这在Google play上架的应用来说是非常有效的

操作方法:

  1. 使用Android Studio 3,2或以上版本
  2. 选择Build菜单,右键选择Build Bundles(s)/Apk(s),然后点击Build Bundle(s)
  3. build成功后可以选择Generate Signed Bundle or Apk对生成的aab文件进行签名,需要进行秘钥的配置
  4. 如果需要测试aab,需要下载buildtool进行安装测试,或者把aab文件上传到google play控制台的测试通道进行测试

优化效果:非常大,Google play上架的app极力推荐

注意事项:

  • 在 2021 年下半年,新应用将需要使用Android App Bundle才能在 Google Play 中发布

参考链接:Android App Bundle

极限优化

以下优化方式存在各种各样的问题,看自己情况使用

使用 XZ Utils 进行 Dex 压缩

XZ Utils是具有高压缩率的免费通用数据压缩软件。XZ Utils是为类似POSIX的系统编写的,但也可以在某些非POSIX系统上使用。XZ UtilsLZMA Utils的后继产品。XZ Utils压缩代码的核心基于LZMA SDK,但是已经对其进行了大量修改以适合XZ Utils。当前,主要压缩算法是LZMA2,它在.xz容器格式内使用。对于典型文件,XZ Utils的输出比gzip小30%,比bzip2小15%

存在的问题

  • 当文件较大时,在低端机可能存在3-5s的解压时间
  • 当 Dex 非常多的时候会增加应用的安装时间,如果还使用了压缩 Dex 的方式,那么首次生成 ODEX 的时间可能就会超过1分钟

参考链接:XZ Utils

避免产生 Java access 方法

为了能提供内部类和其外部类直接访问对方的私有成员的能力,又不违反封装性要求,Java 编译器在编译过程中自动生成 package 可见性的静态 access$xxx方法,并且在需要访问对方私有成员的地方改为调用对应的 access 方法。在开发过程中需要注意在可能产生 access 方法的情况下适当调整,比如去掉 private,改为 package 可见性,也可以使用ASM或者ByteX在编译时删除生成的 access 方法。

存在的问题:效果很小但使用相对麻烦

AndResGuard

AndResGuard是微信团队开源出来的一个注重减小Res包大小的工具,可以直接对Apk进行处理得到处理后的Apk文件。原理类似Java Proguard,但是只针对资源。他会将原本冗长的资源路径变短,例如将res/drawable/wechat变为r/d/aAndResGuard不涉及编译过程,只需输入一个apk(无论签名与否,debug版,release版均可,在处理过程中会直接将原签名删除),可得到一个实现资源混淆后的apk(若在配置文件中输入签名信息,可自动重签名并对齐,得到可直接发布的apk)以及对应资源ID的mapping文件

apply plugin: 'AndResGuard'
buildscript {
    
    
    repositories {
    
    
        jcenter()
        google()
    }
    dependencies {
    
    
        classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.20'
    }
}

andResGuard {
    
    
    // mappingFile = file("./resource_mapping.txt")
    mappingFile = null
    use7zip = true
    useSign = true
    // 打开这个开关,会keep住所有资源的原始路径,只混淆资源的名字
    keepRoot = false
    // 设置这个值,会把arsc name列混淆成相同的名字,减少string常量池的大小
    fixedResName = "arg"
    // 打开这个开关会合并所有哈希值相同的资源,但请不要过度依赖这个功能去除去冗余资源
    mergeDuplicatedRes = true
    whiteList = [
        // for your icon
        "R.drawable.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",
    ]
    sevenzip {
    
    
         artifact = 'com.tencent.mm:SevenZip:1.2.20'
         //path = "/usr/local/bin/7za"
    }
    /**
    * 可选: 如果不设置则会默认覆盖assemble输出的apk
    **/
    // finalApkBackupPath = "${project.rootDir}/final.apk"
    /**
    * 可选: 指定v1签名时生成jar文件的摘要算法
    * 默认值为“SHA-1”
    **/
    // digestalg = "SHA-256"
}

参考链接:Apk混淆工具:AndResGuard

AabResGuard

节跳动抖音技术团队开源的一款针对 .aab 文件的资源混淆工具,也就是使用app bundle打包的应用,由于 .aab.apk 文件结构的差异,AndResGuard 的资源混淆方案是不适用于 AAB 的,需要使用它的另一个版本,也就是AabResGuard,使用方式:

build.gradle(root project) 中进行配置:


buildscript {
    
    
  repositories {
    
    
    mavenCentral()
    jcenter()
    google()
   }
  dependencies {
    
    
    classpath "com.bytedance.android:aabresguard-plugin:0.1.0"
  }
}

build.gradle(application) 中配置

apply plugin: "com.bytedance.android.aabResGuard"
aabResGuard {
    
    
    mappingFile = file("mapping.txt").toPath() // 用于增量混淆的 mapping 文件
    whiteList = [ // 白名单规则
        "*.R.raw.*",
        "*.R.drawable.icon"
    ]
    obfuscatedBundleFileName = "duplicated-app.aab" // 混淆后的文件名称,必须以 `.aab` 结尾
    mergeDuplicatedRes = true // 是否允许去除重复资源
    enableFilterFiles = true // 是否允许过滤文件
    filterList = [ // 文件过滤规则
        "*/arm64-v8a/*",
        "META-INF/*"
    ]
    enableFilterStrings = false // 过滤文案
    unusedStringPath = file("unused.txt").toPath() // 过滤文案列表路径 默认在mapping同目录查找
    languageWhiteList = ["en", "zh"] // 保留en,en-xx,zh,zh-xx等语言,其余均删除

参考链接:Aab混淆工具:AabResGuard

ByteX

ByteX是字节跳动抖音团队开发的一个基于gradle transform apiASM的字节码插件平台。目前集成了若干个字节码插件,每个插件完全独立,既可以脱离ByteX这个宿主而独立存在,又可以自动集成到宿主和其它插件一起整合为一个单独的Transform。插件和插件之间,宿主和插件之间的代码是完全解耦的(有点像组件化),这使得ByteX在代码上拥有很好的可拓展性,新插件的开发将会变得更加简单高效

buildscript {
    
    
    ext.plugin_version="0.2.6"
    repositories {
    
    
        google()
        jcenter()
    }
  
    dependencies {
    
    
        classpath "com.bytedance.android.byteX:base-plugin:${plugin_version}"
      	// Add bytex plugins' dependencies on demand. 按需添加插件依赖
        classpath "com.bytedance.android.byteX:refer-check-plugin:${plugin_version}"
      	// ...
    }
}
apply plugin: 'com.android.application'
// apply ByteX宿主
apply plugin: 'bytex'
ByteX {
    
    
    enable true
    enableInDebug false
    logLevel "DEBUG"
}
// 按需apply bytex 插件
apply plugin: 'bytex.refer_check'
// ...

已集成的插件

存在的问题:

  • 本身是为了插件扩展而做的插件平台,如果只用作瘦身,有点大材小用,并且对瘦身的优化不是很大
  • 可以扩展插件功能,但是有一定的使用难度

参考链接:ByteX

资源最小化配置

根据 App 目前所支持的语言版本去选用合适的语言资源和图片大小

android {
    
    
    ...
    defaultConfig {
    
    
	    ...
        resConfigs "zh", "zh-rCN"
        resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
    }
    ...
}   

存在问题:优化不大,并且可能会导致适配兼容出现问题

资源在线化

将一些图片资源放在服务器,然后 结合图片预加载 的技术手段,这些 既可以满足产品的需要,同时可以减小包大小

存在问题:用户网络不好的时候体验不佳

使用 XZ Utils 对 Native Library 进行压缩

Native LibraryDex 一样,也可以使用XZ Utils进行压缩,对于 Native Library的压缩,我们 只需要去加载启动过程相关的Library,而其它的都可以在应用首次启动时进行解压,并且,压缩效果与 Dex 压缩的效果是相似的

存在问题:和压缩Dex一样,对应用启动时间影响较大

So 动态下载

将部分 So 文件使用动态下发的形式进行加载,也就是在业务代码操作之前,可以先从服务器下载下来 So,接下来再使用

存在问题:用户网络不好时体验不佳

插件化

H5嵌入

So 动态下载

将部分 So 文件使用动态下发的形式进行加载,也就是在业务代码操作之前,可以先从服务器下载下来 So,接下来再使用

存在问题:用户网络不好时体验不佳

插件化

H5嵌入

总结

根据对各种方法的分析,筛选出较为实用和常用的方法如下:

  • 删除无用代码和无用资源文件
  • 开启proguard混淆
  • 开启D8R8
  • 使用webp格式或TinyPng压缩png图片
  • 使用booster插件优化
  • 使用Android App Bundle方式打包

备选方法:

  • 过滤so库
  • AndResGuard
  • AabResGuard
  • ByteX

猜你喜欢

转载自blog.csdn.net/Nbin_Newby/article/details/120461293