Android开发经验谈Android技术知识Android开发

知己知彼,百战不殆——Android 性能优化的那些事

2022-06-12  本文已影响0人  程序老秃子

前言

随着业务复杂度的逐渐增加,代码、资源也在不断的增加,此时你的APP大小也在增加; 从用户层面来说,面对动辄几十兆的APP来说在非WIFI情况下还是会犹豫要不要下载,不下载你就可能因此失去了一个用户;从公司层面来讲,流量就是钱,减少APP的大小就显得尤为重要;从开发者层面上来讲,你掌握了这个手艺也会略显逼格满满

废话不多说了,开始正题

APK结构的那些事

知己知彼,方能百战不殆; 了解应用程序APK的结构对于我们来说很有帮助,APK文件由一个ZIP存档组成,其中包含组成应用程序的所有文件;这些文件包括Java类文件,资源文件和包含编译资源的文件

APK包含以下目录:

来看看淘宝APP的unzip之后的文件目录

一般来讲APK结构中比较大的部分一般是classes.dex、lib、res、assets这些文件或者目录; 所以接下来将会针对这四种情况进行讲解,另外,我们通过APK Analyser 可以分析 APK

减小 classes.dex

classes.dex 包含了所有 Java 代码; 当你编译你的应用时,gradle 会将你的所有模块里的 .class 文件转换成 .dex 文件并将这些文件合成一个 classes.dex 文件;单个的 classes.dex 文件可以容纳大约 64K 方法;如果你达到了这个限制,你必须要在你的工程中启用 multidexing。这将会创建另一个 classes1.dex 文件去存储剩下的方法。所以 classes.dex 文件数目由你的方法数而定

减少第三库的使用

随着业务的频繁变更以及复杂度的增加,我们往往会使用第三方Libaray; 有时候我们可能仅仅用到了很少一部分的功能,这个时候就需要慎重考虑完全引用;从我的开发经验上来讲,宁愿参照自己去实现,也不愿意多引入一个第三方库

避免枚举

一个枚举可以为您的应用程序的classes.dex文件添加大约1.0到1.4 KB的大小,这些添加可以快速累积到复杂系统或共享库; 如果可能,请考虑使用@IntDef注释,这种类型转换保留了枚举的所有类型安全优势

使用ProGuard

下面这段来自 build.gradle 文件的代码用于为发布构建启用代码压缩:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n45" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">android {
 buildTypes {
 release {
 minifyEnabled true
 proguardFiles getDefaultProguardFile('proguard-android.txt'),
 'proguard-rules.pro'
 }
 }
 ...
}</pre>

除了 minifyEnabled 属性外,还有用于定义 ProGuard 规则的 proguardFiles 属性: getDefaultProguardFile('proguard-android.txt') 方法可从 Android SDK tools/proguard/ 文件夹获取默认的 ProGuard 设置

提示:要想做进一步的代码压缩,请尝试使用位于同一位置的 proguard-android-optimize.txt 文件; 它包括相同的 ProGuard 规则,但还包括其他在字节码一级(方法内和方法间)执行分析的优化,以进一步减小 APK 大小和帮助提高其运行速度,proguard-rules.pro 文件用于添加自定义 ProGuard 规则;默认情况下,该文件位于模块根目录(build.gradle 文件旁)

优化assets和res中的资源文件

res/raw和assets的相同点:

res/raw和assets的不同点:

图片资源优化策略

格式压缩

使用TinyPng或者Guetzli进行压缩

使用WebP文件格式

定位Android 3.2(API级别13)或更高级别时 ,您也可以使用WebP文件格式来制作图像,而不是使用PNG或JPEG文; WebP格式提供有损压缩(如JPEG)以及透明度(如PNG),但可以提供比JPEG或PNG更好的压缩

Android 4.0 (API level 14) 支持有损压缩的WebP格式,Android 4.3 (API level 18) 开始支持无损透明WebP图像

看下图:

压缩效率极高,仅为PNG格式的12%

使用矢量图形

您可以使用矢量图形来创建与分辨率无关的图标和其他可伸缩媒体; 使用这些图形可以大大减少您的APK足迹,矢量图像在Android中表示为VectorDrawable对象

通过一个VectorDrawable对象,一个100字节的文件可以生成一个与屏幕尺寸一致的清晰图像; 但是,系统渲染每个 VectorDrawable对象需要很长时间,而较大的图像需要更长的时间才能显示在屏幕上。因此,只有在显示小图像时才考虑使用这些矢量图形

其它策略

有时候我们可能对一张图片进行重复利用,比如一张图片仅仅是整体颜色的变换可以使用setColorFilter或者tint; 尽量减少使用帧动画,那可是一堆图片呀

压缩资源

要启用资源压缩,请在 build.gradle 文件中将 shrinkResources 属性设置为 true

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n75" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">android {
 ...
 buildTypes {
 release {
 shrinkResources true
 minifyEnabled true
 proguardFiles getDefaultProguardFile('proguard-android.txt'),
 'proguard-rules.pro'
 }
 }
}</pre>

资源压缩器目前不会移除 values/ 文件夹中定义的资源(例如字符串、尺寸、样式和颜色);这是因为 Android 资源打包工具 (AAPT) 不允许 Gradle 插件为资源指定预定义版本

同时,我们也可以指定哪些资源可以保留下来

例如:将下边的代码保存在 res/raw/keep.xml,构建不会将该文件打包到 APK 之中

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n79" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
 tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
 tools:discard="@layout/unused2" /></pre>

resources有以下属性:

safe和strict的优化策略:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="text" cid="n91" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());</pre>

img_ 前缀的资源标记为已使用; 在strict模式下,img_前缀的资源会做未使用的处理,因此你需要使用tools:keep手动进行已使用标识

移除未使用的备用资源

我们知道google给我们的apk提供了国际化支持,如适应不同的屏幕分辨率的drawable资源,还有适应不同语言的字符串资源等等,但是在很多情况下我们只需要一些指定分辨率和语言的资源就可以了,这个时候我们可以使用resConfigs方法来配置

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n95" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">defaultConfig {
 // 对于国际化支持只打包中文资源,
 resConfigs "zh-rCN"
}</pre>

lib中资源优化

有些SO库可以采用网络下载,把负担放到用户安装完应用之后;对于哪些SO文件可以放到网络中加载,还需要看具体业务情况

总结

性能优化不是更新一两个版本就可以解决的,是持续性的需求,持续集成迭代反馈; 在实际的项目中,在项目刚开始的时候,由于人力和项目完成时间限制,性能优化的优先级比较低,等进入项目投入使用阶段,就需要把优先级提高;但在项目初期,在设计架构方案时,性能优化的点也需要提早考虑进去,这就体现出一个程序员的技术功底了

需要本文中底层源码的同学 可以 点击 “此处” 即可 获取 完整代码 以及 更多Android学习笔记+源码解析+面试视频

技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面

Android 架构师之路还很漫长,与君共勉

PS:有问题欢迎指正,可以在评论区留下你的建议和感受;

欢迎大家点赞评论,觉得内容可以的话,可以转发分享一下

上一篇下一篇

猜你喜欢

热点阅读