Android 性能优化(四)优化Apk体积

为什么需要做 Android 包体积优化

  1. 提高下载转化率,同类型的 App 包体积越小,用户下载等待的时间也会越短,所以下载转换成功率也就越高。
  2. 应用市场要求。比如Google Play 应用市场强制要求超过 100MB 的应用只能使用 APK 扩展文件方式上传。
  3. 包体积过大对 App 的性能会有影响。比如:Resource 资源、Library 以及 Dex 类加载都会占用应用的一部分内存。

优化资源文件

使用 Lint 查找未使用的资源文件并删除

在 Android 项目中,可能会遗留很多旧版本不需要的子类文件、比如图片、xml等。我们可以通过 鼠标右键 -> Refactor -> Remove Unused Resource -> preview 来看到有哪些资源文件未被使用。如下图所示:

需要注意的,Android Lint 不会分析 assets 文件夹下的资源,因为 assets 文件可以通过文件名直接访问,不需要通过具体的引用,Lint 无法判断资源是否被用到。

使用 isShrinkResources

arduino 复制代码
buildTypes {// 生产/测试环境配置
    release {// 生产环境
        isMinifyEnabled = false //是否对代码进行混淆
        isShrinkResources = false // 指定是否为此生成类型启用缩减资源。
        ...
    }
}

isShrinkResources 是用来开启删除无用资源,也就是没有被引用的文件(经过实测 drawable,layout,实际并不是彻底删除,而是保留文件名,但是没有内容,等等),但是因为需要知道是否被引用所以需要配合isMinifyEnabled 一起使用,只有当两者都为true的时候才会起到真正的删除无效代码和无引用资源的目的。

去除重复资源

在大型的 App 项目中,一个 App 一般由多个团队开发,并会划分成很多不同的模块。因此模块之间的资源可能会重复,但是资源名会不同(不同模块一般会加上模块的前缀)。为了解决这个问题,可以在 Gradle 执行=执行 package${flavorName}Task 之前通过修改 Compiled Resources 来实现重复资源的去除。

具体看 这里

图片压缩

一般来说,1000行代码在APK中才会占用 5kb 的空间,因此对图片的压缩是收益最大的行为。常见的图片压缩操作有:

  1. 能使用 webp 格式,尽量使用 webp
  2. 对于无法使用 webp的图片,可以在 TinyPNG 上先压缩后使用

更多关于图片压缩的可以看 这里

资源文件后加载技术

如果你正在开发的App确实有很多图片显示需求,而且都是GIF图,无法应用WebP转换,也无法压缩,该怎么办呢?典型的例子就是聊天软件中的动画表情,它们大部分都是GIF动图,而且数量很庞大。解决方案就是资源后加载。它有两种实现方案:

  1. 将动画资源使用ZIP压缩,放在assets目录中。使用时,或首次启动App时,由程序代码操作释放它们到存储空间中,这样做可以缩小APK的体积。
  2. 即随用随加载。当App检测到或"猜测"用户可能要发送动画表情时,通过网络请求获取动画表情。这种方案比起第一种更加节省空间,但缺点是当网络状况欠佳时,资源加载速度会很慢。

目前主流是这两种方案结合在一起使用。首先,在APK内部以ZIP格式压缩一些常用的动画表情,放到assets目录中,随APK分发。然后,在适当的时机完成其他动画表情的下载。这样一来,既保证了用户体验,又达到了给APK瘦身的目的。

需要注意,解压缩时最好对内部的资源文件做 md5 检查,确保其完整性。

资源混淆

资源混淆可以将 资源路径混淆成单个资源的路径 ,它可以使冗余的资源路径变短,例如将 res/drawable/wechat 变为 r/d/a

资源混淆工具有 AndResGuard

R Field 的内联优化

android 中的 R 文件,除了 styleable 类型外,所有字段都是 int 型变量/常量,且在运行期间都不会改变。所以可以在编译时,记录 R 中所有字段名称及对应值,然后利用 ASM 工具遍历所有 Class,将除 R$styleable.class 以外的所有 R.class 删除掉,并且在引用的地方替换成对应的常量,从而达到缩减包大小和减少 Dex 个数的效果。

实现的插件有:ByteX

资源文件最少化配置

我们可以通过 resConfig 来配置使用哪些语言,从而让构建工具移除指定语言之外的所有资源。代码示例如下:

erlang 复制代码
android    
    ...
    defaultConfig {
	    ...
        resConfigs "zh", "zh-rCN"
        resConfigs "nodpi", "hdpi"
    }
    ...
}  

也可以利用 Density Splits 来选择应用应兼容的屏幕尺寸大小,代码示例如下:

bash 复制代码
android {
    ...
    splits {
        density {
            enable true
            exclude "ldpi", "tvdpi", "xxxhdpi"
            compatibleScreens 'small', 'normal', 'large', 'xlarge'
        }
    }
    ...
}

还可以通过多渠道打包,只打包所需要的依赖项,代码示例如下:

arduino 复制代码
"huaweiImplementation"("com.huawei.hms:push:6.11.0.300")

其他资源优化建议

  • 尽量每张图片只保留一份

比如说,我们统一只把图片放到 xhdpi 这个目录下,那么 在不同的分辨率下它会做自动的适配 ,即 等比例地拉伸或者是缩小

  • 资源在线化

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

  • 统一应用风格

如设定统一的 字体、尺寸、颜色和按钮按压效果、分割线 shape、selector 背景 等等。

代码优化

代码混淆

代码混淆可以通过缩短变量和函数名以及丢失部分无用信息等方式,能使得应用包体积减小。代码示例如下:

arduino 复制代码
android {
    // ... 其他配置
    buildTypes {
        release {
            // 开启代码混淆
            isMinifyEnabled = false
            // 指定混淆规则文件
            proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
        }
    }
}

除了开启代码混淆外,isMinifyEnabled 还会从应用及其库依赖项中检测并安全地移除不使用的类、字段、方法和属性

Dex 分包优化

当我们的 APK 过大时,Dex 的方法数就会超过65536个,因此,必须采用 mutildex 进行分包。但是此时每一个 Dex 可能会调用到其它 Dex 中的方法,这种 跨 Dex 调用的方式会造成许多冗余信息,主要造成以下问题:

  1. 多余的 method id:
  2. 其它跨dex调用造成的信息冗余:

我们可以使用 redex 来分析了类之间的调用关系,尽量确保将有调用关系的类和方法分配到同一个 Dex 中。

使用 XZ Utils 进行 Dex 压缩

XZ Utils 是具有高压缩率的免费通用数据压缩软件。相对于典型的压缩文件而言,XZ Utils 的输出比 gzip 小 30%,比 bzip2 小 15%。

FaceBookApp 中就使用了 Dex 压缩 的方式,而且它 将 Dex 压缩后的文件都放在了 assets 目录中。而 apk 中的 Dex 仅包含了启动时要用到的类,这样可以为 Dex 压缩文件 secondary.dex.jar.xzs 的解压争取时间。

三方库处理

  1. 将图片加载库、网络库、数据库以及其他基础库进行统一,去掉冗余的库
  2. 尽可能地选择那些比较小的库来实现相同的功能
  3. 如果我们引入三方库的时候,可以 只引入部分需要的代码,而不是将整个包的代码都引入进来。如果你引入的三方库 没有进行过结构剥离,就需要 修改源码,只提取出来你需要的功能即可。

so 库优化

设置 abiFiliters

less 复制代码
defaultConfig {
    ndk {
        // 指定一个或多个你需要的 ABI 
        abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a"))
    }
}

通过 abiFilters 设置支持的 abi。

So 库动态下发

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

工具

android-classshark

android-classshark 是一个 面向 Android 开发人员的独立二进制检查工具 ,它可以 浏览任何的 Android 可执行文件,并且检查出信息,比如类的接口、成员变量等等。它还可以支持多种格式,比如说 APK、Jar、Class、So 以及所有的 Android 二进制文件如清单文件等。使用android-classshark显示方法数,如下图所示:

android-classshark项目地址

coverage

coverage 用于线上无用代码分析。

由于代码设计不合理以及keep规则限制等原因,静态代码检查无法找出所有的无用代码。我们可以从用户的角度去分析,对每个类插桩,执行时将信息上报到服务器。基于大量用户上报,用户没有用到的类可以被定义为无用类。在抖音项目中,就发现了1/6的无用类,不包含其引用的资源,共计3M(dex大小20M),如果能全部删除,将减少5%包大小。

redex

redex是一款安卓字节码(dex)优化程序,最初由 Facebook 开发。它提供了一个用于读取、写入和分析 .dex 文件的框架,以及一套使用该框架改进字节码的优化程序。经过 ReDex 优化的 APK 应该比源代码更小更快

参考

相关推荐
Rains04226 小时前
TEE可信执行环境的安全业务保护方案
android·安全
vincent_woo6 小时前
再学安卓 - binder之驱动函数ioctl
android·操作系统
Mr.pyZhang8 小时前
安卓基础组件Looper - 03 java层面的剖析
android·java·数据结构·epoll
Yang-Never8 小时前
OpenGL ES -> GLSurfaceView纹理贴图
android·java·开发语言·kotlin·android studio·贴图
暴怒的代码9 小时前
基础篇——深入解析SQL多表操作与关联查询:构建复杂数据关系的桥梁
android·java·sql
Nathan202406169 小时前
数据结构 - LinkedHashMap(二)
android·数据结构·面试
抛砖者10 小时前
9. Flink的性能优化
android·性能优化·flink
小墙程序员11 小时前
Android Framework 面试系列(八)ContentProvider
android
令狐掌门11 小时前
android智能指针android::sp使用介绍
android·android智能指针