文档章节

Android APK 瘦身实践

boonya
 boonya
发布于 2016/07/21 10:20
字数 3574
阅读 37
收藏 1
点赞 0
评论 0

瘦身前

因为平时就考虑到大小的限制,所以很多工作已经做过了,如下列举现在的状态:

  1. 7.3M(Debug版本)和6.5M(Release版本)
  2. 开启minifyEnabled
  3. 开启shrinkResources
  4. 已经去除不相关的大型库
  5. 图片和代码已经经历过粗略的一轮清理

开始魔鬼瘦身

1. tinypng有损压缩

android打包本身会对png进行无损压缩,不信大家可以看看apk中的图片的大小实际上比你代码工程里的图片要小(针对没进行过无损压缩的那些png图)。
所以,纯粹的进行无损压缩并不会对apk的减小有任何效果,这是我特别想在这里强调的一个经验。
现在大家主流的比较喜欢用的tinypng其实是有损压缩:

https://tinypng.com/
[原文] TinyPNG uses smart lossy compression techniques to reduce the file size of your PNG files…
[翻译] TinyPNG使用智能有损压缩技术,来减少PNG文件的大小…

通过tinypng确实能在尽量少的损失下再减小apk,如果图片资源多或者大的话,效果还是很明显的。
具体减少多少,因为这个处理过程我们是间隔做的,无法准确给出结果,就按200k~500k算吧。

2. png换成jpg

经验发现,一些背景,启动页,宣传页的PNG图片比较大,这些图片图形比较复杂,如果转用有损JPG可能只有不到一半(当然是有损,不过通过设置压缩参数可以这种损失比较小到忽略)。
因为都是大图,所以这种方式能有效减小apk的大小。
这种情况下的apk的减小是不可估量的。

3. jpg换成webp

如果png大图转成jpg还是很大,或者想压的更小,而尽量不降低画质,那么可以考虑一下webp。

android 4.0+才原生支持webp, 但是我们的app是兼容2.3+,所以4.0以下的设备将无法看到图片。

  1. 考虑到我们4.0以下的所有设备比例之和大约在0.44%,非常少
  2. 4.0以下的设备不会崩溃

我们选择不对4.0以下做webp兼容处理,不显示就不显示。否则,要引入webp相关so文件增大apk大小。

通过把下面四张大图换成webp,webp的quality参数按50配置(据说官方评测75是最佳值),清晰度勉强可以接受,这个值大家具体按产品要求来定。
jpg大图转webp
其中安装jpg转webp工具:

Java

brew install webp

1

brew install webp

转换命令如下

Java

cwebp -q input.jpg output.webp// Example:cwebp -q 50 a.jpg a.webp

1

cwebp -q  input.jpg output.webp// Example:cwebp -q 50 a.jpg a.webp

更多下载:https://developers.google.com/speed/webp/docs/precompiled

最终,apk减小了188k。

4. 大图缩小

如果经过上面的步骤,依然存在大图的话,说明确实图有点大了,可能真的有点大了!
所以,要考虑的问题是,是否有必要保证如此的大小?能否缩小?
如果这方面能减小的话,apk瘦身的效果必然又会上一个档次。
这种情况下的apk的减小是不可估量的。

5. 覆盖aar里的一些默认的大图

一些aar库里面包含根本就没有用的图。最典型的是support-v4兼容库中包含一些“可能”用到的图片,实际上在你的app中不会用到。
support库资源替换
我没有把所有图都替换掉,只是把几张大一点点的图(选中的那些图)用1×1的图片替换,如果9patch图的话,要做成3×3的9patch图替换。
support库可能还算好的,就怕有些库引用了一些大图而不自知,可以在/build/intermediates/exploded-aar/下的各个aar库的res目录查找检验。
apk减小了18k。

6. 删除armable-v7包的so

感谢@杨辉__ ,@kymjs张涛的提醒,armable-v7和armable文件夹可以只保留armable。
当然,armable-v7a的库会对图形渲染方面有很大的改进,因为我们主要是一些业务上动态库,所以删掉无大碍。
armable-v7a的so文件列表
apk减小了191k。

7. 微信资源压缩打包

这个方案网上一直在说,之前一直没有需求或者动力实践,在这里感谢一下@裸奔的凯子哥的推荐和交流,他那边的apk可以压小1M,效果还是比较惊人的。
这个步骤我是在后面很多步压缩之后测试的,每个阶段的压缩结果都会有些许出入,所以数据仅供参考。
微信压缩方案结果比较
通过正常压缩,apk包减小了464k。
如果开启7zip,apk包减小了594k。
apk减小了594k。

PS: 关于这个压缩,我集成到了gradle脚本中了,新建了一个Task,大概代码如下:

Java

task compressReleaseApp { // 在现有release的版本上生成到compressed目录下 def appid = "appid" def channel = "abcdefghijkl" def guardJarFile = file('../AndResGuard/andresguard-1.1.jar') def guardConfigFile = file('../AndResGuard/config.xml') def originApkFile = file("../app.${appid}/build/outputs/apk/release/${appid}-release-${rootProject.ext.versionName}-${rootProject.ext.versionCode}-${channel}.apk") def outputDir = file("../app.${appid}/build/outputs/apk/compressed/") def keystoreFile = file(RELEASE_STORE_FILE) // 开始执行压缩命令 def proc = "java -jar ${guardJarFile} ${originApkFile} -config ${guardConfigFile} -out ${outputDir} -signature ${keystoreFile} ${RELEASE_STORE_PASSWORD} ${RELEASE_KEY_PASSWORD} ${RELEASE_KEY_ALIAS}".execute(); proc.waitFor(); println "return code: ${ proc.exitValue()}" + ", stderr: ${proc.err.text}" + " stdout: ${proc.in.text}" }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

task compressReleaseApp {

    // 在现有release的版本上生成到compressed目录下

    def appid = "appid"

    def channel = "abcdefghijkl"

    def guardJarFile = file('../AndResGuard/andresguard-1.1.jar')

    def guardConfigFile = file('../AndResGuard/config.xml')

    def originApkFile = file("../app.${appid}/build/outputs/apk/release/${appid}-release-${rootProject.ext.versionName}-${rootProject.ext.versionCode}-${channel}.apk")

    def outputDir = file("../app.${appid}/build/outputs/apk/compressed/")

    def keystoreFile = file(RELEASE_STORE_FILE)

    // 开始执行压缩命令

    def proc = "java -jar ${guardJarFile} ${originApkFile} -config ${guardConfigFile} -out ${outputDir} -signature ${keystoreFile} ${RELEASE_STORE_PASSWORD} ${RELEASE_KEY_PASSWORD} ${RELEASE_KEY_ALIAS}".execute();

    proc.waitFor();

    println "return code: ${ proc.exitValue()}" + ", stderr: ${proc.err.text}" + " stdout: ${proc.in.text}"

}

config开启了7zip, 部分配置如下:

Java

<?xml version="1.0" encoding="UTF-8"?> <resproguard> <!--defaut property to set --> <issue id="property" > <seventzip value= "true" /> <!-- ... --> </issue> <issue id="whitelist" isactive="true"> <path value ="com.xxx.yyy.R.drawable.emoji_*" /> <path value ="com.xxx.yyy.... /> </issue> <issue id ="compress" isactive="true"> <!-- ... --> </issue> </resproguard>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<?xml version="1.0" encoding="UTF-8"?>

<resproguard>

    <!--defaut property to set  -->

    <issue id="property" >

        <seventzip value= "true" />

        <!--  ...  -->

    </issue>

 

    <issue id="whitelist" isactive="true">

        <path value ="com.xxx.yyy.R.drawable.emoji_*" />

        <path value ="com.xxx.yyy.... />

    </issue>

 

    <issue id ="compress" isactive="true">

        <!--  ...  -->

    </issue>

</resproguard>

详情参考:https://github.com/shwenzhang/AndResGuard
原理介绍:
http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=208135658&idx=1&sn=ac9bd6b4927e9e82f9fa14e396183a8f#rd

8. proguard深度混淆代码

之前为了简单起见,很多包都直接忽略了,现在启动严格模式,把能混淆的都混淆了:
release版本混淆效果
采用微信压缩方案最终效果比较:
最终压缩release版本混淆效果
apk减小了215k。

PS:混淆后,一定要经过严格测试,有时候甚至很难发现错误,比如我开启严格混淆,用了一段时间之后慢慢发现了两个bug,排除了两个包程序才正常。

9. 深度清理代码和资源

有意思的是,无论何时何地去清理代码和资源,总能有新的发现:

  • 新发现或者新引入的无用图片
  • 这几张图怎么一样
  • 这个类好像没有用
  • 没用的类相关的图片也没用
  • 有些图片可以用着色方案替换
  • 有些图片可以用shape来代替
  • hdpi里的ic_luancher.png好像也可以删掉

apk减小了66k。

10. proguard去符号表

之前为了保留调试信息,我们是在Proguard保留了符号表的:

Java

-keepattributes SourceFile,LineNumberTable

1

-keepattributes SourceFile,LineNumberTable

官方渠道我觉得还是尽量保留这个,现在针对推广渠道,只能采用特殊手段,注释这一行。
apk减小了230k。
ps:以后友盟上看推广渠道的bug要辛苦一点,手动上传mapping.txt了。

11. provided关键字

可以对仅在运行时需要的库设置provided关键字,实际并不被打包:

Java

provided 'com.android.support:support-annotations:22.0.0'

1

provided 'com.android.support:support-annotations:22.0.0'

我没有发现这样的场景,如果说有的话,就是support-annotations,但是经过后来的测试验证,support- annotations本来就会在release版本中被minifyEnabled掉,所以对support-annotations设置 provided是没有意义的。
如果有实际场景,欢迎留言说明,不甚感激。
apk没有减小。

12. 表情包在线化

虽然应用的表情不多,只有50来个,但是如果能把这部分表情放到网上,不仅能有效减小apk大小,还可以方便后期扩展支持:
表情列表
打包成emoji_v1.zip, 大小是202k。
现在把emoji_v1.zip放到网上,按需下载后使用,最终对比结果如下:
表情包在线化成效
apk减小了193k。

13. 全版本兼容的着色方案

考虑着色方案主要目的是更方便支持多主题,减轻UI工作量,减少工程里一大堆selector文件等,然后才是,顺便的减小一下apk大小。
通过着色方案,我们去除了10多张纯色的按下状态图片和对应的xml等等。
apk减小了15k。

PS: 具体实现可以参考 http://www.race604.com/tint-drawable/ ,而我也把它集成到了我的LessCode库中了:DrawableLess.java

14. 去除重复库

发现两个地方:

  • 现在发现七牛的SDK引用了android-async-http-1.4.6.jar,虽然不大,只有95.4k,但是感觉完全可以写一个轻量级的jar,控制在10~20k就足够了,具体可以在现有的网络库上实现。
  • 自己工程使用的是UIL,但是引入的第三方库引用了picasso,两个重复的图片下载库也是完全没用必要的。

现在还没有处理这块,新任务介入,延期优化,敬请期待。

15. 去除无用库

这是一个很基本的点,但是确很容易被人忽视,当你仔细回顾的时候,有一些鸡肋的功能或者库,是几无用处的。不如干脆去掉。
比如,在很早的时候,我就把我们app里的sharesdk删除了,因为对于我们的产品定位和推广来看,这毫无意义。
这种情况下的apk的减小是不可估量的。

16. 去除百度统计

这个视具体情况决定。
因为我们的APP里面包含友盟和百度两套统计系统,早期老板要求,事实上后面已经很少看这方面的数据,百度统计的数据几乎没用人去看,可以暂时先去除。
原本的百度统计的jar有130多k,去除之后的apk的减小会远远没有这么多。
apk减小了20k。

17. 使用更小的库

使用更小的库不应该成为你选择方案的决定性因素,但是可以作为参考因素(freso确实太大了,这个大小也可以成为决定性因素)。
图片下载,网络请求,json解析等等的库和它的竞品都有多大,你心里有数吗?
以工具库为例,网上有很多工具库,但是往往它们的大小很难控制。

  • xutils-3.2.6.aar – 843.8k
  • lite-common-1.1.3.jar – 148.1k
  • lesscode-core-0.8.2.aar – 64k

上面最后一个库LessCode是我自己收集的工具类集合,非常小:LessCode,混淆后只有不到50k大小。
不仅提高了开发效率,减少了冗余代码,而且能避免引用一些其他大型的库,有效避免包的增大。
比如,我们碰到过这样的一个bug,快速点击按钮多次触发跳转,现在RxJava结合RxBind有这样的一个场景解决方案,如果引入这些库的话必然会增 大apk大小,实际上就几行代码,我把这样的解决方案集成到了LessCode,下次别的项目碰到这样的问题不用再犹豫是否要引入一个这么大的库了。
这些小的工具库,建议根据自己的经验人手总结一个,不求全,但求精!
这种情况下的apk的减小是不可估量的。

18. 插件化

尴尬的是,我们所呈现的功能大部分都是重要的不可分割的功能,很难从业务上分离出来。
今年预计要实践一个轻量级的插件化方案,用别人的也好,自己写也好,希望能解决或者优化一些安装包加载多模块,或者主题切换,或者热修复的问题。
这里作为候选方案备用。
这种情况下的apk的减小是不可估量的。

19. 功能业务取舍

一开始考虑瘦身,领导是允许适当的砍掉一些功能,因为4M的目标我们已经实现了,所以现在还没有到砍功能的地步。
这里作为候选方案备用。
这种情况下的apk的减小是不可估量的。

补充

文章发出后,收到了一些朋友的建议,补充几点。

1. 去除无用的语言资源

感谢@牧志轩的建议,通过配置resConfigs可以选择只打包哪几种语言,进而去掉各种aar包中全世界的语言,尤其是support包中的。
没选择语言的语言包
选择保留什么语言要根据产品的用户和市场来定,如果只选择默认英语和中文语言,配置如下

Java

android { defaultConfig { resConfigs "zh" } }

1

2

3

4

5

android {

    defaultConfig {

        resConfigs "zh"

    }

}

看看效果:
只选择默认英文和中文的语言包效果
如果不采用微信压缩方案结果对比,apk减小了197k。
如果采用微信压缩(开启7zip)对比结果,apk只减小了16k,因为微信对resources.arsc进行了强力压缩,厉害!
apk减小了16k。

2. 删除x86包的so

再次感谢@杨辉__的建议,x86的包删除了之后,测试反应好像有些机器容易崩溃,未能经过严格测试,所以主版本又复原了,只在个别渠道执行这条措施。
一般情况下不会有问题,测试了一下效果,apk减小了78k。
这里作为候选方案备用。

小结

最终,我们成功的把apk压到了2.9M,如果把上面遗漏的步骤继续再做,应该还能再减小一点。
客户反应压的好小,领导简直不敢相信~
瘦身不难,难的是魔鬼瘦身!

本文转载自:http://android.jobbole.com/82401/

共有 人打赏支持
boonya
粉丝 72
博文 211
码字总数 43922
作品 0
成都
高级程序员
Android插件化开发之动态加载技术学习

Android插件化开发之动态加载技术学习 为什么要插件化开发和动态加载呢?我认为原因有三点: 可以实现解耦 可以解除单个dex函数不能超过65535的限制 可以给apk瘦身,比如说360安全卫士,整个...

幸运券发放 ⋅ 05/18 ⋅ 0

Android系统源码分析团体项目BeesAndroid正式上线啦

嗨,BeesAndroid开源技术小组正式成立啦,Bees,即蜜蜂,取义分享、合作与奉献的意思,这也是BeesAndroid小组的宗旨,我们第一个团体项目BeesAndroid也于2018年3月6日同步上线,该项目的前 ...

郭孝星 ⋅ 03/08 ⋅ 0

APK瘦身看这一篇文章就够了

原创 2016-08-12 张明云 来源:Open软件开发小组 前言 之前我在微信群里面有说到,随着Android开发越来越成熟,关于Android方面的技术文章越来越多,作为开发者不缺资源,缺乏的是系统的知识...

3kqing ⋅ 2016/09/06 ⋅ 0

[Android技术专题]APK瘦身看这一篇文章就够了

前言 之前我在微信群里面有说到,随着Android开发越来越成熟,关于Android方面的技术文章越来越多,作为开发者不缺资源,缺乏的是系统的知识和指导,对于学生和上班族来讲,更缺乏筛选信息的...

张明云 ⋅ 2016/08/11 ⋅ 0

Android 【插件化】"偷梁换柱"的高手-VirtualApk源码解析

关于VirtualApk VirtualApk github : https://github.com/didi/VirtualAPK VirtualAPK wiki : https://github.com/didi/VirtualAPK/wiki 工程介绍 工程结构 CoreLibrary是VirtualApk(以下简称......

qq_17250009 ⋅ 04/12 ⋅ 0

Android动态化框架App Bundles

Android App Bundles 在今年的Google I/O大会上,Google向 Android 引入了新 App 动态化框架(即Android App Bundle,缩写为AAB),与Instant App不同,AAB是借助Split Apk完成动态加载,使用...

code_xzh ⋅ 05/16 ⋅ 0

Android APK瘦身之Android Analyser

打开方式非常简单 直接把APK拖到AS里去即可 Android Analyser 结果发现是so库太过庞大。。。没辙了,我这里的情况可以删去一个arm的,估计可以减小32m左右。。。到时候还要再测试看看,能不能...

qq_36523667 ⋅ 04/21 ⋅ 0

Android app 在线更新那点事儿(适配Android6.0、7.0、8.0)

一、前言 app在线更新是一个比较常见需求,新版本发布时,用户进入我们的app,就会弹出更新提示框,第一时间更新新版本app。在线更新分为以下几个步骤: 在线更新就上面几个步骤,前2步比较简...

codeGoogle ⋅ 04/28 ⋅ 0

Android Flutter 内存机制初探

阿里妹导读:闲鱼技术团队一直在探索如何使用Flutter来统一移动App开发。移动设备上的资源有限,内存使用成了日常开发中的常见问题。那么,Flutter是如何使用内存,又会对Native App的内存带...

b0q8cpra539hafs7 ⋅ 05/23 ⋅ 0

Janus签名漏洞(CVE-2017-13156)原理与利用分析

  *本文原创作者:Tasfa,本文属FreeBuf原创奖励计划,未经许可禁止转载   0×00 简介   前言:去年比较严重的洞,以比较浅显的方式学习记录一下,网上有很多其他深度分析文章,也可借...

FreeBuf ⋅ 05/17 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

从 Confluence 5.3 及其早期版本中恢复空间

如果你需要从 Confluence 5.3 及其早期版本中的导出文件恢复到晚于 Confluence 5.3 的 Confluence 中的话。你可以使用临时的 Confluence 空间安装,然后将这个 Confluence 安装实例升级到你现...

honeymose ⋅ 今天 ⋅ 0

用ZBLOG2.3博客写读书笔记网站能创造今日头条的辉煌吗?

最近两年,著名的自媒体网站今日头条可以说是火得一塌糊涂,虽然从目前来看也遇到了一点瓶颈,毕竟发展到了一定的规模,继续增长就更加难了,但如今的今日头条规模和流量已经非常大了。 我们...

原创小博客 ⋅ 今天 ⋅ 0

MyBatis四大核心概念

本文讲解 MyBatis 四大核心概念(SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper)。 MyBatis 作为互联网数据库映射工具界的“上古神器”,训有四大“神兽”,谓之:Sql...

waylau ⋅ 今天 ⋅ 0

以太坊java开发包web3j简介

web3j(org.web3j)是Java版本的以太坊JSON RPC接口协议封装实现,如果需要将你的Java应用或安卓应用接入以太坊,或者希望用java开发一个钱包应用,那么用web3j就对了。 web3j的功能相当完整...

汇智网教程 ⋅ 今天 ⋅ 0

2个线程交替打印100以内的数字

重点提示: 线程的本质上只是一个壳子,真正的逻辑其实在“竞态条件”中。 举个例子,比如本题中的打印,那么在竞态条件中,我只需要一个方法即可; 假如我的需求是2个线程,一个+1,一个-1,...

Germmy ⋅ 今天 ⋅ 0

Springboot2 之 Spring Data Redis 实现消息队列——发布/订阅模式

一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式,这里利用redis消息“发布/订阅”来简单实现订阅者模式。 实现之前先过过 redis 发布订阅的一些基础概念和操...

Simonton ⋅ 今天 ⋅ 0

error:Could not find gradle

一.更新Android Studio后打开Project,报如下错误: Error: Could not find com.android.tools.build:gradle:2.2.1. Searched in the following locations: file:/D:/software/android/andro......

Yao--靠自己 ⋅ 昨天 ⋅ 0

Spring boot 项目打包及引入本地jar包

Spring Boot 项目打包以及引入本地Jar包 [TOC] 上篇文章提到 Maven 项目添加本地jar包的三种方式 ,本篇文章记录下在实际项目中的应用。 spring boot 打包方式 我们知道,传统应用可以将程序...

Os_yxguang ⋅ 昨天 ⋅ 0

常见数据结构(二)-树(二叉树,红黑树,B树)

本文介绍数据结构中几种常见的树:二分查找树,2-3树,红黑树,B树 写在前面 本文所有图片均截图自coursera上普林斯顿的课程《Algorithms, Part I》中的Slides 相关命题的证明可参考《算法(第...

浮躁的码农 ⋅ 昨天 ⋅ 0

android -------- 混淆打包报错 (warning - InnerClass ...)

最近做Android混淆打包遇到一些问题,Android Sdutio 3.1 版本打包的 错误如下: Android studio warning - InnerClass annotations are missing corresponding EnclosingMember annotation......

切切歆语 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部