Android包体积优化(常规、进阶、极致)

简介: Android包体积优化(常规、进阶、极致)

前言

包大小的重要性已经不需要多说,包大小直接影响用户的下载,留存,甚至部分厂商预装强制要求必须小于一定的值。但是随着业务的迭代开发,应用会越来越大,安装包会不停的膨胀,因此包大小缩减是一个长期持续的治理过程。


提升下载转化率,安装包越小,转化率越高。

降低渠道推广成本。

降低安装时间,文件拷贝、Library解压、编译ODEX、签名校验这些,包体积越大越耗时。

降低运行时内存等等。

环境

Android Studio Arctic Fox | 2020.3.1 Patch 2

AGP 7.0

项目地址:wanandroid_jetpack

优化前

image.png


4.7MB,4.2MB是google play下载的大小,会有压缩。

除了AS自带的Analyzer之外,还有ApkChecker、ClassyShark等工具。


APK的组成

image.png

APK构建流程

image.png

这是官方新版的打包流程,虽然省略了一些步骤,但是大致的流程还是比较清晰的。

再次简化一下:


资源文件、Java文件 > dex文件 > APK

优化思路

APK本质是一个压缩文件,是打包后的产物,那可以作为切入点的阶段就是打包前、以及打包中。


打包前,即减少打包的文件,比如无用的资源、代码;

打包中,对打包中的产物进行压缩,比如资源文件、So文件;

关键词:减少、压缩。


常规操作

1.Lint检测无用资源文件

Analyze > Run Inspection by Name > Unused resources

image.png


检测结果:


确定无用删除即可。


注意:

因为lint是本地静态扫描,所以动态引用的资源文件并不会识别出来,也会出现在检测列表里。


2.Lint检测代码

Analyze > Inspect code

image.png

检测结果:

image.png

因为这个项目是用kotlin写的,所以直接看kotlin目录下的检测结果。


注意:

因为lint是本地静态扫描,所以反射、动态引用的class并不会识别出来,也会出现在检测列表里。


3.图片压缩

推荐使用tinypng在线压缩。


4.TinyPngPlugin

手动压缩毕竟不高效,可以使用TinyPngPlugin一键压缩。

plugins搜索TinyPng安装即可。(新版AS安装完plugin已经不需要重启了)


压缩结果:

image.png

9张图片,可以看到效果还是非常可观的。

如果图片多,效果更加明显。


经过上面的操作,包体积减小4%,这还只是一个4.7MB的APK而已。


5.WebP

那这9张图还能继续优化吗?

可以,WebP格式的体积更小,而已AS也提供了一键转换支持。

image.png

以ic_avatar.png为例:

image.png

6.开启混淆

minifyEnabled true,默认启用R8代码缩减功能。


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

慎用R8,因为:


R8 会忽略试图修改默认优化行为的所有 ProGuard 规则,例如 -optimizations 和 - optimizationpasses。


可以开启混淆,而不使用R8。


android.enableR8=false
android.enableR8.libraries=false

混淆参考:Android混淆从入门到精通


7.缩减资源

shrinkResources true


假如有一些资源文件不确定还用不用,也不敢删,或者不确定需求是否会变更,所以先留着,那这种情况怎么办呢?

可以使用shrinkResources来缩减资源。


 

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

要配合混淆minifyEnabled一起使用才行,原理也很简单,代码移除之后,引用的资源也就变成无用资源了,才可以进一步缩减。


8.so文件缩减

比如集成了一个三方的直播或者浏览器,可能会提供很多so文件,起初可能是一股脑的copy进项目,但并不一定都用的到。

比如各种cpu架构的so:


app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
├── arm64-v8a/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
├── x86/
│   ├── libgameengine.so
│   ├── libothercode.so
│   └── libvideocodec.so
└── x86_64/
    ├── libgameengine.so
    ├── libothercode.so
    └── libvideocodec.so

目前市面上的手机cpu都是arm架构的,所以保留arm的一种即可(定制的除外),

armeabi-v7a或armeabi都可,其他直接删除。
android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi-v7a'
        }
    }
}

如果开发需要模拟器调试,就加上x86的架构,正式包记得去掉,或者在local.properties中用变量控制一下。


这块如果之前没有优化过,而又有很多so文件的话,或许可以减少30%以上,恐怖如斯!


9.移除未使用的备用资源


很多出海的应用会做国际化,但也适配不了这么多的语言。

除了自己app的之外,还有一些官方的、三方的,可以统一配置支持的语言。

image.png

 

defaultConfig {
        resConfigs("en","zh","zh-rCN")
    }

资源文件同理

defaultConfig {
        resConfigs("xxhdpi","xxxhdpi")
    }

10.小结

针对上面的操作做个小结,看看目前效果如何。

image.png


2MB,包体积减少57%,恐怖如斯!


如果是大型项目,收益非常可观。


author:yechaoa

进阶操作

上面只是一些常规操作,下面看一些进阶操作。


1.resources.arsc资源混淆

资源混淆就是将原本冗长的资源路径变短,例如将res/drawable/wechat变为r/d/a。

开源工具AndResGuard。


2.移除无用的三方库

引入之后未使用的,或者是功能下架之后未移除的。


3.功能重复的三方库整合

比如glide和picasso,都是图片库,保留其一即可。


4.ReDex

dex文件是打包中的产物,redex是facebook开源的分包优化方案。

可以参考:ReDex。


5.so动态加载

前面已经做了so文件缩减,但是可能so文件占比还是比较大,可以考虑除了首次启动外的so文件做动态下发。

也就是插件化的思想,按需加载,但是收益很大的同时,风险也很大,有很多case需要考虑到,比如下载时机、网络环境、线程进程,加载失败是否有降级策略等等。


可以参考facebook开源的SoLoader。


6.插件化

按需加载,收益越大风险越大,风险同上。


极致操作

那如果我想做到极致,还有哪些骚操作呢,ok,继续。


1.原生改用H5或小程序等方案

有些功能可能原生做就显得太重,比如各种促销活动,需要加载各种大图,原生既重又不够动态化,这个时候H5是一种很好的替代方案。

但是如果你原本就不支持H5或者小程序的话,接入这种能力可能反而会加大包体积,做好对比。


2.砍功能

有些功能可能想的很美好,但上线之后收益并不大,是否需要重新思考价值点,最好找到数据依托,再跟产品打架。


3.修改三方库的源码,不需要的代码剔除

比如引入了一个功能很齐全的三方库utils,但实际只用到几个,对源码进行抽取也能减少包体积,同时还能减少网络下载的编译时间。

弊端就是升级成本较大。


4.图片网络化

即把图片上传到服务器,通过动态下载的方式减少包体积,弊端就是首次加载的时候依赖网络环境,对加载速度、流量需要做一个平衡。

图片可以预加载,但是流量消耗是无法避免了,如果比较在意流量指标,需要权衡了。


5.DebugItem

DebugItem 里面主要包含两种信息:


调试的信息。函数的参数变量和所有的局部变量。

排查问题的信息。所有的指令集行号和源文件行号的对应关系。

去除debug信息与行号信息,如果不是极致,不推荐。

可以参考支付宝的这篇 支付宝 App 构建优化解析:Android 包大小极致压缩。


6.R Field内联

内联R Field可以解决R Field过多导致MultiDex 65536的问题,而这一步骤对代码瘦身能够起到明显的效果。

美团代码片段:


ctBehaviors.each { CtBehavior ctBehavior ->
    if (!ctBehavior.isEmpty()) {
        try {
            ctBehavior.instrument(new ExprEditor() {
                @Override
                public void edit(FieldAccess f) {
                    try {
                        def fieldClassName = JavassistUtils.getClassNameFromCtClass(f.getCtClass())
                        if (shouldInlineRField(className, fieldClassName) && f.isReader()) {
                            def temp = fieldClassName.substring(fieldClassName.indexOf(ANDROID_RESOURCE_R_FLAG) + ANDROID_RESOURCE_R_FLAG.length())
                            def fieldName = f.fieldName
                            def key = "${temp}.${fieldName}"
                            if (resourceSymbols.containsKey(key)) {
                                Object obj = resourceSymbols.get(key)
                                try {
                                    if (obj instanceof Integer) {
                                        int value = ((Integer) obj).intValue()
                                        f.replace("\$_=${value};")
                                    } else if (obj instanceof Integer[]) {
                                        def obj2 = ((Integer[]) obj)
                                        StringBuilder stringBuilder = new StringBuilder()
                                        for (int index = 0; index < obj2.length; ++index) {
                                            stringBuilder.append(obj2[index].intValue())
                                            if (index != obj2.length - 1) {
                                                stringBuilder.append(",")
                                            }
                                        }
                                        f.replace("\$_ = new int[]{${stringBuilder.toString()}};")
                                    } else {
                                        throw new GradleException("Unknown ResourceSymbols Type!")
                                    }
                                } catch (NotFoundException e) {
                                    throw new GradleException(e.message)
                                } catch (CannotCompileException e) {
                                    throw new GradleException(e.message)
                                }
                            } else {
                                throw new GradleException("******** InlineRFieldTask unprocessed ${className}, ${fieldClassName}, ${f.fieldName}, ${key}")
                            }
                        }
                    } catch (NotFoundException e) {
                    }
                }
            })
        } catch (CannotCompileException e) {
        }
    }
}

同时可以参考字节开源的shrink-r-plugin,还有滴滴开源的booster。


7.图片着色器

针对同图不同色的处理,可以使用tint,比如原本是一个黑色的返回icon,现在另一个页面要用白色了,就不需要两张图了,而是使用tint来修改为白色即可。



<ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/ic_back_black"
                android:tint="@android:color/white" />

8.减少ENUM的使用

每减少一个ENUM可以减少大约1.0到1.4 KB的大小。


包体积监控

包体积监控应该作为发布流程的一个环节,最好是做到平台化、流程化,否则很难持续,没几个版本包体积又涨上来了。


大致思想:当前版本与上一个版本的包大小做对比,超过200KB需要审批。临时审批需要给出后续优化方案等等。


参考文档

Improve your code with lint checks

Shrink, obfuscate, and optimize your app

Android App包瘦身优化实践

ReDex

SoLoader

支付宝 App 构建优化解析:Android 包大小极致压缩

AndResGuard

深入探索 Android 包体积优化

Android开发高手课包体积优化

目录
相关文章
|
29天前
|
移动开发 监控 前端开发
构建高效Android应用:从优化布局到提升性能
【7月更文挑战第60天】在移动开发领域,一个流畅且响应迅速的应用程序是用户留存的关键。针对Android平台,开发者面临的挑战包括多样化的设备兼容性和性能优化。本文将深入探讨如何通过改进布局设计、内存管理和多线程处理来构建高效的Android应用。我们将剖析布局优化的细节,并讨论最新的Android性能提升策略,以帮助开发者创建更快速、更流畅的用户体验。
50 10
|
13天前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
37 20
Android经典面试题之图片Bitmap怎么做优化
|
15天前
|
Java Android开发 UED
安卓应用开发中的内存管理优化技巧
在安卓开发的广阔天地里,内存管理是一块让开发者既爱又恨的领域。它如同一位严苛的考官,时刻考验着开发者的智慧与耐心。然而,只要我们掌握了正确的优化技巧,就能够驯服这位考官,让我们的应用在性能和用户体验上更上一层楼。本文将带你走进内存管理的迷宫,用通俗易懂的语言解读那些看似复杂的优化策略,让你的开发之路更加顺畅。
26 2
|
16天前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
30天前
|
Ubuntu Android开发
安卓系统调试与优化:(一)bootchart 的配置和使用
本文介绍了如何在安卓系统中配置和使用bootchart工具来分析系统启动时间,包括安装工具、设备端启用bootchart、PC端解析数据及分析结果的详细步骤。
71 0
安卓系统调试与优化:(一)bootchart 的配置和使用
|
13天前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
29 0
|
26天前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
104 0
|
27天前
|
存储 缓存 前端开发
安卓开发中的自定义控件实现及优化策略
【8月更文挑战第31天】在安卓应用的界面设计中,自定义控件是提升用户体验和实现特定功能的关键。本文将引导你理解自定义控件的核心概念,并逐步展示如何创建一个简单的自定义控件,同时分享一些性能优化的技巧。无论你是初学者还是有一定经验的开发者,这篇文章都会让你对自定义控件有更深的认识和应用。
|
1月前
|
Android开发
Android项目架构设计问题之使用动态代理来优化GoodsApiImpl中的接口实现如何解决
Android项目架构设计问题之使用动态代理来优化GoodsApiImpl中的接口实现如何解决
16 0
|
1月前
|
存储 缓存 Java
Android项目架构设计问题之优化业务接口数据的加载效率如何解决
Android项目架构设计问题之优化业务接口数据的加载效率如何解决
33 0