Android 性能优化总结:包体积优化

前言

  随着开发不断迭代,App体积越来越大,包大小的增大也会给我们应用带来其他的影响 比如

  1. 下载率影响 过大的包体积会影响下载转化率,根据Google Play Store包体积和转化率分析报告显示,平均每增加1M,转化率下降0.2%左右
  2. 渠道限制 部分厂商预装强制要求安装包大小(比如国内市场在下载较大安装包就会提醒大流量是否继续下载的弹窗)
  3. 性能影响 过大的包体积在安装耗时和运行内存占用方面都会有很大影响

  但是包大小的优化不是一次就可以搞定的,需要持续的维护做好打持久战的准备,此篇文章算是在本地生活对包大小实战和别人的经验的总结,日常学习记录仅做参考。

基础了解

  在包体积优化前需要对APK做一个基本的了解

目录内容
lib文件夹下主要存放不同的cpu架构的so文件,会有armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips,大多数情况下只需要支持armabi与x86的架构即可
res文件夹下存放编译后的资源文件,drawable和layout资源
assets应用程序的资源、字体、音频文件等,需要通过AssetManager类来访问内容
dexAndroid项目中的代码在编译后会生成.class文件,然后再通过dx工具转换为字节码文件,就是这个dex文件。一般情况下只有一个classes.dex,如果项目代码方法数超过了65535而采用了multidex的话,会有其他的.dex文件
META-INF签名信息,用来验证apk文件的完整性、合法性
resources.arsc二进制资源文件、AndroidManifest.xml清单文件

打包简要流程

image.png

打包主要有以下几步:

  1. 使用aapt工具处理所有的资源,生成一个R.java文件,一个resources.arsc文件以及其他资源。
  2. 处理.aidl文件,生成对应的Java接口文件。
  3. 将上述两步得到的R.java文件、Java接口文件,与Andorid源码一起,通过Java编译器,编译得到Java字节码文件.class文件。
  4. 获取依赖的第三方库文件,将其与上一步得到的.class文件一起,通过使用dx工具,生成.dex文件。
  5. 将资源索引文件resources.arsc、资源目录res、与上一步得到的.dex文件一起,通过apkbuilder工具,构建出初始的.apk文件。
  6. 使用jarsigner工具,对.apk文件进行签名。
  7. 使用zipalign工具,对.apk文件进行对齐。(让资源按四字节的边界进行对齐,加快资源的访问速度)

经过以上七步,一个完整的apk文件就诞生了。

那么针对压缩文件,主要的压缩体积方式分为:减少和压缩

包现状分析

使用AppChecker分析

  包体分析主要借助的是腾讯AppChecker完成的,AppChecker分析包文件主要还是借助了andoid build-tool下面的 aapt工具

image.png

  上图只是查看了各个文件类型占比,还支持统计 APK 中包含的 R 类、检查是否有多个动态库静态链接了 STL 、搜索 APK 中包含的无用资源、重复资源分析以及支持自定义检查规则等 (强烈推荐的检测工具)

借助AS提供的Analyze APK

image.png

  可以直观的查看APK的组成大小占比等信息,也可以用来查看其他产品使用了那些三方库等信息。

常规优化方式

Lint自动检测

// 扫描res资源文件
Analyze > Run Inspection by Name > Unused resources
// 扫描无用代码
Analyze > Inspect code

  不过需要注意这里扫描为静态扫描,部分资源可能存在动态调用,再删除的时候需要再三确认 ,Inspect code扫描出来的一样为静态扫描结果,反射和动态引用的代码是不会出现在这里的。

常规资源压缩

  这里主要以图片资源来进行压缩优化,列举常用方案前,简单说下各个图片文件及其特征
jpg: 一种有损的基于直接色的图片格式 属于光栅类型,所以可以表示2的24次方种颜色,非常适合色彩丰富图片、渐变色,所以相对的 jpg图片文件大小较大。

png: 也是属于光栅类型,无损压缩格式的基于8为索引色的位图格式称为png-8,支持透明度 并且文件尺寸相比jpg更小。还有一种png-24则是基于直接色的位图格式,图片存储相对较大,但是可以展示比较丰富的图像色彩。

gif: 光栅格式的图像文件类型。它使用无损压缩,但将图像“限制”为每像素 8 位和 256 色的有限调色板,常用于动画图像。

svg: 矢量图像文件类型,使用笛卡尔平面上的线和曲线系统,与总面积相比,而不是任何单个像素,可以无限放大而不会失真

Webp: 供更好的无损和有损图像压缩而开发的图像格式,相较于png 格式还可以压缩 10% -30%大小并且可以获得相同的质量

   主要有两种资源优化手段 :png文件压缩和替换为 Webp图片。

  1. png压缩,平时主要使用 https://tinypng.com/ ,不过很多UI平台都会在切图上传的时候进行自动压缩。
  2. 转为Webp, Android Studio可以右键资源文件 Convert to Webp 一键切换。

代码混淆

android {
    ...
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

压缩( -dontshrink 关闭压缩):默认开启,用以减小应用体积,移除未被使用的类和成员,并且会在优化动作执行之后再次执行(因为优化后可能会再次暴露一些未被使用的类和成员)。

优化( -dontoptimize ):默认开启,在字节码级别执行优化,让应用运行的更快。

-dontoptimize  关闭优化
-optimizationpasses n 表示proguard对代码进行迭代优化的次数,Android一般为5

混淆( -dontobfuscate 关闭混淆):默认开启,增大反编译难度,类和类成员会被随机命名,除非用keep保护。

D8 R8优化

  Android Studio 3.1 或之后的版本 D8 将会被作为默认的 Dex 编译器,相较于D8,重点说下R8.

R8 官网地址

  根据官方说法,R8 是 Proguard 压缩与优化部分的替代品,并且它仍然使用与 Proguard 一样的 keep 规则。如果我们仅仅想在 Android Studio 中使用 R8,在 build.gradle 中打开混淆的时候,R8 就已经默认集成进 AGP 中了。

那么,R8 与混淆相比优势在哪里呢?

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

从表面上看,ProGuardR8 非常相似。它们都使用相同的配置,因此在它们之间进行切换很容易。放大来看的话,它们之间也存在一些差异。R8 能更好地内联容器类,从而避免了对象分配。但是 ProGuard 也有其自身的优势,具体有如下几点:

  • ProGuard 在将枚举类型简化为原始整数方面会更加强大。它还传递常量方法参数,这通常对于使用应用程序的特定设置调用的通用库很有用。ProGuard 的多次优化遍历通常可以产生一系列优化。例如,第一遍可以传递一个常量方法参数,以便下一遍可以删除该参数并进一步传递该值。删除日志代码时,多次传递的效果尤其明显。ProGuard 在删除所有跟踪(包括组成日志消息的字符串操作)方面更有效
  • ProGuard 中应用的模式匹配算法可以识别和替换短指令序列,从而提高代码效率并为更多优化打开了机会。在优化遍历的顺序中,尤其是数学运算和字符串运算可从中受益
  • ProGuard 具有独特的能力来优化使用 GSON 库将对象序列化或反序列化为 JSON 的代码。该库严重依赖反射,这很方便,但效率低下。而 ProGuard 的优化功能可以 通过更高效,直接的访问方式 来代替它。

重复资源过滤

  由于大项目大多采用组件或插件化,多个模块之间可能存在资源的重复引入,常见的解决方法是通过资源包中的每个ZipEntry的CRC-32 checksum来筛选出重复的资源;通过android-chunk-utils修改resources.arsc,把这些重复的资源都重定向到同一个文件上; 把其它重复的资源文件从资源包中删除。

根据美团的方案之前实践过,并没有落地 实现方式较复杂,并且收益率不太高,本人并没有落地到项目中,有落地的同学可以反馈下实际优化提升。

ENUM减少使用

  如可以在开发过程 尽量减少 enum 的使用,每减少一个 enum 可以减少大约 1.0 到 1.4 KB 的大小

so文件移除

  市面上的手机cpu大多是arm架构的,所以保留arm的一种即可(定制的除外),armeabi-v7aarmeabi都可,其他直接删除。

复制代码
android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi-v7a'
        }
    }
}

  在模拟器调试,就加上x86的架构,在local.properties中变量控制,正式包移除即可

  还有一种情况是不同的第三方库中存在相同的so文件
pickFirst只会打包第一个遇到的冲突的so,merge(碰到冲突会合并)和exclude(直接排除匹配到的文件,不建议使用)

packagingOptions {
       pickFirst 'lib/arm64-v8a/libgnustl_shared.so'
       pickFirst 'lib/armeabi-v7a/libgnustl_shared.so'
}

三方库处理

  实际项目开发中,各个模块都会有不同的移动团队开发,那么就可能存在重复引用的情况,比如某些不同的三方库,可能底层存在依赖同一套库的代码,那么在依赖的时候就可以进行去除

implementation('com.allenliu.versionchecklib:library:2.0.5') {
      exclude group: 'com.android.support', module: 'appcompat-v7'
      exclude group: 'com.android.support.constraint', module: 'constraint-layout'
      exclude group: 'org.greenrobot', module: 'eventbus'
      exclude group: 'com.squareup.okhttp3', module: 'okhttp'
}

三方库整合

  比如RN中使用的图片加载库是 Fresco ,但是Native使用的Glide ,那么我们就可以通过整合此类Case 达到缩减包大小目的。

  还有一种场景 比如一个三方库我们只使用一部分代码,完全可以拉出来魔改下,引入到自己的工具库中,减少多余代码引入。

进阶优化手段

插件化

   依赖于插件化的特点,将整个App拆分为多个模块,每个模块可单独运行 都是APK,最终打包的时候将宿主Apk和插件Apk分开打包,发布的时候只需要发布宿主Apk即可,用户进入不同场景按需动态下载对应Apk即可,可以很大程度上优化包体积问题。

重复技术方案筛选

  如果项目中由于历史包袱,存在多个跨端方案,比如存在 Web、小程序和 Flutter ,如果业务允许,可以将多余跨端方案移除,相对应的引擎So文件即可进行删除,起到减少包体积目的。

So动态化下载

  以Flutter为例,考虑到引擎加载和初始化时间,项目中首页一般还是采取Native方式展示,跨端场景大多在二级页面或非首页场景,按照按需思想,Flutter So也可以不放在本地,我们可以在进入跨端页面路由前,或进入App闲时阶段,添加动态下载So逻辑,相同思想,比如一些音视频文件非启动必须得话,也可以按需下载加载,本地尽量不放大文件。

DebugItem

  JVM 运行时加载的是 .class 文件,而 Android 为了使包大小更加紧凑、运行时更加高效就发明了 Dalvik 和 ART 虚拟机,两种虚拟机运行的都是 .dex 文件,当然 ART 虚拟机还可以同时运行 oat 文件。

  所以 Dex 文件里的信息内容和 Class 文件包含的信息是一样的,不同的是 Dex 文件对 Class 中的信息做了去重,一个 Dex 包含了很多的 Class 文件,并且在结构上有比较大的差异,Class 是流式的结构,Dex 是分区结构,Dex 内部的各个区块间通过 offset 来进行索引。

  为了在应用出现问题时,我们能在调试的时候去显示相应的调试信息或者上报 crash 或者主动获取调用堆栈的时候能通过 debugItem 来获取对应的行号,我们都会在混淆配置中加上下面的规则:

-keepattributes SourceFile, LineNumberTable

这样就会保留 Dex 中的 debug 与行号信息。根据 Google 官方的数据,debugItem 一般占 Dex 的比例有 5% 左右

ReDex

  ReDex 是 Facebook 开发的一个 Android 字节码的优化工具。它提供了 .dex 文件的读写和分析框架,并提供一组优化策略来提升字节码。官方提供预期优化效果:对dex文件优化为 8%

后期维稳

  在打包发布环节,可以编写插件针对各个模块大小或资源文件扫描,设置规则,写进流程中,可以防止包大小爆发式增长,当然好的包大小资源压缩思维,也可以帮助Apk维持在一个健康的水位上。

思考

   以上记录的种种方式,可根据项目状态进行选择优化,抓大放小,实事求是,也不能一味的以包大小越小越好,不影响业务,避免引起线上事故需要放在包大小之前衡量,避免得不偿失。

参考文章

美团 Android App包瘦身优化实践

jsonChao Android包体积优化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/352959.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入Pyecharts:极坐标系绘制与炫酷效果实战【第39篇—python:极坐标系】

文章目录 深入Pyecharts:极坐标系绘制与炫酷效果实战1. 导入必要的库2. 极坐标系基础3. 定制化极坐标系4. 方向性的极坐标系5. 极坐标系的动画效果6. 自定义极坐标轴标签7. 添加极坐标系的背景图8. 极坐标系的雷达图 总结 深入Pyecharts:极坐标系绘制与炫…

Apache Shiro <= 1.2.4反序列化漏洞攻击 CVE-2016-4437 已亲自复现

Apache Shiro < 1.2.4反序列化漏洞攻击 CVE-2016-4437 已亲自复现 漏洞名称漏洞描述影响版本 漏洞复现环境搭建漏洞利用 修复建议总结 漏洞名称 漏洞描述 在 1.2.5 之前的 Apache Shiro 中&#xff0c;当未为“记住我”功能配置密钥时&#xff0c;远程攻击者可以通过未指定…

docker由浅入深

一、什么是docker docker 顾名思义就是轮船的意思&#xff0c;轮船我们知道是通过集装箱运载货物的东西&#xff0c;那么docker其实也是类似的东西&#xff0c;只是装载的是虚拟的运行程序罢了。其中集装箱在docker里面被称为container&#xff08;后面以容器称之&#xff09;…

怎样自行搭建幻兽帕鲁游戏联机服务器?

幻兽帕鲁是一款深受玩家喜爱的多人在线游戏&#xff0c;为了获取更好的游戏体验&#xff0c;许多玩家希望能够自行搭建幻兽帕鲁游戏联机服务器&#xff0c;本文将指导大家如何自行搭建幻兽帕鲁游戏联机服务器。 自行搭建幻兽帕鲁游戏联机服务器&#xff0c;阿里云是一个不错的选…

2024.1.27 GNSS 学习笔记

1.精确的描述轨道的一组数据(星历)是实现精确定位与导航的基础。 2.GNSS卫星广播星历的提供方式一般有两种&#xff1a;一种是提供开普勒轨道参数和必要的轨道摄动改正项参数&#xff0c;如GPS、BDS、Galileo三大系统采用此种模式&#xff0c;还有QZSS系统&#xff1b;另一种是…

Win32 PE图标资源提取(ICO图标提取)

最近需要写一个提取EXE或者DLL图标资源的功能, 网上找了很久, 要么功能不好用, 最后结果如下: 1.很多是加载为HICON句柄后转换为图片保存, 全损画质..., 2.后来找了个还能用的, 详见 https://github.com/TortoiseGit/TortoiseGit/blob/master/src/Utils/IconExtractor.cpp …

力扣1312. 让字符串成为回文串的最少插入次数

动态规划 思路&#xff1a; 通过插入字符构造回文串&#xff0c;要想插入次数最少&#xff0c;可以将字符串 s 的逆序 s 进行比较找出最长公共子序列&#xff1b;可以先分析&#xff0c;字符串 s 通过插入得到回文串 ps&#xff0c;其中间的字符应该不会变化&#xff1a; 若 s…

计算方法实验1:熟悉MATLAB 环境

一、问题描述 熟悉MATLAB 环境。 二、实验目的 了解Matlab 的主要功能&#xff0c;熟悉Matlab 命令窗口及文件管理&#xff0c;Matlab 帮助系统。掌握命令行的输入及编辑&#xff0c;用户目录及搜索路径的配置。了解Matlab 数据的特点&#xff0c;熟悉Matlab 变量的命名规则&a…

log4cplus开源库使用

log4cplus 的github地址&#xff1a;https://github.com/log4cplus/log4cplus 下载链接&#xff1a;log4cplus - Browse /log4cplus-stable/2.0.7 at SourceForge.net 官方文档&#xff1a;log4cplus / Wiki / Home 1.log4cplus配置 &#xff08;1&#xff09;打开解决方案…

迷人的数据结构:揭秘数组和链表的不同

数据结构中的数组和链表的区别 一、简介二、数组的特点和特性三、链表的特点和特性四、数组和链表的对比五、数组和链表的代码实现六、总结 一、简介 数据结构是组织和存储数据的方式&#xff0c;直接影响着程序性能、内存利用和资源管理等关键方面。 数据结构提供了各种方法来…

写点东西《JavaScript 中的递归》

写点东西《JavaScript 中的递归》 您是否曾经发现自己需要在 JavaScript 中循环遍历一个复杂的多维对象&#xff0c;却不知道如何操作&#xff1f; 那么&#xff0c;递归函数到底是什么&#xff1f; 让我们回到我们的树对象。 为什么使用递归&#x1f31f;更多精彩 您是否曾经发…

【前端web入门第二天】01 html语法实现列表与表格

html语法实现列表与表格 文章目录: 1.列表 1.1 无序列表1.2 有序列表1.3 定义列表 2.表格 2.1 表格基本结构2.2 表格结构标签 写在最前,第二天学习目标: 列表 表格 表单 元素为嵌套关系 1.列表 作用:布局内容排列整齐的区域。 列表分类:无序列表、有序列表、定义列表。 1…

动态规划算法题刷题笔记

首先看动态规划的三要素&#xff1a;重叠子问题、最优子结构和状态转移方程。 重叠子问题&#xff1a;存在大量的重复计算 最优子结构&#xff1a; 状态转移方程&#xff1a;当前状态转移成以前的状态 动态规划的解题步骤主要有&#xff1a; 确定 dp 数组以及下标的含义状…

HTML新手教程

HTML入门 教程&#xff1a;【狂神说Java】HTML5完整教学通俗易懂_哔哩哔哩_bilibili 一.初识HTML HyperTextMarkupLanguage&#xff08;超文本标记语言&#xff09; 超文本包括&#xff1a;文字、图片、音频、视频、动画。 HTML5的优势 世界知名浏览器厂商对HTML5的支持市场的…

Spring: alibaba代码规范校验工具checkstyle

文章目录 一、idea配置checkstyle插件二、激活CheckStyle三、配置自动格式化功能四、使用代码格式化 一、idea配置checkstyle插件 下载 Intellij IDEA Checkstyle 插件&#xff1a;File -> setting -> plugin通过关键字CheckStyle-IDEA搜索并安装。 安裝完成后重启idea…

【复现】万户ezoffice协同管理平台 任意文件读取漏洞_30

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 万户ezOFFICE协同管理平台分为企业版和政务版。 解决方案由五大应用、两个支撑平台组成&#xff0c;分别为知识管理、工作流程、沟…

Linux cat,tac,more,head,tail命令 查看文本

目录 一. cat 和 tac命令二. head 和 tail 命令三. more命令 一. cat 和 tac命令 cat&#xff1a;用来打开文本文件&#xff0c;从上到下的顺序显示文件内容。tac&#xff1a;用法和cat相同&#xff0c;只不过是从下到上逆序的方式显示文件内容。当文件的内容有很多的时候&…

LiveGBS流媒体平台GB/T28181常见问题-如何快速查看推流上来的摄像头并停止摄像头推流?

LiveGBS流媒体平台GB/T28181常见问题-如何快速查看推流上来的摄像头并停止摄像头推流&#xff1f; 1、负载信息2、负载信息说明3、会话列表查看3.1、会话列表 4、停止会话5、搭建GB28181视频直播平台 1、负载信息 实时展示直播、回放、播放、录像、H265、级联等使用数目 2、负…

Linux下的进程操作

进程概念 ps -elf&#xff1a;查看操作系统的所有进程&#xff08;Linux命令&#xff09; ctrl z&#xff1a;把进程切换到后台 crtl c&#xff1a;结束进程 fg&#xff1a;把进程切换到前台 获取进程进程号和父进程号 函数原型&#xff1a; pid_t getpid(void); //pid_t…

【阻塞队列】阻塞队列的模拟实现及在生产者和消费者模型上的应用

文章目录 &#x1f4c4;前言一. 阻塞队列初了解&#x1f346;1. 什么是阻塞队列&#xff1f;&#x1f345;2. 为什么使用阻塞队列&#xff1f;&#x1f966;3. Java标准库中阻塞队列的实现 二. 阻塞队列的模拟实现&#x1f35a;1. 实现普通队列&#x1f365;2. 实现队列的阻塞功…