一篇文章搞定《APK的包体积优化》

前言

相信跟随过一个完整的中大型项目的同学们,到项目的中后期都会考虑进行包体积优化。
那么这篇文章来帮助大家,能快速的减少你的包体积。
本篇文章的内容为:
1、APK的组成和构建
2、为什么优化(简述)
3、优化的顺序(性价比排序)
4、具体的动手操作
5、总结

APK的组成

组成

Android 项目最终会编译成一个 .apk 后缀的文件,他以:资源文件、Java文件 > dex文件 > APK 这样一个顺序进行构建压缩。因此,他内部由多种不同的文件组成。
那怎么查看APK的组成呢?
那不得不说AS 2.2之后给我们提供的Tool:Analyze APK (当然ApkTool反编译也很好,还有很多工具来分析大家可以自己探索。这里只讲一个性价比高的Analyze APK)
Analyze APK 具有如下功能:

  • 可以直观地查看到 APK 的组成,比如大小、占比等等。
  • 查看 dex 文件的组成。
  • 对不同的 APK 进行对比分析。

怎么查看

1、我们可以 直接将电脑上的 apk 拖进 AS 中就可以自动使用 Analyze APK 打开 apk。
2、或者我们直接双击我们release目录下的app-release包。
打开我们apk的结果如下:
在这里插入图片描述
这样就可以看到我们各项内容的占比大小了
那这些都是些什么呢?

文件 描述
lib so文件,不同架构的cpu
res 编译后的资源文件,drawble、layout
classes(n).dex java文件通过dx编译后的文件
assets 应用程序的资源、字体、音频文件
META-INF 签名信息相关
resources.arsc 二进制资源文件
kotlin 编译后的kotlin文件
AndroidMainfest 清单文件

为什么优化

跑不掉这三大方面的原因。

一、下载转化率

什么是下载转化率,为什么能提高呢?
直接给你举个例子:一个20M和一个80M相同的应用。
1、包体积越小,用户下载等待的时间也会越短(提升用户体验)
2、体积小,下载的时间少,过程中的网络等其他问题导致下载失败的概率小
3、体积大的80M会有用户因为网络问题而放弃,但是20M的很快就下完了,后悔都来不及

二、渠道合作商的要求

一句话概括:有些渠道商会根据你APK大小进行定价,越小越省钱。

三、性能问题

1、安装时间长(体积大当然安装时间长了)
2、运行时的内存占用变大(毕竟我们需要加载资源和Dex类到内存中)
3、占用内存大(ROM)APK是一个压缩的文件,解压之后会更大的。

优化的顺序(性价比排序)

一:看情况

其实优化的顺序是要对于你的APK的实际情况来的,那怎么个实际情况呢?
那就是那部分的占比高,那么我们就要优先关注哪一个方面
比如:下面的图片
在这里插入图片描述
可以看到Apk的整个大小只有28.8MB
其中lib就占用了13MB并且占有率达到了45.5%
其中res占用了10M占有率达到了38%
那么之后肯定要去有限处理lib和res的。

二:常规优化

这里不说特殊情况了,就列举一些性价比比较高的瘦身方案。大家自己来斟酌。

优化一:Lint检测无用资源和代码

Android Studio Lint 查找无用的代码
操作:

Analyze > Run Inspection by Name > Unused resources

检测资源
在这里插入图片描述
在这里插入图片描述
显示结果(我特意创建了一个aaa无用的)
在这里插入图片描述
在这里插入图片描述
之后删除不就完事了吗。。。。。
注意: 因为lint是本地静态扫描,所以动态引用的资源文件并不会识别出来,也会出现在检测列表里。

检测代码
在这里插入图片描述
显示结果:
在这里插入图片描述
注意: 因为lint是本地静态扫描,所以反射、动态引用的class并不会识别出来,也会出现在检测列表里。
ps:说实话检测的代码,真需要斟酌一下,因为大多数项目不是你一个人开发的,有可能是你的战友特意存放的。或者反射和动态引用的。

优化二:图片压缩

注意:SVG的不需要压缩,但是需要注意你使用SVG的场景,如果你是需要动态图像的时候,再去使用SVG。因为SVG是一种矢量图形格式,它支持无限缩放而不会失真,适用于在Android应用中展示需要动画或交互的图像。
其他的那就需要尽量去压缩体积了。
首先要知道我们一般有哪两种方式去做:
这里一般有两种:替换webp和TinyPng压缩

TinyPng
plugins搜索TinyPng安装即可
在这里插入图片描述

但是,需要注意的是,在 Android 的构建流程中,AAPT 会使用内置的压缩算法来优化 res/drawable/ 目录下的 PNG 图片,但这可能会导致本来已经优化过的图片体积变大,因此,可以通过在 build.gradle 中 设置 cruncherEnabled 来禁止 AAPT 来优化 PNG 图片,代码如下所示:

aaptOptions {
    
    
    cruncherEnabled = false
}

Webp
这个就比较简单了,我们去右键我们的图片,之后选择Convert to Webp
在这里插入图片描述
看一下差距大小:

优化 大小
原图 210.05KB
TingPng压缩 70.85KB
webp转化 14.5KB

可以看到WebP还是一种很有效的选择

优化三:开启混淆

大家都知道为了其他人对我们的APK进行反编译,我们会对代码开启混淆的状态。但是开启混淆不仅能保护代码。还能通过缩短变量和函数名以及丢失部分无用信息等方式,精简编译后程序大小,从而缩小应用包体积。

混淆的形式
目前有以下三种混淆的形式

  • 将代码中的各个元素,比如类、函数、变量的名字改变成无意义的名字。例如将 hasValue 转换成单个的字母 a。这样,反编译阅读的人就无法通过名字来猜测用途。
  • 重写 代码中的 部分逻辑,将它变成 功能上等价,但是又 难以理解 的形式。比如它会 改变循环的指令、结构体。
  • 打乱代码的格式,比如多加一些空格或删除空格,或者将一行代码写成多行,将多行代码改成一行。
    所以说,混淆不仅是保障 Android 程序源码安全 的 第一道门槛,而且在一定程度上,使用它能够减小 优化字节码 的大小

混淆的使用

buildTypes {
    
    
    release {
    
    
        // 1、是否进行混淆
        minifyEnabled true
        // 2、开启zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗
        zipAlignEnabled true
        // 3、混淆文件的位置,其中 proguard-android.txt 为sdk默认的混淆配置,
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        signingConfig signingConfigs.release
    }
}

混淆的规则

# * 表示仅保持该包下的类名,而子包下的类名还是会被混淆
-keep class com.json.chao.wanandroid.*
# ** 表示把本包和所含子包下的类名都保持
-keep class com.json.chao.wanandroid.**

# 既保持类名,又保持里面的内容不被混淆
-keep class com.json.chao.wanandroid.* {
    
    *;}

# 也可以使用Java的基本规则来保护特定类不被混淆,比如extend,implement等这些Java规则
-keep public class * extends android.app.Activity

# 保留MainPagerFragment内部类JavaScriptInterface中的所有public内容不被混淆
-keepclassmembers class com.json.chao.wanandroid.ui.fragment.MainPagerFragment$JavaScriptInterface {
    
    
    public *;
}

# 仅希望保护类下的特定内容时需使用匹配符
<init>;     //匹配所有构造器
<fields>;   //匹配所有字段
<methods>;  //匹配所有方法
# 还可以在上述匹配符前面加上privatepublic、native等来进一步指定不被混淆的内 容
-keep class com.json.chao.wanandroid.app.WanAndroidApp {
    
    
    public <fields>;
}
# 也可以加入参数,以下表示用java.lang.String作为入参的构造函数不会被混淆
-keep class com.json.chao.wanandroid.app.WanAndroidApp {
    
    
    public <init>(java.lang.String);
}

# 不需要保持类名,仅需要把该类下的特定成员保持不被混淆时使用keepclassmembers
# 如果拥有某成员,要保留类和类成员使用-keepclasseswithmembers

了解完上面的这些混淆规则之后,相信我们已经能够根据我们当前的应用写出相应的混淆规则了。需要注意的是,在 AndroidMainfest 中的类默认不会被混淆,所以四大组件和 Application 的子类和 Framework 层下所有的类默认不会进行混淆,并且自定义的 View 默认也不会被混淆。因此,我们不需要手动在 proguard-rules.pro 中去添加如下代码:

-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService

而 Application 和四大组件是必须在 AndroidMainfest 中进行注册的,所以如果想要通过 混淆四大组件和 Application、自定义 View 的方式去减小APK的体积是行不通的,因为没有规则去配置如何混淆四大组件和 Application。因此,对于混淆的优化,我们能做的只能是 尽量保证 keep 范围的最小化,以此实现应用混淆程度的最大化。在混淆配置中添加下列规则还可以在混淆之后输出最终的混淆配置:

# 输出 ProGuard 的最终配置
-printconfiguration configuration.txt

R8和D8
R8和D8是谷歌开发的用于Java字节码优化和转换的工具,主要用于优化、转换和缩小Java字节码,可以自动删除未使用的代码、合并重复的代码、优化方法顺序和调用等。
说白了就是Proguard 压缩与优化部分的替代品。(但是替换不了混淆)

D8是Android Studio 3.1 之后的
R8是Android Studio 3.4 之后的
开启和关闭需要在 gradle.properties 中配置如下代码让 App 的混淆去支持 R8

android.enableD8 = true

android.enableR8=true
android.enableR8.libraries=true

优化四:缩减资源

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

buildTypes {
    
    
    release {
    
    
        // 1、是否进行混淆
        minifyEnabled true
        // 2、开启zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗
        zipAlignEnabled true
        // 3、混淆文件的位置,其中 proguard-android.txt 为sdk默认的混淆配置,
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        signingConfig signingConfigs.release
        // 4、移除无用的resource文件:当ProGuard 把部分无用代码移除的时候,
        shrinkResources true
    }
}

优化五:SO文件缩减

统一你的引用库
多个三方库同一个功能的时候,那么这时候不要全部引入,比如:
有人引入了一个 Fresco 图片库,然后这个库你可能不熟悉,你会引入一个 Glide,并且另一个人它可能又会引入他熟悉的图片库 Picasso。那么一个项目中出现了三个功能相同的SDK。这时候你去删除另外两个,岂不是会减小很多的体积。

精准引用你的使用库
比如说你现在只需要一个播放库的播放功能,但是一般音视频的三方库会有很多附带的拉流、解码、推流等等与你播放库不相关的库。
那么这时候就需要删除不相关的so库。那么能有效的减小体积。

保留需要的架构
目前市面上的手机cpu都是arm架构的,所以保留arm的一种即可(定制的除外),armeabi-v7a或armeabi都可,其他直接删除。

android {
    
    
    defaultConfig {
    
    
        ndk {
    
    
            abiFilters 'armeabi-v7a'
        }
    }
}

在这里插入图片描述

三:其他方案

1、自己动手删除开源库的不需要的代码
2、动态加载so文件(按需加载、下载、插件化)
3、插件化加载其他资源(按需加载,收益越大风险越大)
4、H5的融合替换原生文件

总结

总结就是,不要只在开发之后进行想着优化了。
程序员说的最多的就是,”我后面优化“。
要在开发过程中潜移默化的去注意这些点,来提高你开发的质量。
Ok 结束。

猜你喜欢

转载自blog.csdn.net/weixin_45112340/article/details/131653663