安卓app瘦身实践

用户常常避免下载太大的APP,尤其是使用移动流量的情况下,而且太大的APP也会占用更多的内存并消耗更多的资源,导致安装速度和加载速度变慢,特别是在低配手机上,这些情况尤为严重。以下是我在对自己的APK瘦身之路上的一些经验分享。

APK的组成结构

在使用一些很酷的方法来减少APK的大小之前,必须先了解实际的APK文件格式。简单的说,APK是一个包含文件、文件夹的压缩文件。在Android Studio工具栏里,打开build–>Analyze APK, 选择要分析的APK包(Raw File Size表示原文件大小,Download Size表示经过Google play处理压缩后的apk大小)。

AS打开APK的视图(按文件大小排序)
各个文件和文件夹的功能

APK的瘦身方案

1. 整体优化

1.1 插件化

从应用功能扩张的角度看,APK包体积的增大是必然的,然而插件化技术的出现很好的解决了这个问题。通过分离应用中比较独立的模块,然后以插件的形式进行加载。比如爱奇艺Android客户端有很多相对独立的功能,游戏、漫画、文学、电影票、应用商店等,都是通过插件的方式,从服务器下载,然后以插件的额方式加载到我们的主工程。

1.2 重新压缩

一般情况下面,AS直接编译生成的APK里面,.arsc文件是没有进行任何压缩的,我们可以解压APK,重新用压缩软件(WinRAR/7zip)进行压缩,就会发现几乎所有的文件都变小了,特别是.arsc文件,减少的比较多。

1.3 签名方式

Google在Android7.0系统提供了新的apksigner签名工具,相比使用java提供的jarsigner签名工具,APK体积可以减少约5%(依赖文件数量)。产生上述变化的原因是jarsigner是针对每个文件进行了签名,然后针对签名后的文件计算摘要,并写入到META-INF文件夹下的MANIFEST.MF文件里面;而apksigner直接计算所有文件的摘要,写入MANIFEST.MF文件。

新的apksigner工具,已经集成到Android7.0 SDK中了,使用方法可以参考官方文档:
https://developer.android.com/studio/command-line/apksigner.html

2. 资源优化

2.1 移除重复的资源
  • 一套资源
    Android在适配图片资源的时候,如果只有一套资源,低密度的手机会缩放图片,高密度的手机会拉伸图片。我们利用这个特性,存放一套资源图就可以供所有密度的手机使用。综合考虑图片清晰度,静态大小和内存占用情况,建议取720p的资源,放到xhdpi目录。
  • 重复资源
    很多时候,随着工程的增大,以及开发人员的变动,有些资源文件名字不同,但是内容却完全不同。我们可以同过扫描文件的MD5值,找出名字不同,内容相同的图片并删除,做到图片不重复。
2.2 移除无用的资源
  • 通过Lint工具扫描工程资源
    当Lint工具扫描发现无用资源的时候,会输出如下信息,就可以删除这种资源。
res/layout/preferences.xml: Warning: The resource R.layout.preferences appears to be unused [UnusedResources]

需要特别注意的是,需要确保不存在反射,资源拼接等访问这些资源,才可以安全的删除掉这些资源,从而减少资源个数。

  • 通过Gradle参数配置
    如果工程比较大,由主工程和多个子工程组成的话,子工程里面也可能包含很多的无用资源。可以通过设置shrinkResources=true让Gradle移走无用的资源,否则默认情况下,Gradle编译只会移除无用代码,而不会关心无用资源。
    需要特别注意的是shrinkResources依赖于minifyEnabled,必须和minifyEnabled一起用,即打开shrinkResources也必须打开minifyEnabled
android {
    // Other settings
 
    buildTypes {
            release {
                    minifyEnabled true
                    shrinkResources true       
             
                    proguardFiles
                    getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
  • 通过开源扫描工具
    大家可能会发现Lint不是非常好用,当工程里面存在反射,过滤结果非常麻烦。所以我们实现了一个资源扫描的工具(https://github.com/zhuzhumouse/ScanUnusedResouce ),可以过滤掉通过反射调用的资源。原理就是把所有java和xml文件以字符串扫描到内存,然后拿到资源文件(xml,png,jpg等)名称做匹配查找,如果没有匹配到,该资源就是无用资源,可以直接删除。
    该扫描工具可以解决反射调用的问题,但是不能解决资源拼接的问题,还有就是不能处理存在很多资源前缀相同的情况。
2.3 png图片压缩

可以通过使用图片压缩工具对png图片进行压缩,压缩效果比较好的工具有:pngcrush,pngquant,zopflipng等,可以在保持图片质量的前提下,缩减图片的大小。

还可以通过网站对图片进行压缩,如比较有名的www.tinypng.com,该网站对上传的图片自动选择合适的压缩算法,压缩比比较高,但是只支持500张免费图片,更多图片处理是要收费的。

2.4 采用WebP格式

WebP分为有损压缩,无损压缩以及包含透明度的有损压缩。

有损WebP是基于VP8视频编码中的预测编码方法来压缩图像数据;无损WebP基于使用不同的技术对图像数据进行转换;有损WebP(支持透明度)区别于有损WebP和无损WebP,这种编码允许对RGB频道的有损编码同时可对透明度频道进行无损编码。

目前4.2及以上的手机系统已经支持WebP的无损和有损压缩,但是4.0,4.1的手机系统只支持不含透明度的有损压缩。如果应用支持的最低版本(minSdkVersion)是4.0,那么就只能针对不含透明度的图片进行WebP转换了。

在Android Studio 2.3版本及以上,我们可以选中 drawable 和 mipmap 文件夹,右键后选择 convert to webp,将图片转为 WebP 格式。如果Android Stuido版本比较低的话,可以直接通过官方提供的cwebp工具,将png转换为WebP。

两张png转WebP的详情对比图

从以上两张样图的转换结果看,不是所有的图片都有高压缩比,有些图片压缩后反而会增大,比如第二张样图。WebP对色差比较小的图片,压缩比会比较高,任何一种压缩算法只能针对具有某种特点的图片进行压缩,没有万能压缩方法。

2.5 优化库中资源

通常在大型的项目中,都会引入很多系统库和第三方的库。

比如低版本兼容库V4、V7、网络请求库、图片处理库等,如果库中包含一些大图,而我们并不会用到,就可以采用1x1的透明图片替代,达到既能编译通过,又可以缩小库体积的目的。

2.6 大背景图处理

对清晰度要求高的大图片,采用单纯的压缩方法就不能满足UE的要求了,需要找到一种非压缩方式来解决这个问题。

纯色图+后台下载的方式很好的解决了这个问题,客户端先使用纯色图片,然后大图从后端下载,这样只是启动的前几次使用纯色图,以后都会使用大图

2.7 Lottie动画库的使用

动画,尤其是帧动画,一直都是相当占用资源的。现在可以通过Airbnb公司开源的Lottie动画库,直接用json文件来描述动画,然后直接加载绘制出来。

具体使用参考:https://github.com/airbnb/lottie-android

2.8 其他资源策略
  • 首先考虑能否不用图片,比如使用shape代码实现。
  • 其次如果用图片的话,能否优先使用.9图来简化图片。
  • 采用svg矢量图和VectorDrawable类来替换传统的图片。
  • 如果图片只是旋转角度或者颜色不同,可以用代码实现变换。

3. 代码优化

3.1 代码混淆

在gradle使用minifyEnabled进行Proguard混淆的配置,可大大减小APP大小:

android {
    buildTypes {
        release {
            //是否进行混淆
            minifyEnabled true
            //混淆文件的位置
            proguardFile('proguard.cfg')
         }
     }
}
3.2 无用代码扫描

同无用资源扫描方式一样,可以针对无用的代码进行扫描,这里需要关注的一点就是在插件里面通过反射的方法调用的主应用的一些类和方法是不能删除的。

也可以使用SonarQube扫描无用类,以及不同类里面的重复代码。

详情请参考:https://github.com/SonarSource/sonarqube

3.3 剔除R文件

随着项目中资源的增加,会发现生成的dex文件里面R.class文件越来越大。我们知道真正使用资源的地方都是以R.xxx.xxx这种方式访问的,而R.xxx.xx是对应于.arsc文件里面的一个常量值。arsc里面的内容具体如下:

07e455dff96e2b4d9bee3999a0ce7d60.png

通过这两张截图我们可以看出,直接用ID替换资源访问代码R.XXX.XXX,这样R.class文件就没有任何作用了,可以删除它,并且代码里面的资源访问字符串也变成了常量,两个方面都减小了dex的大小。

剔除R文件可以参考开源工具:https://github.com/meili/ThinRPlugin

3.4 注解替代枚举

谷歌官方一直强烈推荐用注解替代枚举,一方面可以缩减包体积,另一方便可以节省内存开销。我们来对比一下,在使用注解和使用枚举两种情况下,生成的class文件内容。

枚举类型源码

public enum MarkViewType3{
    SIMPLE_TEXT_MARK,
    DO_LIKE_MARK,
    BOTTOM_BANNER1,
    BOTTOM_BANNER2,
    TL_GREY_BACKGROUND_RANK,
    /**
     *服务导航mark
     */
    SERVICENAVIRIGHTMARK,
    /**
     *搜索页热点事件,标题、评论、事件
     */
    BOTTOM_COMPOUND_TEXT_BANNER
}

编译生成dex后的class文件

public enum MarkViewType3
{
  static
  {
    DO_LIKE_MARK = new MarkViewType3("DO_LIKE_MARK", 1);
    BOTTOM_BANNER1 = new MarkViewType3("BOTTOM_BANNER1", 2);
    BOTTOM_BANNER2 = new MarkViewType3("BOTTOM_BANNER2", 3);
    TL_GREY_BACKGROUND_RANK = new MarkViewType3("TL_GREY_BACKGROUND_RANK", 4);
    SERVICENAVIRIGHTMARK = new MarkViewType3("SERVICENAVIRIGHTMARK", 5);
    BOTTOM_COMPOUND_TEXT_BANNER = new MarkViewType3("BOTTOM_COMPOUND_TEXT_BANNER", 6);
    $VALUES = new MarkViewType3[] { SIMPLE_TEXT_MARK, DO_LIKE_MARK, BOTTOM_BANNER1, BOTTOM_BANNER2, TL_GREY_BACKGROUND_RANK, SERVICENAVIRIGHTMARK, BOTTOM_COMPOUND_TEXT_BANNER };
  }
}

通过对比可以看到生成的class文件里面,每个变量都是一个对象,并且还有一个value对象数组。

注解的实现源码

public class MarkViewType1{
    public static final int SIMPLE_TEXT_MARK = 0;
    public static final int DO_LIKE_MARK = 1;
    public static final int BOTTOM_BANNER1 = 2;
    public static final int BOTTOM_BANNER2 = 3;
    public static final int TL_GREY_BACKGROUND_RANK = 4;
    /**
     *服务导航mark
     */
    public static final int SERVICENAVIRIGHTMARK = 5;
    /**
     *搜索页热点事件,标题、评论、事件
     */
    public static final int BOTTOM_COMPOUND_TEXT_BANNER = 6;
    @IntDef ({SIMPLE_TEXT_MARK, DO_LIKE_MARK, BOTTOM_BANNER1, BOTTOM_BANNER2, TL_GREY_BACKGROUND_RANK
            , SERVICENAVIRIGHTMARK, BOTTOM_COMPOUND_TEXT_BANNER})
    @Retention(RetentionPolicy.SOURCE)
    public @interface MarkViewType1Anno{
    }
}

生成的class文件

{
  public static final int BOTTOM_BANNER1 = 2;
  public static final int BOTTOM_BANNER2 = 3;
  public static final int BOTTOM_COMPOUND_TEXT_BANNER = 6;
  public static final int DO_LIKE_MARK = 1;
  public static final int SERVICENAVIRIGHTMARK = 5;
  public static final int SIMPLE_TEXT_MARK = 0;
  public static final int TL_GREY_BACKGROUND_RANK = 4;
 
  @Retention(RetentionPolicy.SOURCE)
  public static @interface MarkViewType1Anno
  {
  }
}

注解生成的class文件只是一些常量。

通过上面的代码对比可以看出,常量+注解的形式,一方面可以减小生成的class文件的字节数,另一方面可以减小内存开销。

4. arsc文件优化

在剔除R文件小节中,大家已经看到了.arsc文件内容格式。在整体优化小节中,已经对.arsc进行了比较大的优化,接下来分析一下其它优化方式。

可以采用混淆来缩减资源文件的名称,以及移除未使用的备用资源等方式来优化.arsc文件。如何移除未使用的备用资源,gradle里面

增加如下配置:

android {
    defaultConfig {
        ...
            resConfigs "zh", "zh_CN", "zh_HK", "zh_MO", "zh_TW", "en"
    }
}

5. lib目录优化

只提供对主流架构的支持,比如arm,对于mips和x86架构可以考虑不提供支持,系统会自动提供相应的兼容。

除了插件化,客户端还是用了RN的方案,从而引入了RN的so库。由于RN的so库资源比较大,有2M多,进而引入了RN的so库的插件化。通过so库的插件化,来缩减包体积。RN库的插件化,包体积就缩减了1M多。

APK瘦身中要注意的问题

  • WebP图片的转化过程中,一定要注意资源拼接的情况。比如如果存在vip_1,vip_2,vip_3,vip_4,vip_5等五个资源,要么都转化成WebP,要么都不转,不能处理其中的一部分。

  • 替换一些引导图的时候,一定要打包工具和客户端同时替换。如果客户端把引导图替换成了WebP格式,而打包的时候,由于不同步,该图片又被替换成png格式,就会导致资源加载不成功,进而程序崩溃。

  • 使用apksigner签名工具前,必须先执行zipalign操作;而使用jarsigner签名工具则是先签名,然后再用zipalign优化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,387评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,845评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,091评论 0 246
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,308评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,662评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,795评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,008评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,466评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,687评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,181评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,531评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,177评论 3 239
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,126评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,862评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,734评论 2 274

推荐阅读更多精彩内容

  • 本文会不定期更新,推荐watch下项目。如果喜欢请star,如果觉得有纰漏请提交issue,如果你有更好的点子可以...
    天之界线2010阅读 18,076评论 19 154
  • 最近一直在研究关于apk瘦身方面的知识,看了很多文章受益匪浅。原文地址 http://tech.meituan.c...
    王元_Trump阅读 3,647评论 1 30
  • 1、 前言 如果你对App优化比较敏感,那么Apk安装包的大小就一定不会忽视。关于瘦身的原因,大概有以下几个方面:...
    未来的理想阅读 10,674评论 4 39
  • 最近几周一直在研究如何为APK瘦身,折腾了很久,是时候写篇博客总结一下了,虽然已经准备了下周一要在客户端周会分享用...
    风清袖一阅读 980评论 1 10
  • 1.字符串字面量 2.初始化空字符串 3.字符串可变性 4.使用字符 5.连接字符串和字符 6.格式字符串 7.字...
    诠释残缺阅读 194评论 0 0