安装包的构成

1 assets

assets目录可根据应用需求存放任何文件夹架构,比如配置文件,资源文件,图片资源,这些文件的内容再程序运行过程中可以通过AssetManager类获得。和res不同在于,res下的文件会在R文件中生成对应的资源ID,assets不会生成ID

2 lib

存放C/C++写的库文件,4个类型:ARM、ARM-v7a、MIPS、X86

目前的移动设备大部分是基于ARM或者ARM-v7a架构的,X86和MIPS架构的移动智能终端比较少,并且X86的设备基本都兼容了ARM的指令集(会影响功耗),所以再应用程序中,native的程序使用频率不高或者运算并不复杂的情况下,只包含ARM和ARM-v7a的so,如果不需要使用到neon的指令集,就只包含ARM的编译的so即可。

3 res

resource的缩写,这个目录存放资源文件,包含的多个文件夹由资源的类型和设备硬件的类型两个维度区分,xhdpi,xxhdpi这些虽然兼容性高了,缺因为更多的资源导致安装包增长,所以尽量用代码实现自适应,除非有特别要求的资源文件还是要通过这个适配一下。

4 META-INF

保存应用的签名信息,签名信息可以验证APK文件的完整性。打包时回计算APK中所有文件的完整性,并把这些完整性保存道META-INF文件夹下,应用程序在安装时首先根据这个文件夹校验APK的完整性,可以保证apk中的每一个文件都不能被篡改。以此来确保APK应用程序不被恶意修改或者病毒感染,有利于确保应用的完整性和系统的安全性。META-INF目录下包含的文件有CERT.RSA、CERT.DSA、CERT.SF和MANIFEST.MF,其中CERT.RSA是开发者利用私钥对APK进行签名的签名文件,CERT.SF、MANIFEST.MF记录了文件中的文件的SHA-1哈希值

5 AndroidManifest.xml

相当于Android应用向系统作自我介绍的配置文件,注册四大组件,包含了对一些权限的声明以及使用的SDK版本信息。打包时会编译这个文件,便于系统识别,编译之后的格式是AXML,包含了以下内容

1.axml头:其中的axml头是固定标示axml文件的,其值固定为0x00090003.
2.axml长度:表示了axml的大小
3.StringDataSegment:XML文件中所有字符串类型保存在此
4.ResourceIdSegment:XML文件中声明的资源ID保存于此
5.XmlContentSegment:是XML的内容段,按照XML文件中的结构依次排开,保存XML数据内容

6 classes.dex

Java可执行程序,需要先把Java文件编译成class文件,字节码都保存在class文件中,Java虚拟机可以通过解释并执行这些class文件。而Dalvik虚拟机再Java虚拟机进行了优化,执行的是Dalvik字节码,这些Dalvik字节码由Java字节码转化而来,一般情况下,应用在打包时通过AndroidSDK中的dx工具将Java字节码转换成Dalvik字节码。dx工具可以对多个class文件进行合并重组、优化,达到减小体积,缩短运行时间的目的。

7 proguard.cfg

代码混淆配置文件。

8 resources.arsc

记录资源文件和资源ID之间的映射关系,用来根据资源ID寻找资源。Android的开发是分模块的,res目录专门用来存放资源文件,在代码中需要调用资源文件时,只需要调用findViewById()就可以得到资源文件,每当再res文件夹下放一个文件,aapt就会自动生成对应的Id保存再R文件中,调用这个ID就可以,但是只有这个ID还不够,R文件只是保证编译程序不报错,实际上在程序运行期间,系统要根据ID寻找对应的资源路径,而resources.arsc文件就是用来记录这些ID和资源文件位置对应关系的文件。
由此可见,APK中的classes.dex、lib
、资源文件是大头,APK瘦身主要就是优化这三类。

减少安装包大小的常用方案

代码混淆

ProGuard是一个开源的Java代码混淆器,默认集成道SDK中,有以下功能

  • 压缩:移除无效的类、属性、方法等。
  • 优化:移除没用的结构
  • 混淆:把类名、属性名、方法名替换为一到两个字母。

常用参数 具体参数参考表

  1. ProGuard配置
    -include{filename} : 从定义的文件中读取配置参数。
    -basedirectory {directoryname} : 指定基础目录为以后对应的档案名称。
    -injars{class_path} : 指定要处理的应用程序jar、war和目录
    -outjars{class_path} : 指定处理完后要输出的jar、war和目录的名称。
    -libraryjars{class_path} : 指定要处理的应用程序jar、war和目录所需的程序库文件。
    -dontskipnonpubliclibraryclasses : 指定不忽略非公共的库类
    -dontskipnonpubliclibraryclassmembers : 指定不忽略包可见的库类的成员。
  2. 保留选项
    -keep{Modifier}{class_specification} : 保护指定的类文件和类的成员。
    -keepclassmembers{modifier}{class_specification} : 保护指定类的成员
    -keepclasseswithmembers{class_specification} : 保护指定的类和类的成员,但条件是所有指定的类和类的成员要存在。
    -keepnames{class_specification} : 保护指定的类和类的成员名称(如果不需要混淆)
    -keepclassmembernames{class_specification} : 保护指定的类的成员名称(不需要混淆)。
    -keepclasseswithmembernames{class_specification} : 保护指定的类和类的成员名称,如果所有指定的类成员出席(在压缩步骤之后)。
    -printseeds{filename} : 列出类和类的成员 -keep 选项的清淡,标准输出到给定的文件。
  3. 压缩
    -dontshrink:不压缩输入的类文件
    -printusage{filename} : 输出无用文件
  4. 优化
    -dontoptimize: 不优化输入的类文件
    -assumenosideeffects{class_specification} : 优化时假设指定的方法,没有任何副作用。
    -allowaccessmodification: 优化时允许访问并修改有修饰符的类和类的成员。
  5. 混淆
    -dontobfuscate: 不混淆输入的类文件
    -printmapping{filename} : 输出映射表
    -applymapping{filename} : 重用映射增加混淆。
    -obfuscationdictionary{filename} : 使用给定文件中的关键字作为要混淆方法的名称。
    -overloadaggressively: 混淆时应用侵入式重载。
    -useuniqueclassmembernames: 确定统一的混淆类的成员名称来增加混淆。
    -renamesourcefileattribute{string}: 设置源文件中给定的字符串常量。

在studio中使用

按照上边规则写完proguard-rules.pro后,再build.gradle中配置minifyEnabled为true

buildTypes {
    debug {
        minifyEnabled false
    }
    release {
        signingConfig signingConfigs.release
        minifyEnabled true
        proguardFiles 'proguard-rules.pro'
    }
}

资源优化

使用Lint删除冗余资源
Refactor -> Remove Unused Resources

assets 文件夹下的扫描不到,需要手动删除

资源文件最少化

1、尽量使用一套图片资源,遇到一些图片在不同分辨率手机上变化差异过大的情况时,再考虑再相应文件夹下放入这个特定的图片。
2、使用一套图,一套布局,多套dimens.xml文件,在使用最小资源的情况下,解决多分辨率适配。
3、使用轻量级的第三方库。
4、减少项目中的预制图片,预制图片改成由服务器下发,尽可能将程序与资源分离。

图片优化

PNG用来存储灰度图像时,灰度图像的深度最多到16位,存储彩色图像时,深度最多到48位。并且可存储多达16位的透明通道数据。JPG以24位颜色存储单个位图。JPG是与平台无关的格式,支持最高级别的压缩,有损耗且没有透明通道。

png在aapt打包时会自动压缩,但是压缩的有限,如果使用图片色彩单一,可以降低图像位数来减少图片大小,还有就是可以用tinyPng,Zopfli,PNGoo来压缩

其他优化

避免重复功能的库:能扩展的库就尽量不要引入新的库

使用WebP图片格式:支持透明度,压缩比JPG更高,但效果更好,4.2.2以上系统才支持。

插件化:需要临时下载,建议在用户使用率不高的功能模块上使用,或者使用预加载。