Android性能优化(5):APK瘦身优化

APK,全称Android Application Package,即Android应用程序包,是Android系统使用的一种应用程序包文件格式,它的作用是将Android程序资源整合在一起,以便Android程序能在Android设备上正常运行。简单地说,就是一个Android应用程序的代码要想在Android设备上运行,必须先进行编译,然后被打包成一个被Android系统所能识别的文件才可以被运行,而这种能被Android系统识别并运行的文件格式就是"APK",而文件后缀为".apk"。

1. APK文件结构

 APK文件本质上是一个压缩文件,我们将一个APK文件进行解压,它主要包含以下文件:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-edOse0O0-1575985466139)(E:\新建文件夹\性能优化\插图\APK瘦身\APK1.png)]

  • 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具体工作流程如下:

  1. 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性;
  2. 优化(Optimize):分析和优化相关方法字节码,移除无用的指令;
  3. 混淆(Obfuscate):使用随机的小写且无意义的名称对类、字段和方法进行重命名;
  4. 预校验(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 格式,支持转换单张图片,也可以转换包含多张图片的文件夹。要转换某张图片或包含多张图片的文件夹,请按照下列步骤操作:

  1. 右键点击某个图片文件或包含很多图片文件的文件夹,然后点击 Convert to WebP
  2. 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文件,比如armeabiarmeabi-v7ax86mipsarm64-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,且该架构前后支持armeabiarm64-v8a设备,也就是说,除非特殊情况需要做CPU架构适配,一般情况我们在Android应用中保留armeabi-v7a架构就可以了。通过删除其他架构的so,实现减小APK的体积。

2.3.2 插件化

 使用插件化技术动态加载so文件,以达到减小APK包体积的目的。关于插件化技术,将在接下来的文章中讲解。

发布了83 篇原创文章 · 获赞 293 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/AndrExpert/article/details/103483635