Android性能优化-减小APK大小
zhangm53g
8年前
<h3><strong>前言</strong></h3> <p>用户通常会避免下载比较大的应用,特别是连接到2G和3G网络,或者按流量收费的设备。这篇文章描述了如何减小apk的大小,帮助你让更多的用户下载你的app。</p> <h2><strong>一 理解APK的结构</strong></h2> <p>在讨论如何减小apk大小之前,理解apk的结构很有必要。一个APK文件包括一个ZIP 文件,该ZIP包含app的所有文件。包括java 字节码文件,资源文件和一个包含了编译后的资源文件。APK包含以下目录:</p> <ul> <li>META-INF/ :包含了 CERT.SF 和 CERT.RSA 签名文件, 以及 MANIFEST.MF manifest 文件.</li> <li>assets/ : 包含了app的assets,app可以通过 AssetManager 对象获取到这些资源</li> <li>res/ : 包含了那些没有被编译到 resources.arsc 的资源</li> <li>lib/ : 包含了用于软件处理器的编译代码,该目录包含一个子目录,针对不同平台: armeabi , armeabi-v7a , arm64-v8a , x86 , x86_64 , and mips .</li> </ul> <p>一个APK也包含了下面的文件,但只有 AndroidManifest.xml 是强制性的</p> <ul> <li> <p>resources.arsc :</p> <p>包含了编译后的资源。该文件包含了 res/values/ 文件夹下的所有XML内容。打包工具抽取了XML内容,将它编译成二进制格式,并且进行了压缩。该内容包括language strings和styles,以及未直接包含在 resources.arsc 文件中的内容路径。比如layout文件和图片。</p> </li> <li> <p>classes.dex :</p> <p>包含可以被Dalvik/ART 识别,以dex文件格式编译后的代码</p> </li> <li>AndroidManifest.xml :</li> </ul> <p>包含了Android核心mainfest文件。该文件罗列了app名字,版本,访问权限,和引用的library文件。该文件采用二进制XML格式。</p> <h2><strong>二 减少资源的数量和大小</strong></h2> <p>APK的大小对app的加载速度以及内存的使用和电量消耗都有影响。一种减小APK大小的最简单方法就是减少APK的资源文件数量和大小。也可以移除那些app不再使用的资源,或者使用可扩展的 Drawable 对象替代图片文件。这部分讨论了这些方法,以及其它几种减小app资源以便最终达到减小APK总体大小的其它方法。</p> <h3><strong>移除无用资源</strong></h3> <p>使用 lint 工具,AndroidStudio中的一个静态的代码分析工具。可以检测 res/ 目录下那些没有被引用的资源. 当 lint 工具发现了项目中潜在的无用资源,就会打印类似如下的信息:</p> <pre> <code class="language-xml">res/layout/preferences.xml: Warning: The resource R.layout.preferences appears to be unused [UnusedResources] </code></pre> <p>注意: lint 工具不能够扫描 assets/ 目录, assets 资源是通过反射的方式引用的,或者app中引用的其它library 文件。但lint并不会移除资源,它只会提示它们的存在。</p> <p>你引入的Libraries有可能引入了无用的资源。Gradle可以通过在 build.gradle 文件中开启 shrinkResources 来帮你自动的移除这些资源:</p> <pre> <code class="language-xml">android { // Other settings buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } </code></pre> <p>为了使用 shrinkResources ,你应该开启code shrinking,在build处理期间, ProGuard 首先会移除无用的代码但是会保留无用的资源。之后Gradle会移除无用的资源。</p> <p>关于ProGuard和使用Android Studio帮助你减小APK大小的更多信息,</p> <p>在Android Gradle Plugin 0.7以及更高的版本中,你可以声明app支持的配置。Gradle通过 resConfig 和 resConfigs flavors 和 defaultConfig 选项把这些信息传递给构建系统。构建系统会阻止来自其它不受支持的资源出现在APK中,从而减少APK的大小。更</p> <h3><strong>最小化使用Libraries中的资源</strong></h3> <p>在开发App的时候,通常会使用外部libraries去提升app的可用性和功能扩展。比如,你可能会引用 Android Support Library 去提升老设备的用户体验。或者使用 Google Play Services 为app提供自动翻译。</p> <p>如果一个library被设计用于桌面服务,那么就可能包含很多app不需要的对象和方法。为了只保留library中app需要的代码,如果license许可的话,你需要编辑library文件。你也可以使用一个移动端友好的替代库。</p> <p>注意: ProGuard 可以清除一些从library中导入的不需要的代码。但是不会移除一个 library的内部依赖。</p> <h3><strong>只支持特定的分辨率</strong></h3> <p>Android支持非常大的设备集,包括各种屏幕密度。 在Android 4.4(API级别19)及更高版本中,框架支持各种分辨率:ldpi,mdpi,tvdpi,hdpi,xhdpi,xxhdpi和xxxhdpi。 虽然Android支持所有这些分辨率,但你不需要导出光栅化资源到每种分辨率。</p> <p>如果你知道只有一小部分用户使用特定分辨率的设备,请考虑是否需要支持这些分辨率。 如果你不包括特定屏幕密度的资源,Android会自动缩放最初为其他屏幕密度设计的现有资源。</p> <p>如果您的应用只需要缩放的图片,您可以通过在drawable-nodpi /中使用图片的单个版本来节省更多空间。 我们建议每个应用至少包含一个xxhdpi图片版本。</p> <h3><strong>减少动画帧数</strong></h3> <p>逐帧动画可能会大幅增加APK的大小。 图1中展示了一个帧动画被分成多个PNG文件的情况。 每个图像是动画中的一帧。</p> <p>对于添加到动画中的每一帧,都会增加APK中存储的图片数量。 在图1中,图像在应用程序中以30 FPS动画。 如果图像仅以15FPS动画化,则动画将仅需要所需帧的数目的一半。</p> <p><img src="https://simg.open-open.com/show/72c8a81d9d60724d73d5980a5b62b7a3.png"></p> <h3><strong>Use Drawable Objects</strong></h3> <p>一些图像不需要静态图像资源; 框架可以在运行时动态地绘制图像。 相反,Drawable对象(XML中的)可能只会占用APK中的一小部分空间。 此外,XML形式的Drawable对象可以生成符合MaterialDesign指南的单色图像。</p> <h3><strong>减少资源</strong></h3> <p>你可能为同一种图像的不同形式都提供了独立的资源,例如同一图像的有色,阴影或旋转版本。 但是,我们建议你重复使用相同的资源,在运行时根据需要进行自定义。</p> <p>Android提供了几个工具来更改资源的颜色,可以在Android 5.0(API级别21)以及更高版本上使用android:tint和tintMode属性。 对于较低版本的平台,请使用ColorFilter类。</p> <p>您还可以节约那些只是某一种资源做了旋转的资源。 以下代码段提供了一个例子,通过简单地将原始图像旋转180度,将“展开”箭头转换为“折叠”箭头图标:</p> <pre> <code class="language-xml"><?xml version="1.0" encoding="utf-8"?> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/ic_arrow_expand" android:fromDegrees="180" android:pivotX="50%" android:pivotY="50%" android:toDegrees="180" /> </code></pre> <h3><strong>从代码中渲染</strong></h3> <p>You can also reduce your APK size by procedurally rendering your images. Procedural rendering frees up space because you no longer store an image file in your APK.</p> <p>你也可以通过程序对图像渲染来减少APK大小。 通过程序渲染可以节约空间是因为不需要在APK中存储图像文件。</p> <h3>压缩PNG文件</h3> <p>aapt工具可以在构建过程期间优化放置在res / drawable /中的图像资源,以及无损压缩。 例如,aapt工具可以将不需要多于256种颜色的真彩色PNG转换为具有调色板的8位PNG。 这样做会产生质量相同但占用内存较小的映像。</p> <p>但请记得aapt有以下限制:</p> <ul> <li> <p>aapt工具不会收缩asset/文件夹中包含的PNG文件。</p> </li> <li> <p>图像文件需要使用256个或更少的颜色的aapt工具来优化它们。</p> </li> <li> <p>aapt工具可能会填充已压缩的PNG文件。 为了防止这种情况,您可以在Gradle中使用cruncherEnabled标志为PNG文件禁用此过程:</p> </li> </ul> <pre> <code class="language-xml">aaptOptions { cruncherEnabled = false } </code></pre> <h3><strong>压缩PNG和JPG文件</strong></h3> <p>你可以使用像 pngcrush , pngquant , 或 zopflipng 等工具来减少PNG文件大小,而不会损失图像质量。 所有这些工具都可以减少PNG文件大小,同时保持图像质量。</p> <p>pngcrush工具特别有效:此工具在PNG过滤器和zlib(Deflate)参数上迭代,使用每个过滤器和参数的组合来压缩图像。 然后选择产生最小压缩输出的配置。</p> <p>对于JPEG文件,您可以使用 packJPG 等工具将JPEG文件压缩为更紧凑的形式。</p> <h3><strong>使用WebP 文件格式</strong></h3> <p>除了使用PNG或JPEG文件,你还可以使用WebP的图像文件。 WebP格式提供有损压缩(如JPEG)和透明度(如PNG),但可以提供比JPEG或PNG更好的压缩。</p> <p>但是,使用WebP文件格式有一些显着的缺点。 首先,在低于Android 3.2(API级别13)的平台版本中不支持WebP。 第二,系统解码WebP比PNG文件需要更长的时间。</p> <p>注意:Google Play只接受使用PNG格式的图标。 如果你打算通过Google Play发布应用,图标就不能使用其他文件格式(如JPEG或WebP)。</p> <h3><strong>使用矢量图形</strong></h3> <p>你可以使用矢量图形创建分辨率独立的图标和其他可伸缩媒体。 使用这些图形可以大大减少APK体积。 矢量图像在Android中表示为VectorDrawable对象。 使用VectorDrawable对象,100字节的文件可以生成屏幕大小的清晰图像。</p> <p>然而,系统渲染每个VectorDrawable对象需要大量的时间,较大的图像则需要更长的时间才能出现在屏幕上。 因此,只有在显示小图像时才考虑使用这些矢量图形。</p> <h2><strong>三 减少Native和Java代码</strong></h2> <h3><strong>删除不必要的生成代码</strong></h3> <p>确保你能够理解那些任何自动生成的代码部分。 例如,许多协议缓冲工具生成过多的方法和类,可以使应用程序的大小增加一倍或两倍。</p> <h3><strong>删除枚举</strong></h3> <p>单个枚举可能为应用程序的classes.dex文件添加大小为1.0到1.4 KB的大小。 对于复杂的系统或者共享库,这种增加可能比较快迅速。 如果可能,请考虑使用@IntDef注解和 ProGuard 来除去枚举并将它们转换为整数。 这种类型转换保留了枚举的所有类型安全的好处。</p> <h3><strong>减少本地二进制文件的大小</strong></h3> <p>如果你的应用使用本地代码和Android NDK,你还可以通过优化这些代码来减小应用的大小。 两个有用的方式是删除debug标记,不提取本地库。</p> <p>删除Debug符号</p> <p>如果你的应用程序正在开发中并仍需要调试,那么使用debug标记很有意义。 使用Android NDK中提供的 arm-eabi-strip 工具从本地库中删除不必要的调试标记。 之后,再编译release版本。</p> <p>避免提取本地库</p> <p>将.so文件存储在APK中未压缩的文件中,并在应用清单的``元素中将android:extractNativeLibs标记设置为false。 这将防止PackageManager在安装过程中将.so文件从APK复制到文件系统,并且有一个额外的好处,会让app的差分更新变得更小。</p> <h2><strong>四 维护多个精简版APK</strong></h2> <p>你的APK可能包含用户下载但从不使用的内容,例如区域或语言信息。 为了让用户提供最小化的下载,你可以将应用细分为多个APK,并根据屏幕尺寸或GPU纹理支持等因素进行区分。</p> <p>当用户下载您的应用时,其设备会根据设备的功能和设置接收正确的APK。 这样,设备不会接收设备没有的功能的资源。 例如,如果用户拥有的是hdpi设备,那么他们不需要你为更高分辨率设备提供的xxxhdpi资源。</p> <p> </p> <p> </p> <p>来自:http://www.lightskystreet.com/2016/10/17/android-optimize-apk/</p> <p> </p>