你必须要懂的APK瘦身知识

作者:我是吸血鬼 链接:https://www.jianshu.com/p/5921e9561f5f 著作权归作者所有,欢迎投稿。

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

废话不多说了,开始正题。

1.APK结构的那些事

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

APK包含以下目录:

  • META-INF/:包含CERT.SF和 CERT.RSA签名文件以及MANIFEST.MF 清单文件。
  • assets/:包含应用可以使用AssetManager对象检索的应用资源。
  • res/:包含未编译到的资源 resources.arsc。
  • lib/:包含特定于处理器软件层的编译代码。该目录包含了每种平台的子目录,像armeabi,armeabi-v7a, arm64-v8a,x86,x86_64,和mips。
  • resources.arsc:包含已编译的资源。该文件包含res/values/ 文件夹所有配置中的XML内容。打包工具提取此XML内容,将其编译为二进制格式,并将内容归档。此内容包括语言字符串和样式,以及直接包含在resources.arsc文件中的内容路径 ,例如布局文件和图像。
  • classes.dex:包含以Dalvik / ART虚拟机可理解的DEX文件格式编译的类。
  • AndroidManifest.xml:包含核心Android清单文件。该文件列出应用程序的名称,版本,访问权限和引用的库文件。该文件使用Android的二进制XML格式。 来看看淘宝APP的unzip之后的文件目录

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

2.减小 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 文件的代码用于为发布构建启用代码压缩:

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

除了 minifyEnabled 属性外,还有用于定义 ProGuard 规则的 proguardFiles 属性:

getDefaultProguardFile('proguard-android.txt') 方法可从 Android SDK tools/proguard/ 文件夹获取默认的 ProGuard 设置。 提示:要想做进一步的代码压缩,请尝试使用位于同一位置的 proguard-android-optimize.txt 文件。它包括相同的 ProGuard 规则,但还包括其他在字节码一级(方法内和方法间)执行分析的优化,以进一步减小 APK 大小和帮助提高其运行速度。

proguard-rules.pro 文件用于添加自定义 ProGuard 规则。默认情况下,该文件位于模块根目录(build.gradle 文件旁)。

3.优化assets和res中的资源文件

题外话

res/raw和assets的相同点:

两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制。 res/raw和assets的不同点:

  1. res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类。
  2. res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹。 针对不同的情况,对于资源文件有不同的优化策略。一般来讲,对于res/drawable-**ddpi中的png资源可以进行压缩。

3.1 图片资源优化策略

格式压缩

使用TinyPng或者Guetzli进行压缩。Guetzli的使用可以参见我之前写的博文https://www.jianshu.com/p/565e944bb594

使用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。尽量减少使用帧动画,那可是一堆图片呀。

3.2 压缩资源

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

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

资源压缩器目前不会移除 values/ 文件夹中定义的资源(例如字符串、尺寸、样式和颜色)。这是因为 Android 资源打包工具 (AAPT) 不允许 Gradle 插件为资源指定预定义版本。 同时,我们也可以指定哪些资源可以保留下来。

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

<?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" />

resources有以下属性:

  • tools:keep 指出哪些资源会保留
  • tools:discard 指定哪些资源需要剔除
  • tools:shrinkMode 资源压缩模式,有两种取值strict和safe,默认为safe

safe和strict的优化策略:

safe可以简单理解为安全模式,它会尽最大努力检查代码中可能会使用到的资源进行保留,避免运行时错误。

如果你的代码调用 Resources.getIdentifier(),这就表示你的代码将根据动态生成的字符串查询资源名称。当你执行这一调用时,默认情况下资源压缩器会采取防御性行为,将所有具有匹配名称格式的资源标记为可能已使用,无法移除。

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

img_ 前缀的资源标记为已使用。

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

移除未使用的备用资源

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

defaultConfig {
    // 对于国际化支持只打包中文资源,
    resConfigs "zh-rCN"
}

4.lib中资源优化

这里我们主要讲一下lib中动态链接库的优化策略,也就是SO文件。如果你有NDK的开发经验可能会更容易理解一些。

为了支持不同指令集的情况,应用可能会包含armeabi、armeabi-v7a、x86的SO文件等。

目前主流的机型都是支持armeabi-v7a的,并且armeabi-v7a兼容armeabi。所以在一般的开发中我们只需要使用armeabi-v7a 进行ABI支持。

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

题外话,如果运行时找不到SO的话,会导致应用崩溃。

java.lang.UnsatisfiedLinkError: Couldn't load stlport_shared
 from loader dalvik.system.PathClassLoader: findLibrary returned null
at java.lang.Runtime.loadLibrary(Runtime.java:365)
at java.lang.System.loadLibrary(System.java:535)
at com.your.app.NativeClass.<clinit>(Native.java:16)
... 63 more
Caused by: java.lang.UnsatisfiedLinkError: Library stlport_shared not found
at java.lang.Runtime.loadLibrary(Runtime.java:461)
at java.lang.System.loadLibrary(System.java:557)
at com.your.app.NativeClass.<clinit>(Native.java:16)
... 5 more

我们也是有办法应对的,可以参见这个开源项目ReLinker

另外关于SO的优化我会单独拿出来讲一讲。

原文发布于微信公众号 - 刘望舒(liuwangshuAndroid)

原文发表时间:2018-05-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏玉树芝兰

如何用Markdown写论文?

本文用简明的样例,介绍如何使用Markdown和自动化工具帮你处理数学公式、参考文献引用、图片交叉索引等学术论文写作中常见的棘手问题。快来试试吧。

732
来自专栏张戈的专栏

WordPress显示访客UA信息:Show UserAgent纯代码轻度汉化版

前几天在修复留言日期显示错误的问题时,突然觉得可以在这个位置集成一下访客信息,因为见到过不少博客的留言板都有了类似的功能。经过搜索、测试并筛选,最终选定了 Sh...

2805
来自专栏Flutter入门

Flutter入门三部曲(1) - 基础认识

image.png 看到整体的架构图,它是由dart完成上层的framework,然后由通过skia来完成图形的绘制。

2285
来自专栏吴伟祥

Settings -> Plugins 原

Free Mybatis Plugins    (*mapper.java-- *mapper.xml)

592
来自专栏乐百川的学习频道

Python 标准库一览

Python官方教程的最后一个部分就是标准库概览,在这里我们浏览一下标准库,了解一下Python标准库包含了哪些功能。 操作系统和文件操作 os os模块包含了...

1975
来自专栏Flutter入门

Flutter入门三部曲(1) - 基础认识

看到整体的架构图,它是由dart完成上层的framework,然后由通过skia来完成图形的绘制。

330
来自专栏拂晓风起

Loader拉取图片,由于redirect重定向,导致策略文件无效 设置checkPolicyFile后还是无效:需要一个策略文件,但在加载此媒体时未设置 checkPolicyFile 标志

936
来自专栏君赏技术博客

团队自用的iOS CSV多语言工具今天终于发布1.0.0版本

image-20180727105818718 一般第一个为基础语言包不需要处理

1064
来自专栏MelonTeam专栏

从一个小任务开始——Python学习笔记

前记  之前随着阿米尔·汗的电影《摔跤吧,爸爸》大热,本人对这部电影产生了浓厚兴趣。但网上关于这部电影的资源实在太少,于是我把目光转向了公司的SMB服务器。对喜...

2548
来自专栏Crossin的编程教室

【每周一坑】数路径

加入教室的新同学看这里 ☞ 给新同学:编程教室资源索引 另外一些书籍推荐 ☞ 几个以前发过、回复过很多次、比较有用的学习资源 代码方面的问题,欢迎大家在论坛上发...

3108

扫码关注云+社区