减少apk体积

[TOC]

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

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

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

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中使用<shape>标签生成drawable对象)替换静态图片资源,减少apk的大小

  比如

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <stroke
        android:width="2dp"
        android:color="#2dcaff" />
    
    <gradient 
        android:startColor="@color/transparent"
        android:endColor="@color/transparent"/>
</shape>

  使用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下支持很多格式的图片,例如:PNGJPGWebP,那我们该怎么选择不同类型的图片格式呢? 在Google I/O 2016中提到了针对图片格式的选择,来看下图(图片来源于Image compression for Android developers):

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中未压缩的文件,并在应用清单的<application> </application>元素中将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

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,077评论 25 707
  • 最近几周一直在研究如何为APK瘦身,折腾了很久,是时候写篇博客总结一下了,虽然已经准备了下周一要在客户端周会分享用...
    风清袖一阅读 1,054评论 1 10
  • 本文来自尚妆Android团队青峰发表于尚妆博客 APK瘦身探索 最近几周一直在研究如何为APK瘦身,折腾了很久,...
    尚妆产品技术刊读阅读 1,786评论 1 23
  • 生活需要一种仪式感 出生,在襁褓中诞生,一堆人围着你笑。 满月,一堆人围着你转,怕你喝不饱,吃不饱,睡不够。 学会...
    stitchyan阅读 256评论 0 0
  • 生活就像链条一样环环相扣,不同的是,有人把它编织成勒住脖颈的枷锁,有人把它编织成攀越高山的藤蔓。
    活色生香徐马懿阅读 212评论 0 0