APK,全称Android Application Package
,即Android应用程序包,是Android系统使用的一种应用程序包文件格式,它的作用是将Android程序
和资源
整合在一起,以便Android程序能在Android设备上正常运行。简单地说,就是一个Android应用程序的代码要想在Android设备上运行,必须先进行编译,然后被打包成一个被Android系统所能识别的文件才可以被运行,而这种能被Android系统识别并运行的文件格式就是"APK
",而文件后缀为".apk
"。
1. APK文件结构
APK文件本质上是一个压缩文件,我们将一个APK文件进行解压,它主要包含以下文件:
assets目录
:存放原生的静态资源文件,如图片、JSON配置文件、二进制数据、HTML5等。系统在编译时不会编译该目录下的资源,因此不会像res目录样能被直接通过R.xxx.xxx进行访问,而是需要通过AssetManager以二进制流的形式来读取资源。注意:res/raw目录存储的也是原生的资源文件,但是它能够被Android映射成R.xxx.xxx资源,它们的不同之处在于assets目录下的文件结构支持树形结构目录,而res/raw不支持。另外,assets目录下的单个文件尽量在1MB以下,而res/raw目录下的单个文件可以任意MB。
// 访问assets目录下的资源
AssetManager manager = this.getAssets();
InputStream in = manager.open("image/logo.jpg");//注意路径
Bitmap bitmap = BitmapFactory.decodeStream(in);
// 访问res/raw目录下的资源
InputStream is = this.getResources().openRawResource(R.raw.logo);
Bitmap image = BitmapFactory.decodeStream(is);
lib目录
:存放应用程序依赖的不同架构(ABI)的.so文件。res目录
:存放应用的资源文件,包括图片、字符串、颜色、尺寸、动画文件、布局文件等资源。这个目录下的所有资源都会出现在资源清单文件R.java的索引中。MERA-INF目录
:保存应用程序的签名信息,签名信息可以验证APK文件的完整性。MANIFEST.MF
:该文件保存了APK包中每个文件得名字及其SHA1哈希值;CERT.SF
:该文件保存了MANIFEST.MF文件的哈希值及其文件中每一个哈希项的哈希值;CERT.RSA
:该文件保存了APK包的签名和证书的公钥信息;
AndroidManifest.xml
:Android 应用的配置文件,用于描述 Android 应用的整体情况。每个 Android 应用必须包含一个 AndroidManifest.xml 文件。AndroidManifest.xml 包含了 Android 四大组件的注册信息,权限声明以及 Android SDK 版本信息等,该文件将会被编译成二进制文件。classes.dex
:应用程序可执行文件,即Dalvik字节码文件。APK中可能包含多个dex文件,具体要看Android程序的所有方法数是否超过65535,如果超过就进行分包处理,就会出现多个dex文件的情况。Resources.arsc
:资源配置文件。该文件用于记录资源文件位置和资源ID之间的映射关系,以便系统能够根据ID去寻找对应的资源路径。该ID实际上与R.java中存储的是一样的,但是R.java只是便于开发者调用资源且保证编译程序不报错,而实际上在程序运行时系统需要根据这个ID从Resources.arsc文件保存的对应的路径中获取资源文件。
2. APK瘦身优化
在第1小节中,我们介绍了一个APK文件的结构主要由动态库目录(lib)、资源文件目录(assets&res)、字节码文件(.dex)、资源配置文件(Resources.arsc)以及清单文件(AndroidManifest.xml)等组成,其中,lib目录
、assets&res
目录、.dex
文件这三部分对整个APK文件大小占比达到99%以上,这里可以通过AS的可以看出。也就是说,对于APK文件的瘦身优化,将从字节码文件、资源文件以及动态库这三个方面入手。AS执行"Build->Analyze APK
"操作效果图如下:
2.1 优化dex文件大小
2.1.1 Proguard
ProGuard是Android SDK中提供的一个可用于对Java字节码文件进行压缩
、优化
、混淆
以及预校验
的免费工具,它通过工程中所有代码以找出无用的代码并将其移除,同时还会使用语义上隐晦的名称来重命名代码中的类、接口、字段和函数等,达到压缩、优化和混淆代码的功能,从而使最终生成的APK文件变得更小、效率更高、更难被反编译。在工程中开启混淆方法,修改app module的build.gradle文件。
android {
...
buildTypes {
release {
// 开启Proguard打包混淆
minifyEnabled true
// 指定混淆规则文件
// 自定义规则在proguard-rules.pro文件
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
- ProGuard工作原理
ProGuard具体工作流程如下:
- 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性;
- 优化(Optimize):分析和优化相关方法字节码,移除无用的指令;
- 混淆(Obfuscate):使用随机的小写且无意义的名称对类、字段和方法进行重命名;
- 预校验(Preverify):在Java平台上对处理后的代码进行预校验,确保加载的class文件是可执行的。
- Proguard使用参数
参数 | 作用 |
---|---|
-optimizationpasses | 指定混淆压缩比,0~7之间,默认-optimizationpasses 5 |
-dontusemixedcaseclassnames | 混淆时不使用大小写混合,混淆后的类名为小写 |
-dontskipnonpubliclibraryclasses | 指定不去忽略非公共的库类 |
-dontskipnonpubliclibraryclassmembers | 指定不去忽略包可见的库类的成员 |
-dontpreverify | 不做预校验,安卓不需要preverify,去掉这一步可加快混淆速度 |
-verbose | 混淆后就会生成映射文件,包含有类名->混淆后类名的映射关系等 |
-dontoptimize | 优化,不优化输入的类文件 |
-allowaccessmodification | 优化时允许访问并修改有修饰符的类和类的成员 |
-printmapping | 指定映射文件的名称,如-printmapping proguardMapping.txt |
-dontshrink | 压缩,不压缩输入的类文件 |
-optimizations | 指定混淆时采用的算法,后面的参数是一个过滤器-optimizations !code/simplification/arithmetic,!field/,!class/merging/ |
-keepattributes [attribute_filter] | 不混淆相关属性,比如:(1) -keepattributes Annotation不混淆代码中的注释(在JSON实体映射中非常重要);(2) -keepattributes Signature,不混淆泛型;(3) -keepattributes SourceFile,LineNumberTable抛出异常保留代码行号 |
-keep [,modifier,…] class_specification | 不混淆指定的类文件和类的成员(变量、方法),比如:(1) -keep public class * extends android.app.Activity不混淆所有Activity的子类(2) -keep public class com.xxxx.app.ui.fragment.** {*;}不混淆android-support-v4.jar包下的所有类及类的成员 |
-keepclassmembers[,modifier,…] class_specification | 不混淆指定类的成员,比如:(1) -keepclassmembers class * extends android.app.Activity {public void *(android.view.View);}不混淆所有Activity的子类中参数是view的方法;(2) -keepclassmembers enum * {public static [] values();public static valueOf(java.lang.String);}不混淆所有枚举类 |
-keepclasseswithmembers [,modifier,…] class_specification | 不混淆指定的类和类的成员,但条件是所有指定的类和类成员必须存在,如保留所有的本地native方法不被混淆:-keepclasseswithmembernames class * { native ;} |
-keepnames class_specification | 不混淆指定的类和类的成员的名称 |
-keepclassmembernames | 不混淆类的成员名 |
-keepclasseswithmembernames | 不混淆类的及其成员名 |
-printseeds {filename} | 列出类和类的成员-keep选项的清单,标准输出到给定的文件 |
-libraryjars class_path | 声明引入的第三方库,比如支付宝SDK:-libraryjars libs/alipaysdk.jar |
-dontwarn | 不提示引入第三方库发出的警告-dontwarn com.alipay.android.app.** |
文件过滤(File Filters) | (1) ?(问号):匹配文件名中的一个字符(2) * (单星号):匹配任何一个文件名,但不包含目录分隔符(3) ** (双信号):匹配任何一个文件名,及任意数量的目录分隔符(4)!(感叹号):匹配除!之外的内容举例:java/*.class,匹配java目录下的所有java文件,不包子目录java/.class,匹配所有Java文件,包括子目录下的java文件!.gif,images/,匹配images目录下所有除gif格式文件 |
注:关于Proguard的具体使用,请参照我这篇文章。
2.1.2 AndResGuard
AndResGuard是微信团队开源的一个缩小APK大小的工具,它的原理类似于Proguard,但是只针对资源
,它会把原本冗长的资源路径变短,比如讲res/drawable/wechat
变为r/d/a
。相比于Proguard,AndResGuard的优势在于它通过自己实现安装包解压和利用7z极限压缩进行打包,使得整个混淆打包过程不依赖源码和编译流程,只需输入一个APK文件(无论签名与否、debug版、release版),从而做到最大混淆,大大减少了安装包体积,同时也提升了APk被破解的难度。AS中配置方法如下:
- 配置根目录下的build.gradle文件
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
// 添加依赖,AndResProguard
classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.17'
}
}
- 配置app目录下的build.gradle文件
apply plugin: 'AndResGuard'
android {
...
}
dependencies {
...
}
andResGuard {
// mappingFile用于keep住资源原有的物理路径
// mappingFile = file("./resource_mapping.txt")
mappingFile = null
use7zip = true
useSign = true
// 打开这个开关,会keep住所有资源的原始路径,只混淆资源的名字
keepRoot = false
// 设置这个值,会把arsc name列混淆成相同的名字,减少string常量池的大小
fixedResName = "arg"
// 打开这个开关会合并所有哈希值相同的资源,但请不要过度依赖这个功能去除去冗余资源
mergeDuplicatedRes = true
// 设置白名单,即不混淆某些资源
// 白名单机制只作用于资源的specsName,不会keep住资源的路径
// 如果想keep住资源原有的物理路径,可以使用mappingFile
whiteList = [
// for your icon
"R.drawable.icon",
// for fabric
"R.string.com.crashlytics.*",
// for google-services
"R.string.google_app_id",
"R.string.gcm_defaultSenderId",
"R.string.default_web_client_id",
"R.string.ga_trackingId",
"R.string.firebase_database_url",
"R.string.google_api_key",
"R.string.google_crash_reporting_api_key"
]
// 指定压缩的文件类型。支持以下三种文件通配符:
// * 为文件通配符,表示0个或多个字符;
// ? 表示0个或1个字符;
// + 表示1个或多个字符;
compressFilePattern = [
"*.png",
"*.jpg",
"*.jpeg",
"*.gif",
]
// 配置7Zip
// 只需设置artifact或path,如果同时设置总以path的值为优先
// 注意:对于发布与Google Play的APP建议不要使用7Zip压缩
sevenzip {
artifact = 'com.tencent.mm:SevenZip:1.2.17'
//path = "/usr/local/bin/7za"
}
/**
* 可选: 如果不设置则会默认覆盖assemble[BuildType | Flavor]输出的apk
* 如果配置则输出至finalApkBackupPath配置路径
**/
// finalApkBackupPath = "${project.rootDir}/final.apk"
/**
* 可选: 指定v1签名时生成jar文件的摘要算法
* 默认值为“SHA-1”
**/
// digestalg = "SHA-256"
}
接着,Sync Android项目,同步完毕后我们打开Android Studio右侧栏的Gradle选项,展开:app
就会出现一个名为"andreguard"的目录,该目录包含了一些可执行选项,用于执行混淆打包。
这里我们双击resguardRelease
执行生成被打包混淆APk,保存路径位于目录app/build/ouputs/realse
中,会自动生成一个AndResGuard_app
目录,然后目录下面有多个被混淆打包的apk文件,而app-rlease-signed-7zip-aligned.apk
就是我们需要的release版apk。通过apktool工具进行反编译,发现确实已经将所有资源(非白名单)进行了混淆,且APK的大小由在没有做任何资源优化的情况下由原来的20MB减小到16MB,可以说明效果还是很明显的。生成APk路径如下:
当然,除了直接在AS中配置AndResGuard,我们也可以官方提供的andresguard.jar
来对apk文件进行混淆,该方式简单用法如下所示:
// 直接混淆处理,默认配置和输出路径
java -jar andresguard.jar input.apk
// 若想指定配置文件或输出目录
java -jar andresguard.jar input.apk -config yourconfig.xml -out output_directory
// 若想指定签名信息或mapping信息:
java -jar andresguard.jar input.apk -config yourconfig.xml
-out output_directory -signature signature_file_path storepass_value
keypass_value storealias_value -mapping mapping_file_path
// 若想指定7zip或zipalign的路径(若已设置环境变量,这两项不需要单独设置):
java -jar andresguard.jar input.apk
-7zip /shwenzhang/tool/7za -zipalign /shwenzhang/sdk/tools/zipalign
// 若想用7zip重打包安装包,同时也可指定output路径
// 指定7zip或zipalign的路径(此模式其他参数都不支持):
java -jar andresguard.jar -repackage input.apk -out output_directory
-7zip /shwenzhang/tool/7za -zipalign /shwenzhang/sdk/tools/zipalign
除此之外,还可以从以下几个方面进行优化:
- 删除无用或重复的第三方库、jar包;
- 重新编译第三方库。由于很多时候在项目中,对于第三方库只使用了部分功能,我们可以尝试抽取出项目用到的部分并重新编译,以此来减小dex文件大小。但是,重新编译第三方库可能会引起较多的问题,不到万不得已,尽量不要走到这步。
2.2 优化资源文件大小
2.2.1 Android Lint
Android Lint是Android SDK于ADT16引入的一个非常强大的代码扫描工具,它通过对Android工程源码代码进行扫描和检查,来帮助开发者自动检测代码的质量、安全问题以及无用资源,通俗来说就是发现潜在的错误,便于开发者及早修正这些问题。Android Lint工作过程比较简单,整个Lint过程由Lint Tool(检测工具)
,Source Files(项目源文件)
和lint.xml(配置文件)
三个部分组成,Lint Tool 读取 Source Files,根据 lint.xml 配置的规则输出结果。Lint工作原理大体如下:
- 启动Lint
由于Android Studio直接集成了Lint,因此我们无需再安装Lint工具,只需点击主菜单->Analyze-> Inspect Code
即可启动Lint工具。弹出检查配置对话框如下:
其中,注释1选项卡用于设置检查的范围,默认为整个工程(whole project
),我们也可以选中Custom scope
指定感兴趣的范围,比如某个module;注释2用于指定检查配置文件,这里选中默认就好(Project Default
)。然后,点击面板上的OK
按钮,等待几分钟就可以看到检查结果,如下图所示:
从结果面板可知,Lint结果由4部分组成:注释1
为Lint检查结果,可以根据不同方式对检查结果进行展示,比如按照问题的严重程度分组、按照目录分组等;注释2
给出自动修改方案,我们点击对应的按钮AS便可完成修改;注释3
给出的是对问题的描述;注释4
用于在指定区域检查同类问题。
- 根据规则名称来检查
在APK瘦身优化中,我们用得较多的就是使用Android Lint移除工程中那些无用的资源,可以通过点击主菜单->Analyze -> Run Inspection By Name
实现。弹出对话框后,在过滤框中输入unused resources
关键字,双击下拉列表中的unused resources
选项,然后会弹出检查范围设置对话框,这里我们使用默认Project Default
,点击OK后,等会儿就会出现检查结果。检查结果如下:
需要注意的是,Lint工具无法检查出被反射访问的资源,如果有反射访问情况,请复查下以免错删。另外,Lint分析资源文件时会跳过assets文件,对于该目录下的资源需要自行判断是否有使用,如果没有,尽量将其从工程中删除。
2.2.2 tinypng
Tinypng 是一款 PNG 图片压缩工具,压缩率能达到 50% 以上,图片在压缩之前和之后几乎看不出差别,几乎是无损压缩。Tinypng API 支持所有主流的平台,除了图片压缩,Tinypng 还支持对图片做缩放,裁切等处。据官网介绍,它的原理是通过合并图片中相似的颜色,通过将 24 位的 PNG 图片压缩成小得多的 8 位色值的图片,并且去掉了图片中不必要的 metadata(元数据,从 Photoshop 等工具中导出的图片都会带有此类信息),这种方式几乎能完美支持原图片的透明度。它还有一个兄弟叫 Tinyjpg,支持 JPG 图片压缩。
- tinypng使用方法
tinypng工具使用非常简单,即打开官网,然后将png格式图片拖拽到主页上方的方框区域即可。
-
压缩效果。
可以看出,几乎没有什么区别。
2.2.3 WebP
WebP是谷歌于2010年发布的一种旨在加快图片加载速度的图片格式,派生自图像编码格式VP8 。它不仅支持无损或有损压缩、alpha通道,还支持动画演示。在同画质的情况下,webp格式图片占用体积相较于jpg图片大约减少40%,相较于无损png图片大约减少30%。目前移动端Android4.0(API 14,如果需要支持无损和透明通道API>=18
)以上、PC端chorme 10+(14 ~ 16 有渲染bug)、opera 11+ 、safri均支持webp格式图片。Android Studio 可以将 PNG、JPG、BMP 或静态 GIF 图片转换为 WebP 格式,支持转换单张图片,也可以转换包含多张图片的文件夹。要转换某张图片或包含多张图片的文件夹,请按照下列步骤操作:
- 右键点击某个图片文件或包含很多图片文件的文件夹,然后点击 Convert to WebP;
- Converting Images to WebP 对话框随即打开。默认设置取决于当前模块的
minSdkVersion
设置。
- 面板说明:
- 注释1:使用有损压缩,
Encoding quality
用于设置压缩质量; - 注释2:使用无损压缩,需minSdkVersion>=18;
- 注释3:转换后比原始图片大则舍弃,默认开启;
- 注释4:不转换.9.png图片,默认开启;
- 注释5:是否跳过包含透明(alpha)通道的图像,minSdkVersion<18默认跳过。
- 注释1:使用有损压缩,
- 点击OK开始转换。如果您在上面选择了无损转换,系统会立即进行转换,并覆盖原来的生成一张后缀为
.webp
的图片;如果您选择了有损转换,并且选择在保存之前查看每张转换后图片的预览效果,其中,注释1处显示了原始图像和编码图像之间的像素,注释2处的进度条用于设置压缩转换质量,当设置为75%时,原始图和转换后图像基本看不出差异,但是图片的大小由749.7KB
降到17.9KB
,相比于tinypng压缩的158.8KB
,整整比处理后的PNG图像又降了88%左右,相对于原始图像缩小了98%。有损压缩预览如下:
除此之外,还可以从以下几个方面进行优化:
- 尽量不要使用帧动画,因为一个帧动画会包含多张图片;
- 将较大的资源文件放到服务端,APP启动后自动下载使用;
- 针对所有屏幕密度,尽量使用一套图片资源、一套布局、多套dimens.xml文件,在使用最小资源的情况下实现多分辨率的适配;
- 去除无用或功能上重复的第三方库,因为这些库也包含了自身的资源文件;
- 对assets资源目录作一定取舍,因为该目录资源保存的是原始资源。
- 使用gradle开启shrinkResources;
android {
...
buildTypes {
release {
// 移除无用的resource文件
shrinkResources true
// 开启Proguard打包混淆
minifyEnabled true
// 指定混淆规则文件
// 自定义规则在proguard-rules.pro文件
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
2.3 优化libs目录大小
2.3.1 裁剪libs目录
为了支持不同的CPU指令集,在Android工程中的lib目录可能会包含多种不同的CPU架构的so文件,比如armeabi
,armeabi-v7a
,x86
,mips
,arm64-v8a
等。它们的区别如表所示:
具体说明:
-
armeabi
:第5代 ARM v5TE,使用软件浮点运算,兼容所有ARM设备,通用性强,速度慢; -
armeabi-v7a
:第7代 ARM v7,使用硬件浮点运算,具有高级扩展功能。兼容支持armeabi和armeabi-v7a,,是目前Android手机主流CPU; -
arm64-v8a
:第8代,64位,包含AArch32、AArch64两个执行状态对应32、64bit。兼容支持armeabi、armeabi-v7a和arm64-v8a; -
X86
:一般用于平板,兼容支持 armeabi(性能有所损耗) 和 x86; -
x86_64
:一般用于平板,兼容支持 x86 和 x86_64; -
mips、mips64
:基本用不上;
从上面的分析可知,由于armeabi
架构的Android设备基本被淘汰了,目前主流的CPU架构为armeabi-v7a
,且该架构前后支持armeabi
和arm64-v8a
设备,也就是说,除非特殊情况需要做CPU架构适配,一般情况我们在Android应用中保留armeabi-v7a
架构就可以了。通过删除其他架构的so,实现减小APK的体积。
2.3.2 插件化
使用插件化技术动态加载so文件,以达到减小APK包体积的目的。关于插件化技术,将在接下来的文章中讲解。
转载:https://blog.csdn.net/AndrExpert/article/details/103483635