apk的打包和优化

Posted 爱炒饭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了apk的打包和优化相关的知识,希望对你有一定的参考价值。

一、打包过程与工具

借用下android 打包流程一文画的apk打包流程图。

1.1 aapt

AAPT(Android Asset Packaging Tool,Android 资源打包工具)是一种构建工具,将android资源文件打包输出 resources.ars、R.java,Android Studio 和 Android Gradle 插件使用它来编译和打包应用的资源,将。Android Gradle 插件 3.0.0 及更高版本默认情况下会启用 AAPT2,AAPT2 会解析资源、为资源编制索引,并将资源编译为针对 Android 平台进行过优化的二进制格式。下载 SDK Build Tools 后,您可以在 android_sdk/build-tools/version/ 下找到 AAPT2。

(1)aapt2 dump用于输出有关您使用 link 命令生成的 APK 的信息。例如,以下命令的输出结果为所指定 APK 的资源表中的内容:

aapt2 dump resources output.apk

 (2)aapt2 compile用于编译资源,生成一个扩展名为 .flat 的中间二进制文件,一般语法如下:

aapt2 compile path-to-input-files [options] -o output-directory/

 (3)aapt2 link,在链接阶段,AAPT2 会合并在编译阶段生成的所有中间文件(如资源表、二进制 XML 文件和处理过的 PNG 文件),并将它们打包成一个 APK。

1.2 javac

javac工具位于java安装目录的bin文件夹下,可以将.java文件成为.class文件

java工具位于java安装目录的bin文件夹下,可以 运行.class 程序

1.3 proguard

ProGuard 是一个开源的 Java 字节码的压缩,优化,混淆器。它检测并删除没有用的类,字段,方法与属性;优化字节码,移除无用指令;使用简短且无意义的名字来重命名类、字段和方法。ProGuard 的输入输出都是class文件,AndroidStudio 3.4.0 版本之前使用ProGuard 来优化字节码,只需在module的build.gradle中设置minifyEnabled true即可。

1.4 dx/d8

dx/d8可以将Java 字节码编译为在 Android 设备上运行的 DEX 字节码,可以在android_sdk/build-tools/version/找到这两个工具。二者的区别是d8是dx的升级版,d8支持Java 8 语言功能,android Dex 方案的问题在于基本只能完整地支持Java6 SE,Java 后续新版本引入的语言特性并不能直接就能用在 Android 开发中,d8 通过一个叫做“脱糖”的编译过程,使您能够在代码中使用 Java 8 语言功能,此过程会将这些实用的语言功能转换为可以在 Android 平台上运行的字节码。Google 自己的基准测试中,D8 相比之前的 DX 方案,编译时间缩短了20%,而且.dex文件更小。

d8 简单易用,只需要指向要转换为 DEX 字节码的已编译 Java 字节码的路径即可,如下所示。

d8 path-to-input-files [options]
d8 MyProject/app/build/intermediates/classes/debug/*/*.class

 dx使用和d8类似,使用如下格式

dx --dex [ OPTIONS ] FILES ... 

 1.5 R8

d8是dx的升级,r8又是d8的拓展,r8同时拥有了d8和proguard的功能,因为proguard不是google主导项目,搞个亲儿子岂不更好。在使用 Android Gradle 插件 3.4.0 或更高版本构建项目时,该插件默认使用 R8 编译器而不是 ProGuard 来执行编译时代码优化。在 gradle.properties 文件中可以通过开关来切换:

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

R8完全兼容ProGuard 的语法规则,所以可以无缝无感知的替换ProGuard,性能上二者差别不大。

1.6 ApkBuilder

ApkBuilder.java位于gradle目录sdklib-25.1.3-sources.jar中,主要是打包资源文件、源码、库等到apk中

 

1.7apksigner

apksigner 位于android_sdk/build-tools/version/ 目录下,作用是为 APK 签名,并确保 APK 的签名能够在 APK 支持的所有版本的 Android 平台上成功通过验证。

使用 apksigner 工具为 APK 签名的语法如下:

apksigner sign --ks keystore.jks |
  --key key.pk8 --cert cert.x509.pem
  [signer_options] app-name.apk

 

1.8 zipalign

zipalign位于android_sdk/build-tools/version/ 目录下, 是一种 zip 归档文件对齐工具。它可确保归档中的所有未压缩文件相对于文件开头都是对齐的。这样一来,您便可直接通过 mmap(2) 访问这些文件,而无需在 RAM 中复制相关数据并减少了应用的内存用量。注意:您必须在应用构建过程中的两个特定时间点之一使用 zipalign,具体在哪个时间点使用,取决于您所使用的应用签名工具:

  • 如果您使用的是 apksigner,只能在为 APK 文件签名之前执行 zipalign。如果您在使用 apksigner 为 APK 签名之后对 APK 做出了进一步更改,签名便会失效。
  • 如果您使用的是 jarsigner,只能在为 APK 文件签名之后执行 zipalign。

二、apk体积优化

打算优化apk大小,首先需要看下一个apk的占用资源分布,下面是一个1.326MB的apk各部分大小分布情况,可以看到排名前三的分别是代码、资源文件、资源映射文件

2.1代码dex优化

android studio打包apk时如果将 minifyEnabled 属性设为 true,系统会默认启用 R8 代码对代码dex大小进行优化.

2.1.1代码缩减

从应用及其库依赖项中检测并安全地移除不使用的类、字段、方法和属性。例如,如果您仅使用某个库依赖项的少数几个 API,那么缩减功能可以识别应用不使用的库代码并仅从应用中移除这部分代码。

为了缩减应用的代码,R8 首先会根据组合的配置文件集确定应用代码的所有入口点。这些入口点包括 Android 平台可用来打开应用的 Activity 或服务的所有类。从每个入口点开始,R8 会检查应用的代码来构建一张图表,列出应用在运行时可能会访问的所有方法、成员变量和其他类。系统会将与该图表没有关联的代码视为执行不到的代码,并可能会从应用中移除该代码。

图 1 显示了一个具有运行时库依赖项的应用。R8 通过检查应用的代码,确定可以从 MainActivity.class 入口点执行到的 foo()、faz() 和 bar() 方法。不过,您的应用从未在运行时使用过 OkayApi.class 类或其 baz() 方法,因此 R8 会在缩减应用时移除该代码。

 

2.1.2混淆

缩短类和成员的名称,从而减小 DEX 文件的大小。混淆处理的目的是通过缩短应用的类、方法和字段的名称来缩减应用的大小。下面是使用 R8 进行混淆处理的一个示例:

androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
    android.content.Context mContext -> a
    int mListItemLayout -> O
    int mViewSpacingRight -> l
    android.widget.Button mButtonNeutral -> w
    int mMultiChoiceItemLayout -> M
    boolean mShowTitle -> P
    int mViewSpacingLeft -> j
    int mButtonPanelSideLayout -> K

虽然混淆处理不会从应用中移除代码,但如果应用的 DEX 文件将许多类、方法和字段编入索引,那么混淆处理将可以显著缩减应用的大小。

2.1.3 优化

检查并重写代码,以进一步减小应用的 DEX 文件的大小。下面是此类优化的几个示例:

  • 如果您的代码从未采用过给定 if/else 语句的 else {} 分支,R8 可能会移除 else {} 分支的代码。
  • 如果您的代码只在一个位置调用某个方法,R8 可能会移除该方法并将其内嵌在这一个调用点。
  • 如果 R8 确定某个类只有一个唯一子类且该类本身未实例化(例如,一个仅由一个具体实现类使用的抽象基类),它就可以将这两个类组合在一起并从应用中移除一个类。

2.1.4 三方优化方案

(1)修改proguard-rules.pro,去掉debugItem

debugItem位于 dex 文件的data 区中,里面主要包含两种信息:

  1. 函数的参数变量和所有的局部变量,平时在用 IDE 进行断点和单步调试的时候都会用到这个区域。
  2. 所有的指令集行号和源文件行号的对应关系有什么用呢?上报 crash 或者主动获取调用堆栈的时候用。因为虚拟机真正执行的时候是执行的指令集,上报堆栈会上报 crash 的对应源文件行号,此时正是通过这个 debugItem 来获取对应的行号,

对于release版本,为了搜集异常信息一般都需要通过在proguard-rules.pro中keep住文件名和行号避免被混淆。支付宝提出了使用proguard在删除debugItem的同时dump一份出来放到服务器,线上用户版本去掉debugItem从而缩减dex。

#抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable

 本人试了上面的上面1.326MB的apk去掉-keepattributes SourceFile,LineNumberTable后大小为1.278,dex文件缩小了(715-667)/715=6.7%。

(2)修改proguard-rules.pro,减少keep **非 exported** 的四大组件以及 View

默认状态下,应用都会 keep 住四大组件以及 View 的部分方法,这样是为了在代码以及 XML 布局中可以引用到它们。实际上可以把**非 exported** 的四大组件以及 View 混淆,但需要注意

XML 替换。在代码混淆之后,需要同时修改 AndroidManifest 以及资源 XML 中引用的名称

代码替换。需要遍历其他已经混淆好的代码,将变量或者方法体中定义的字符串也同时修改。需要注意的是,代码中不能出现经过运算得到的类名,这种情况会导致替换失败。

可以参考饿了么曾经开源过一个可以实现四大组件和 View 混淆的组件Mess

(3)redex

由于apk方法数65536的限制,大一点的apk可以会有多个dex文件,比如classes.dex、classes2.dex……,多dex间方法或者变量相互调用的场景会出现信息冗余,有兴趣的话可以参考Facebook 的开源工具ReDex

(4)app-bundle(https://developer.android.com/platform/technology/app-bundle/)插件化

可以先打包一个基础apk,后面根据用户需求以及个性化配置通过插件化的形式动态下载拓展,比如某些换肤框架、美颜种类等。为支持插件化,Google官方也推出了Android App Bundle,对于 GooglePlay上的apk,在下载时会根据设备配置下载不同的so库以及资源素材。

(5)Dex 压缩

dex一般是资源大头,为此可以在classes.dex中创建一个基本框架,然后将主题的dex压缩一下,在安装的时候再去解压,这样就可以减少apk大小,缺点就是解压dex需要事件, 这样会增加apk安装时间,Facebook的开源项目oatmeal则可以大大缩短apk安装加载时间。

(6)避免使用枚举 

单个枚举会使应用的 classes.dex 文件增加大约 1.0 到 1.4KB 的大小。这些增加的大小会快速累积,产生复杂的系统或共享库。如果可能,请考虑使用 @IntDef 注释和代码缩减移除枚举并将它们转换为整数。此类型转换可保留枚举的各种安全优势。

2.2 res优化

2.2.1缩减资源

资源缩减只有在与代码缩减配合使用时才能发挥作用。在代码缩减器移除所有不使用的代码后,资源缩减器便可确定应用仍要使用的资源,当您添加包含资源的代码库时尤其如此。您必须移除不使用的库代码,使库资源变为未引用资源,因而可由资源缩减器移除。如需启用资源缩减功能,请将 build.gradle 文件中的 shrinkResources 属性和minifyEnabled 属性设为 true。

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

Gradle 资源缩减器只会移除未由应用代码引用的资源,这意味着,它不会移除用于不同设备配置的备用资源。如有必要,您可以使用 Android Gradle 插件的 resConfigs 属性移除应用不需要的备用资源文件。

以下代码段展示了如何设置只保留英语和法语的语言资源:

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}

 

当然,也可以通过Lint静态扫描出Unused Resources,然后点击“Remove All Unused Resources” 。

2.2.2 压缩资源

WebP 是 Google 的一种可以同时提供有损压缩(像 JPEG 一样)和透明度(像 PNG 一样)的图片文件格式,不过与 JPEG 或 PNG 相比,这种格式可以提供更好的压缩。Android 4.0(API 级别 14)及更高版本支持有损 WebP 图片,Android 4.3(API 级别 18)及更高版本支持无损且透明的 WebP 图片。

您可以使用 pngcrushpngquant 或 zopflipng 等工具缩减 PNG 文件的大小,同时不损失画质。所有这些工具都可以缩减 PNG 文件的大小,同时保持肉眼感知的画质不变

2.3 resources.arsc

resources.arsc优化推荐微信推出的AndResGuard工具,原理类似Java Proguard,但是只针对资源。他会将原本冗长的资源路径变短,例如将res/drawable/wechat变为r/d/a。

 

以上是关于apk的打包和优化的主要内容,如果未能解决你的问题,请参考以下文章

HTML/网站一键打包APK工具(html网页打包安卓APP应用)

ionic3代码压缩和apk优化

Android 安装包优化APK 打包流程 ( 文件结构 | 打包流程 | 安装流程 | 安卓虚拟机 )

朝花夕拾Android性能优化篇之Apk打包

apk优化 :android:extractNativeLibs 升级gradle之后发现 打包出来的apk体积突然大了将近一倍。

ionic2新手入门整理,搭建环境,创建demo,打包apk,热更新,优化启动慢等避坑详解