减少apk体积

[TOC]

首先说下apk体积减小的必要性

减小apk的安装时间,增加用户留存,减少CDN流量费用

再这之前,先来看看apk的结构,以微信apk为例,用Android studio打开

减少apk体积_第1张图片
image

可以看到APK由以下主要部分组成:

文件/目录 描述
lib 存放so文件,可能会有armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips,大多数情况下只需要支持armabi与x86的架构即可,如果非必需,可以考虑拿掉x86的部分
assets 应用的资源文件
r 这是经过proguard的资源文件
class*.dex classes文件是Java Class,被DEX编译后可供Dalvik/ART虚拟机所理解的文件格式
META_INF 它包含了APK中所有文件的签名摘要等信息,其中MANIFEST.MF的内容是对apk每个文件进行摘要然后base64一下,进行存储。

CERT.SF内容是对MANIFEST.MF中的每条内容进行进行摘要然后base64一下。

CERT.RSA就是一个pkcs7格式 der编码的证书文件,可以通过openssl获取内容,这里会把CERT.SF文件用私钥计算出签名然后写入CERT.RSA,

META_INF具体内容可以参考[Android签名机制(http://blog.csdn.net/jiangwei0910410003/article/details/50402000)
resources.arsc 编译后的二进制资源
AndroidManifest.xml Android的清单文件

在介绍怎么做之前,先来大概介绍一下App的资源是怎么被打进APK包里的。Android构建工具链使用AAPT工具来对资源进行处理,来看下图(图片来源于Build Workflow):
[图片上传中...(image.png-293ad-1519886265335-0)]

下面来说下具体减少apk size的方法

一. 减少资源的数量和大小,主要有一下几个方面

1.使用drawable(xml中使用标签生成drawable对象)替换静态图片资源,减少apk的大小

  比如



    
    
    

  使用Drawable有什么好处?

   很方便得到一个矩形,圆,椭圆,圆环,很容易维护和修改
   很方便实现圆角,渐变(线性渐变,径向渐变,扫描渐变)
   代替图片作为 View 的背景,减少 apk 的体积(减少 apk 体积最明显最有效的步骤就是去掉图片)
   大图片耗内存,使用 Drawable 节省内存,Android 本身对 Drawable 做了很好的优化(内存优化需要考虑)

  什么情况下选择使用Drawable,而不是使用一张图,反之呢?

   理论上能用 Drawable 的地方就用 Drawable
   如果能够通过 shape 标签就能定义的几何图形就能满足需求,就不用图片来表示
   渐变类型的背景也尽量使用 shape 来实现
   不规则的,复杂的图形还是只能使用图片,比如要一个表示手机的图标,一个人的头像
   有些特殊拉升效果需要使用 .9.png 图片(尽可能的小吧,越小越省内存)

2.删除未使用的资源

  使用lint工具检测未使用的资源,
  删除未使用的资源:主要通过shrinkresource编译时优化无用资源,我以前以为这个关键字是删  除无用资源,其实并非这样,它只是把无用资源替为一个小的虚拟的文件(背景全黑),并非删除它,
  设置了shrinkresource的编译的日志如下
  Skipped unused resource res/drawable-xhdpi-v4/ic_launcher.png: 4366 bytes (replaced with small dummy file of size 67 bytes)

2.图片优化

为了支持Android设备DPI的多样化([l|m|tv|h|x|xx|xxx]dpi)以及用户对高质量UI的期待,美团App中使用了大量的图片,在Android下支持很多格式的图片,例如:PNG、JPG 、WebP,那我们该怎么选择不同类型的图片格式呢? 在Google I/O 2016中提到了针对图片格式的选择,来看下图(图片来源于Image compression for Android developers):

减少apk体积_第2张图片
image

通过上图可以看出一个大概图片格式选择的方法。如果能用VectorDrawable来表示的话优先使用VectorDrawable,如果支持WebP则优先用WebP,而PNG主要用在展示透明或者简单的图片,而其它场景可以使用JPG格式。针对每种图片格式也有各类的优化手段和优化工具

  使用矢量图片

   可以使用矢量图形来创建独立于分辨率的图标和其他可伸缩图片。使用矢量图片能够有效的减少App中图片所占用的大小,矢量图形在Android中表示为VectorDrawable对象。 使用VectorDrawable对象,100字节的文件可以生成屏幕大小的清晰图像,但系统渲染每个VectorDrawable对象需要大量的时间,较大的图像需要更长的时间才能出现在屏幕上。 因此只有在显示小图像时才考虑使用矢量图形。有关使用VectorDrawable的更多信息,请参阅 Working with Drawables。也可参考 Android使用矢量图-简述

  使用WebP

   如果App的minSdkVersion高于14(Android 4.0+)的话,可以选用WebP格式,因为WebP在同画质下体积更小(WebP支持透明度,压缩比比JPEG更高但显示效果却不输于JPEG,官方评测quality参数等于75均衡最佳), 可以通过PNG到WebP转换工具来进行转换。当然Android从4.0才开始WebP的原生支持,但是不支持包含透明度,直到Android 4.2.1+才支持显示含透明度的WebP,在笔者使用中是判断当前App的minSdkVersion以及图片文件的类型(是否为透明)来选用是否适用WebP。见下面的代码片段:

boolean isPNGWebpConvertSupported() {
    if (!isWebpConvertEnable()) {
        return false
    }

    // Android 4.0+
    return GradleUtils.getAndroidExtension(project).defaultConfig.minSdkVersion.apiLevel >= 14
    // 4.0
}

boolean isTransparencyPNGWebpConvertSupported() {
    if (!isWebpConvertEnable()) {
        return false
    }

    // Lossless, Transparency, Android 4.2.1+
    return GradleUtils.getAndroidExtension(project).defaultConfig.minSdkVersion.apiLevel >= 18
    // 4.3
}

def convert() {
    String resPath = "${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/res/merged/${variant.dirName}"
    def resDir = new File("${resPath}")
    resDir.eachDirMatch(~/drawable[a-z0-9-]*/) { dir ->
        FileTree tree = project.fileTree(dir: dir)
        tree.filter { File file ->
            return (isJPGWebpConvertSupported() && (file.name.endsWith(SdkConstants.DOT_JPG) || file.name.endsWith(SdkConstants.DOT_JPEG))) || (isPNGWebpConvertSupported() && file.name.endsWith(SdkConstants.DOT_PNG) && !file.name.endsWith(SdkConstants.DOT_9PNG))
        }.each { File file ->
            def shouldConvert = true
            if (file.name.endsWith(SdkConstants.DOT_PNG)) {
                if (!isTransparencyPNGWebpConvertSupported()) {
                    shouldConvert = !Imaging.getImageInfo(file).isTransparent()
                }
            }
            if (shouldConvert) {
                WebpUtils.encode(project, webpFactorQuality, file.absolutePath, webp)
            }
        }
    }
}

  减少动画帧

   再不影响用户体验的情况下,可适当的减少帧率,进而就减少了帧数,减少资源




二.减少原生和Java代码

 减少本地二进制文件的大小

  如果你的应用使用原生代码和Android NDK,你还可以通过优化代码来减小应用的大小。两个有用的技术是删除调试符号和提取原生库。

   . 删除调试符号

     如果你的应用程序正在开发中并仍需要调试,则使用调试符号很有意义。 使用Android NDK中提供的arm-eabi-strip工具从本机库中删除不必要的调试符号。 之后,你可以编译你的发行版。

   . 避免提取原生库

     将.so文件存储在APK中未压缩的文件,并在应用清单的 元素中将android:extractNativeLibs标记设置为false。 这将防止 PackageManager在安装过程中将.so文件从APK复制到文件系统,并且将具有使你的应用程序的delta更新更小的额外好处。

  混淆(java代码):

   这里不细说混淆了,可以参考这边文章,我觉得写的很好Android混淆,个人再补充点混淆规则之删除日志

-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int w(...);
    public static int d(...);
    public static int v(...);
    public static int i(...);
}

上面的代码加到混淆配置文件里面,可以删除log日志,但是前提是optimize开启, 不要加-dontoptimize, 之前笔者一直加了上面的代码,但是日志依然有,究其原因时因为getDefaultProguardFile('proguard-android.txt'),
这是Android默认的混淆文件,这个文件里面有-dontoptimize,所以一直失败,可以使用proguard-android-optimize.txt这个配置文件

三.resources.arsc的优化

resources.arsc是编译之后的二进制资源,主要维护者资源名字,资源id,资源路径,资源类型,以及字符串资源的值等等,所以直接参考腾讯的方案安装包立减1M--微信Android资源混淆打包工具,其主要原理就是将名字和路径变短,类似于下面的
R.string.name -> R.string.a
res/drawable/icon -> r/s/a

(参考文档)https://tech.meituan.com/android-shrink-overall-solution.html

你可能感兴趣的:(减少apk体积)