Android性能优化—apk瘦身

1.代码:代码压缩混淆
2.资源方面:小图片使用SVG矢量图、移除无用资源、资源压缩、资源混淆
3.动态库:一般只需要配置armeabi和armeabi-v7a即可,相比配置各种ABI减少了大量体积(x86和x86_64架构的手机CPU已经几乎没有了,市面上几乎都是arm架构的手机,因此打包时可以忽略x86和x86_64架构的so库)。

APK的结构

包含以下目录:

  • assets/: 包含了应用的资源,这些资源能够通过AssetManager对象获得。
  • lib/: 包含了针对处理器层面的被编译的代码。这个目录针对每个平台类型都有一个子目录,比如armeabi, armeabi-v7a, arm64-v8a, x86, x86_64和mips。
  • res/: 包含了没被编译到resources.arsc的资源。
  • META-INF/: 包含CERT.SF和CERT.RSA签名文件,也包含了MANIFEST.MF文件。(译注:校验这个APK是否被人改动过)

包含以下文件:

  • classes.dex: 包含了能被Dalvik/Art虚拟机理解的 dex 文件格式的类。
  • resources.arsc: 包含了被编译的资源。该文件包含了res/values目录的所有配置的 xml 内容。打包工具将 xml 内容编译成二进制形式并压缩。这些内容包含了语言字符串和styles,还包含了那些内容虽然不直接存储在resources.arsc文件中,但是给定了该内容的路径,比如布局文件和图片。所以又叫 资源映射表
  • AndroidManifest.xml: 包含了主要的Android配置文件。这个文件列出了应用名称、版本、访问权限、引用的库文件。该文件使用二进制 xml 格式存储。(译注:该文件还能看到应用的minSdkVersion, targetSdkVersion等信息)

使用SVG矢量图

SVG导入

SVG(Scalable Vector Graphics),可缩放矢量图。SVG不会像位图一样因为缩放而让图片质量下降。优点在于可以减小APK的体积(比如如果使用png图片,则需要在drawable和drawable-xhdpi等目录下放置多套分辨率不同的图片,否则png图片会因为缩放而变得模糊)。常用于简单小图标,官方建议一般图片小于200*200时用SVG,当图片较大时SVG图片绘制时间会较长。对于app的启动页的背景图这种大图片,就不能使用SVG,可以使用webp图片来减小图片大小。

svg是由xml定义的,标准svg根节点为<svg>

Android中只支持 <vector>,我们可以通过 vector 将svg的根节点 <svg> 转换为 <vector>

在Android Studio中打开工程,在res目录中点击右键 -> new -> Vector Asset
在这里插入图片描述

在这里插入图片描述

SVG批量转换

如果有多个svg需要转换为android的vector,则可以通过第三方工具 svg2vector 进行批量转换。

执行转换命令:

java -jar svg2vector-cli-1.0.0.jar -d . -o a -h 20 -w 20

-d 指定svg文件所在目录
-o 输出android vector图像目录
-h 设置转换后svg的高
-w 设置转换后svg的宽

不支持的SVG

如果 SVG 文件包含不受支持的功能,将在 Vector Asset Studio 的底部显示一个错误提示,如图:
在这里插入图片描述
不支持的功能举例:
滤镜效果:不支持投影,模糊和颜色矩阵等效果。
文本:建议使用其他工具将文本转换为形状。

矢量图向后兼容

Android 5.0(API 21)之前的版本不支持矢量图,使用 Vector Asset Studio 有两种方式适配:生成PNG和使用支持库。

方式一:生成 png 格式的图片

Vector Asset Studio 可在构建时 针对每种屏幕密度将矢量图转换为不同大小的位图,在 build.gradle 中配置如下,适用于 Gradle 插件1.5 及以上版本:
android{
defaultConfig{
// 5.0(API 21)版本以下,将svg图片生成指定维度的png图片
generatedDensities = [‘xhdpi’,‘xxhdpi’]
}
}

方式二:支持库

在 build.gradle 中配置如下,适用于 Gradle 插件2.0及以上版本:
android{
// Gradle Plugin 2.0+
defaultConfig{
// 利用支持库中的 VectorDrawableCompat 类,可实现 2.1 版本及更高版本中支持 VectorDrawable
vectorDrawables.useSupportLibrary = true
}
}
dependencies {
// 支持库版本需要是 23.2 或更高版本
compile ‘com.android.support:appcompat-v7:23.2.0’
}

这时,使用矢量图必须使用 app:srcCompat 属性,而不是 android:src属性,如下:
在这里插入图片描述

矢量图颜色修改

我们是可以单独修改矢量图某一部分的颜色的,如图:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

Tint着色器

虽然我们前面说了,可以直接在 xml 文件中修改矢量图的颜色,但是并不建议直接修改矢量图的颜色,而是使用Tint 着色器来实现。比如我们一般让矢量图的颜色为黑色,然后用 Tint 着色器去修改矢量图的颜色为其他的各种颜色(比如红色、绿色、蓝色等),这样只需要一张SVG矢量图就可以实现各种颜色,而不需要很多张不同颜色的图片。

Tint着色器修改颜色:
在这里插入图片描述
那如果我们想实现一个点击效果呢?

Tint着色器–点击变色

创建两个选择器,然后正常使用即可。

创建 drawable 选择器 battery_selector.xml
在这里插入图片描述

创建 color 选择器 battery_tint_selector.xml
在这里插入图片描述
使用
在这里插入图片描述

移除无用资源

Remove unused resources

AS 给我们提供了一键移除所有无用的资源,如图。
在这里插入图片描述

但是这种方式不建议使用,因为如果某资源仅存在使用动态获取资源id的方式,那么这个资源会被认为没有使用过,从而会直接被删除。

动态获取资源id的方式:
getResources().getIdentifier(“name”,“defType”,getPackageName());

另外,如果某些资源文件可能以后会用到,所以不想删除,这种方式无法做到,因为会删除所有检测到的没有使用过的资源。

Lint移除无用资源

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以点击这个按钮过滤不想被移除的资源:
在这里插入图片描述

国际化资源配置

默认会适配很多国家的语言:
在这里插入图片描述
配置为只适配英语后:

android{
    defaultConfig{
        // 只适配英语
        resConfigs 'en'
    }
}

在这里插入图片描述
这样string中只有指定语言对应的字符串,减少了大量其他语言的字符串。

动态库打包配置

首先我们需要知道 so文件是由ndk编译出来的动态库,是 c/c++ 写的,所以不是跨平台的。即每一个平台需要使用对应的so库。

ABI 是应用程序二进制接口简称(Application Binary Interface),定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。

在Android 系统上,每一个CPU架构对应一个ABI:armeabi,armeabi-v7a,arm64- v8a,x86,x86_64,mips,mips64。

现在我们一般只需要配置armeabi-v7a即可,相比配置各种ABI减少了大量体积。

android{
    defaultConfig{
        ndk{
            abiFilters "armeabi-v7a"
        }
    }
}

反编译微信的apk就可以发现lib目录下只有armeabi-v7a目录。

ABI–应用二进制接口

早期的Android系统几乎只支持ARM v5的CPU架构,而现在你知道它支持多少种了吗?7种!

Android系统目前支持以下七种不同的CPU架构:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和x86_64 (从2014年起),每一种都关联着一个相应的ABI。

ABI 是应用程序二进制接口简称(Application Binary Interface),定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。

在Android 系统上,每一个CPU架构对应一个ABI:armeabi,armeabi-v7a,arm64-v8a,x86,x86_64,mips,mips64。

ABISupported Instruction Set(s)Notes
armeabiARMV5TE and later,Thumb-1No hard float
armeabi-v7aarmeabi,Thumb-2,VFPv3-D16,Other,optionalIncompatible with ARMv5,v6 devices
arm64-v8aAArch-64
x86x86(IA-32),MMX,SSE/2/3,SSSE3No support for MOVBE or SSE4
x86_64x86-64,MMX,SSE/2/3,SSE3,SSE4.1,SSE4.2,POPCNT
mipsMIPS32r1 and later
mips64MIPS64r6

各版本分析如下:

  • mips / mips64:极少用于手机可以忽略
  • x86 / x86_64:x86 架构的手机都会包含由 Intel 提供的称为 Houdini 的指令集动态转码工具,实现 对 arm .so 的兼容,再考虑 x86 1% 以下的市场占有率,x86 相关的两个 .so 也是可以忽略的
  • armeabi:ARM v5 这是相当老旧的一个版本,缺少对浮点数计算的硬件支持,在需要大量计算时有性能瓶颈
  • armeabi-v7a:ARM v7 目前主流版本
  • arm64-v8a:64位支持

ps:ARM v8的相关信息:

ARM公司自己本身并没有64位芯片设计技术,在2011年11月,他是通过了收购MIPS64处理器架构的部分技术使用权,再结合ARM的一些特性设计出来的。也就是说:MIPS、ARM、X86三大架构中,唯一没有64位技术的ARM,通过收购MIPS的形式得到了64位。

所谓的ARMv8架构,就是在MIPS64架构上增加了ARMv7架构中已经拥有的的TrustZone技术、虚拟化技术及NEON advanced SIMD技术等特性,研发成的。

64位ARMv8架构中包含两个执行状态:AArch32(也就是我们常说的ARMv7)和AArch64(ARMv8)。AArch64执行状态针对64位处理技术,引入了一个全新指令集A64(也就是基于收购的MIPS64架构),而AArch32执行状态将支持现有的ARM指令集。所以64位的ARM处理器中同时包含着32位的ARMv7和64位的ARMv8两种架构。因此:看到这里,你一定明白了,ARM64位处理器和电脑的64位处理器是两个截然不容的概念,他并不是64位就能原生向下兼容32位程序,而是通过64位处理器中集成的32位架构来运行32位程序。说得通俗点,它不是以64位形态来运行32位程序,而是以32位的形态运行32位程序的。

由于目前新出的64位处理器包含两个架构,而且制程技术没有提升(28nm),同时在手机与平板上,芯片面积有着严格的限定,不能过分增加,这导致64位ARM处理器平均分配到每个架构的晶体管数量锐减,也就是说从64位处理器中的32位架构方面,对于同规格的32位处理器而言,不但没有提高,性能反而是一定规模下降的。但处理器厂家又必须给消费者一个交代,以更好的推广64位,所以厂家就必须在其他方面提升性能,以弥补CPU的晶体管数量减少带来的损失。比如:更换性能更强的GPU、提升内存带宽、多核心虚拟单颗核心提升单核性能、联合跑分软件商修改跑分权重(提升GPU分数,降低CPU分数的权重)等等。这样,扬长避短,最终到达消费者手里,用跑分软件一跑,确实有提升,用户开心,厂家腰包也鼓了。

综上所述,ARM64位处理器从严格意义来说,叫它ARM32+64更加贴切,他相对于ARM32位处理器,有倒退的地方,也有进步的余地,但正因为倒退激起了ARM进取的决心,让它大刀阔斧的向前变革,不得不说也算一种进步。但ARM64在的手机上真的有用吗?我只能说,目前确实没啥用,但今后或许有。

谷歌官方曾说,安卓很早前就支持64位了,这话不假,从Android4.0到Android4.4,安卓系统都支持64位的硬件,但是这仅仅表示底层驱动支持64位,能运行在64位的硬件之上,仅此而已。然而,上层运行软件的,无论是Dalvik的虚拟机,还是ART虚拟机都是32位的。也就是说,只要你的手机系统是Android4.0—4.4,即便你的处理器是64位,也只能在32位虚拟机下运行32位程序,就算真的64位程序摆在你眼前,也无法安装。

Android L开始才真正支持32位和64位的ART虚拟机,配合上64位处理器,名正言顺的运行64位软件。但是问题又来了,没有软件商 愿意开发64位程序。

ARMv8是一套不错的指令集,它既支持未来的64位程序,也向下兼容现有32位程序。有了ARMv8的支撑,以后的64位手机操作系统,如Android L 64bit都可以简单、高效地支持现有的32位App,你不用担心兼容性问题。

大小端

大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。

例如这样一个数据:0x11223344 转为二进制为 00010001 00100010 00110011 01000100

大端存储后 我们看到的就是 00010001 00100010 00110011 01000100 即 16进制的 11 22 33 44

小端存储后 我们看到的就是 01000100 00110011 00100010 00010001 即 16进制的 44 33 22 11

现阶段状况

目前Intel的80x86系列芯片是唯一还在坚持使用小端的芯片,ARM芯片默认采用小端,但可以切换为大端;而MIPS等芯片要么采用全部大端的方式储存,要么提供选项支持大端——可以在大小端之间切换。另外,对于大小端的处理也和编译器的实现有关,在C语言中,默认是小端(但在一些对于单片机的实现中却是基于大端,比如Keil 51C),Java是平台无关的,默认是大端。在网络上传输数据普遍采用的都是大端。

代码混淆压缩

将 minifyEnabled 设置为 true 即可
在这里插入图片描述
但是我们会发现,运行时会报错,因为 minifyEnabled 既压缩了代码,也混淆了代码,所以我们需要处理下混淆,需要在proguard-rules.pro文件中keep不能被混淆的类。

代码混淆压缩为什么能减小apk大小?代码中复杂的变量名和方法名全变为了简短的字母和数字,自然减小了体积。

资源压缩

资源压缩只与代码压缩协同工作,需要同时开启minifyEnabled:
在这里插入图片描述
开启时,会检测未使用的资源,对于使用的资源,包括动态方式使用的资源:

	//动态获取资源id的方式:
	getResources().getIdentifier("activity_main1", "layout", getPackageName());

不进行处理,activity_main1.xml保持原内容。

对于未使用的资源,打包进apk时,会进行资源压缩,比如:
activity_main2.xml是未使用的,apk中activity_main2.xml里面的内容几乎全被删除,压缩的只剩下很少的内容:
在这里插入图片描述
注意:删除的是apk中activity_main2.xml里面的内容,项目中activity_main2.xml里面的内容不会被删除,资源压缩是指压缩资源里的内容。

默认情况是未启用严格模式,即寻找使用的资源名称是采用startwith模式,如果activity_main1.xml被使用,则会认为activity_main12.xml,activity_main123.xml等资源文件也被使用,所以这些资源文件的内容也被保留。如果启用严格模式,则寻找使用的资源名称是采用equal模式,所以activity_main12.xml,activity_main123.xml等资源文件的内容会被删除,因为这些资源文件没有被使用。

如需启动则需设置 shrinkMode,需要创建keep.xml,如下
在这里插入图片描述
将该文件保存在项目资源中,例如,保存在 res/raw/keep.xml。构建时不会将该文件打包到 APK 之中。

如果你有想要保留或舍弃的特定资源,则可以创建如下的 xml 文件,然后在 tools:keep 属性中指定每个要保留的资源,在 tools:discard 属性中指定每个要舍弃的资源。
在这里插入图片描述

资源混淆

资源混淆主要这两个部分:
在这里插入图片描述
资源目录名和资源名都会被混淆。

未混淆的:
在这里插入图片描述
混淆后:
res变为r
在这里插入图片描述
资源目录名和资源名全是简短的字母和数字:
在这里插入图片描述
在这里插入图片描述

资源混淆后,代码中是如何访问到资源的?
在这里插入图片描述
resource.arsc – > Android Resource
resources.arsc是Android打包后,资源映射文件(资源映射表),所有res目录下的资源都会到此文件里面。代码中是通过R文件中的资源id访问资源的,通过这个资源id可以在resources.arsc中找到对应的资源路径,因此只要resources.arsc中的资源路径改为混淆后的资源路径即可定位到资源。

资源id的格式
R文件中资源的整型数格式为:0xpptteeee(16进制,p代表的是package,t代表的是type,e代表的是entry)。

  • Package ID 包ID,系统为0x01,应用程序资源为0x7f。
  • Type ID 资源的类型ID,资源的类型有animator、anim、color等等,每一种都会被赋予一个ID。
  • Entry ID 资源在其所属的资源类型中所出现的次序。

在这里插入图片描述

混淆步骤

1.解压待混淆APK,记录APK内文件存储方式(结合强制压缩文件列表,/config/compressData.txt)
解析 arsc 文件(ZIP中存储文件两种方式:DEFLATED(压缩)/STORED(仅存储),对于APK文件来说某些资源不允许压缩(如:SoudPool加载raw下的mp3),而有些资源可以压缩但是AS打包APK时却没有压缩(如png/jpg等)。)

2.混淆 arsc 文件数据中对应的资源名与文件路径字节数据

3.输出混淆后的 arsc 文件至 app 目录

4.将 apk 中其他文件拷贝到 app 目录,并根据混淆修改 res/ 目录下文件名

5.打包、对齐并签名

http://androidos.net.cn/android/8.0.0_r4/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h
http://androidos.net.cn/android/7.1.1_r28/xref/frameworks/base/include/androidfw/ResourceTypes.h

https://github.com/shwenzhang/AndResGuard

ARSC文件格式

在这里插入图片描述
查看二进制格式的ARSC文件可以使用010editor工具。

字符串池的格式

在这里插入图片描述

混淆ARSC文件

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值