东莞网站排名优化,网站seo推广优化,开封网站制作,wordpress获取标签摘要#xff1a; 如何瘦身是 APK 的重要优化技术。APK 在安装和更新时都需要经过网络下载到设备#xff0c;APK 越小#xff0c;用户体验越好。本文作者通过对 APK 内在机制的详细解析#xff0c;给出了对 APK 各组成成分的优化方法及技术#xff0c;并实现了一个基本 APK… 摘要 如何瘦身是 APK 的重要优化技术。APK 在安装和更新时都需要经过网络下载到设备APK 越小用户体验越好。本文作者通过对 APK 内在机制的详细解析给出了对 APK 各组成成分的优化方法及技术并实现了一个基本 APK 的最小化过程。 正文
高尔夫运动中分数最小者胜出。
让我们将这一原则应用到 Android App 开发中。我们将玩转一个称为“ApkGolf”的 APK目的是创建一个尽可能具有最少字节数的 App并可安装在运行 Oreo 的设备上。
基线测定
一开始我们用 Android Studio 生成一个缺省的 App创建密钥库Keystore
并对 App 签名然后使用命令stat -f%z $filename测定生成 APK 文件的字节数大小。
进一步为确保该 APK 工作正常我们将在一台运行 Oreo 的 Nexus 5x 手机上安装它。 看上去挺漂亮。但是现在我们的 APK 大小近乎 1.5Mb。
APK Analyser
考虑到我们 App 的功能非常简单1.5Mb 的规模看上去过于臃肿了。因此我们要深入了解一下该项目看看是否有一些能立竿见影地削减文件大小的地方。Android Studio 生成了
扩展AppCompatActivity而得到的MainActivity使用根视图ConstraintLayout的布局文件Value 文件其中包含三种颜色、一个字符串资源Resource和一个主题ThemeAppCompat和ConstraintLayout的支持库一个AndroidManifest.xml文件PNG 格式的启动图标分别是正方形、圆形和前台的。
看上去首当其冲的目标是启动图标文件因为 APK 中共包含了 15 个图像文件并且在mipmap-anydpi-v26下还有两个 XML 文件。下面让我们使用 Android Studio 的 APK Analyser
(https://developer.android.com/studio/build/apk-analyzer.html)
对该 APK 文件做一个定量分析。 给出的结果与我们的最初假设大相径庭其中显示 Dex 文件是大头而上述资源仅占 APK 大小的 20%。
文件大小占比classes.dex74%res20%resources.arsc4%META-INF2%AndroidManifest.xml1%
下面让我们逐个分析每个文件的行为。
Dex 文件
看上去罪魁祸首是classes.dex文件它占据了 73% 的空间因而它成为我们的首要削减目标。该文件为 Dex 格式
其中包含了我们的全部编译后代码以及对 Android 框架和支持库中外部方法的引用。
然而android.support软件包中引用了超过 13000 种的方法对于一个简单的“Hello World”App 而言完全没有必要。
资源
目录“res”中包含了大量的布局Layout文件、Drawable 和动画它们并非在 Android Studio UI 中立刻可见。同样它们也是由支持库推入其中的约占 APK 规模的 20%。 在resources.arsc文件中还包含了对每个资源的引用。
签名
目录“META-INF”中包含有CERT.SF、MANIFEST.MF和CERT.RSA文件这些文件都需要 v1 APK 签名
(https://source.android.com/security/apksigning/v2#v1-verification) 。
如果有攻击者修改了我们 APK 中的代码签名就会不匹配。这一机制保障了用户能避免执行第三方恶意软件的风险。
在MANIFEST.MF文件中列出了 APK 中的所有文件。其中CERT.SF文件中包含了文件清单的摘要以及每个文件的独立摘要。CERT.RSA文件中包含了一个公钥用于验证CERT.SF文件的完整性。 在签名文件中没有目标明显可优化。
AndroidManifest 文件
看上去AndroidManifest文件非常类似于我们的原始输入文件。唯一差别在于文件中的字符串和 Drawable 等资源被整数资源 ID 所替代这些 ID 以0x7F开头。
启用最小化功能Minification
我们尚未在 App 的build.gradle文件中设置允许最小化Minification和资源收缩Resource Shrinking。我们现在做此设置
android {buildTypes {release {minifyEnabled trueshrinkResources trueproguardFiles getDefaultProguardFile(proguard-android.txt), proguard-rules.pro}}
}-keep class com.fractalwrench.** { *; }将minifyEnabled属性设置为“true”值这将启用 Proguard
(https://www.guardsquare.com/en/proguard)
该功能将从 App 中剥离出那些未使用的代码并对符号的名称做模糊化处理使得 App 难以被反向工程。
设置shrinkResources属性将会在 APK 中移除任何并非直接引用的资源。这时如果我们使用反射机制间接地访问资源就会导致问题但是本文给出的 App 并不存在这样的问题。
优化为 786 Kb削减 50%
我们已经实现了 APK 规模减半并未对我们的 APP 有任何可见的影响。 对于那些尚未在 App 中启用AndroidManifest.xml和shrinkResources的开发人员这是本文给出的最需要重视的并应学会的技巧。他们仅花费数小时做配置和测试就能轻松地削减数兆的规模。
我们尚未了解 AppCompat 的工作机制
现在classes.dex文件已削减到占用 APK 的 57%。在我们的 Dex 文件中大多数方法引用属于android.support软件包因此我们将要去除该支持库。具体做法为
从build.gradle中彻底清除依赖块。 dependencies { implementation ‘com.android.support:appcompat-v7:26.1.0’ implementation ‘com.android.support.constraint:constraint-layout:1.0.2’ }更新MainActivity以扩展android.app.Activity。
public class MainActivity extends Activity更新布局使用单一的TextView。
?xml version1.0 encodingutf-8?
TextView xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:gravitycenterandroid:textHello World! /删除styles.xml文件并从AndroidManifest文件的application元素中移除android:theme属性。删除colors.xml文件。在 gradle 同步时做 50 次上推push-up。
优化为 108 Kb削减 87%
天哪我们刚刚实现了近十倍的削减即从 786Kb 削减到 108Kb。唯一可见的更改是工具条Toolbar的颜色现在它使用了缺省的 OS 主题。 目录“res”现在占用 APK 规模约 95%原因是所有的加载图标。如果这些 PNG 图片是由我们自己的设计师所给出的那么我们可以尝试 将它们转换为 WebP 格式该格式更加高效并被 API 15 及以上所支持。
幸运的是Google 已经优化了我们的 Drawable。即便没有这种优化ImageOptim 也可优化 PNG 并从中剥离不必要的元数据。
让我们当一次坏人将我们所有的加载图标替换为单一的单像素黑点并置于未验证的res/drawable目录中。图片大小约 67 个字节。
优化为 6808 字节削减 94%
我们已经移除了几乎全部的资源因此毫不奇怪 APK 规模已经削减了约 95%。但是resources.arsc依然引用了如下项
一个布局文件一个字符串资源一个调用图标。
让我们从第一项着手。
布局文件优化为 6262 字节削减 9%
Android 框架会膨胀我们的 XML 文件
并自动创建一个TextView对象用于Activity对象的contentView。
我们可以尝试一些跳过中间的过程具体做法是移除 XML 文件并使用程序设置contentView。这样会降低资源的规模因为我们减少了一个 XML 文件。但是 Dex 文件将会增大因为我们引用了额外的TextView方法。
TextView textView new TextView(this);
textView.setText(Hello World!);
setContentView(textView);让我们查看一下这一权衡做法的工作情况它削减了 5710 个字节。
App 名称优化为 6034 字节削减 4%
下面我们将删除strings.xml文件并将AndroidManifest中的android:label属性值更改为“A”。这看上去是一个小更改但是它从resources.arsc中删除了一项削减了 Manifest 文件中的字符数并从“res”目录中移除了一个文件。略有裨益我们削减了 228 个字节。
加载图标优化为 5300 字节削减 13%
Android Platform 代码库中的resources.arsc的文档
告诉我们APK 中的每个资源通过resources.arsc中的一个整数 ID 引用。这些 ID 具有两个命名空间Namespace
0x01: 系统资源预装在 framework-res.apk 中0x7f: 应用资源捆绑在应用的.apk 文件中。那么如果在0x01命名空间中引用了一个资源我们的 APK 发生了什么我们应该可以在削减文件规模的同时得到一个更漂亮的图标。
android:iconandroid:drawable/btn_star虽然文档是这样说的但是在一个生产 App 中我们应该保持“永远不要信任系统资源”这一原则。该步骤会导致 Google Play 验证失败而且考虑到我们知道某些制造商已经重定义了白色
因此在具体操作时需要慎重。
Manifest 文件优化为 5252 字节削减 1%
目前为止我们尚未对 Manifest 文件下手。
android:allowBackuptrue
android:supportsRtltrue移除这些属性将会削减 48 个字节。
防止破解优化为 4984 字节削减 5%
看上去 Dex 文件中依然包括BuildConfig和R。
-keep class com.fractalwrench.MainActivity { *; }如果我们精炼 Proguard 规则就会清除掉这些类。
命名混淆优化为 4936 字节削减 1%
现在对我们的Activity赋予一个混淆后的名字。对于正常类Proguard 可自动实现混淆功能但是考虑到Activity类名会通过Intents唤醒因此缺省情况下不要混淆Activity的名字。
MainActivity - c.javacom.fractalwrench.apkgolf - c.cMETA-INF优化为 3307 字节削减 33%
当前在 App 签名中我们使用了 v1 和 v2 签名。看上去这完全是浪费尤其是 v2 会对整个 APK 做哈希提供了更高级的保护能力和性能
(https://source.android.com/security/apksigning/#apk-signing-schemes)。
在 APK Analyser 中v2 签名并不可见因为它在 APK 文件本身中以二进制块的形式存在。v1 签名是可见的它是以CERT.RSA 和 CERT.SF文件的形式给出。
Android Studio UI 中提供了 v1 签名的复选框我们需要去除该选择并生成一个签名的 APK。我们也需要做相反的过程。
签名大小字节v13511v23307
看上去从此以后我们使用的是 v2。
下面的操作将无需 IDE 的支持
现在我们要手工编辑我们的 APK 了。我们将使用如下命令
# 1\. 创建一个未签名的 APK。
./gradlew assembleRelease# 2\. 解压缩归档文件。
unzip app-release-unsigned.apk -d app# 对文件进行编辑。# 3\. 压缩归档文件
zip -r app app.zip# 4\. 运行 zipalign。
zipalign -v -p 4 app-release-unsigned.apk app-release-aligned.apk# 5\. 使用 v2 签名运行 apksigner。
apksigner sign --v1-signing-enabled false --ks $HOME/fake.jks --out signed-release.apk app-release-unsigned.apk# 6\. 验证签名。
apksigner verify signed-release.apk详细概述了 APK 签名过程。总而言之gradle 生成了一个未签名的归档文件zipalign 更改了未压缩资源的字节对齐方式用于改进加载 APK 时的 RAM 使用最后 APK 将被加密签名。
未签名且未对齐的 APK 大小为 1902 字节这意味着签名和对齐过程增加了约 1 Kb。
文件大小差异优化为 2608 字节削减 21%
很奇怪我们对未对齐的 APK 解压缩并手工签名并手动移除了META-INF/MANIFEST.MF这削减了 543 字节。如果有人知道原因请告诉我
现在我们的签名 APK 中只有三个文件当然还可以去除resources.arsc因为我们并未定义任何资源
这将使我们仅保留 Manifest 和classes.dex文件两个文件大小相当。
压缩破解Compression Hack优化为 2599 个字节削减 0.5%
让我们将剩余的字符串都更改为‘c’更新版本为 26然后生成一个签名的 APK。
compileSdkVersion 26buildToolsVersion 26.0.1defaultConfig {applicationId c.cminSdkVersion 26targetSdkVersion 26versionCode 26versionName 26}manifest xmlns:androidhttp://schemas.android.com/apk/res/androidpackagec.capplicationandroid:iconandroid:drawable/btn_starandroid:labelcactivity android:namec.c.c这将削减 9 个字节。
尽管文件中的字符数并未改变但是我们更改了‘c’字符的频次。这使得压缩算法可以进一步降低文件的大小。
你好ADB优化到 2462 字节削减 5%
通过移除Activity的 Launch Intent Filter我们可以进一步优化 Manifest。此后我们将使用如下命令加载 App
adb shell am start -a android.intent.action.MAIN -n c.c/.c下面给出新的 Manifest 文件
manifest xmlns:androidhttp://schemas.android.com/apk/res/androidpackagec.capplicationactivityandroid:namecandroid:exportedtrue //application
/manifest我们还移除了加载图标。
削减方法引用优化为 2179 字节削减 12%
我们最初需求是生成一个可安装在设备上的 APK。现在是运行“Hello World”的时候了。
我们的 App 引用了TextView、Bundle和Activity中的方法。通过移除Activity并替换为用户定义的Application类我们可以进一步削减 Dex 文件大小。现在我们的 Dex 文件应该仅引用了单一的方法即Application的构造函数。
现在我们的源文件如下
package c.c;
import android.app.Application;
public class c extends Application {}manifest xmlns:androidhttp://schemas.android.com/apk/res/androidpackagec.capplication android:name.c /
/manifest我们可以使用 adb 验证该 APK 是可以成功安装的也可以通过 Setting App 做验证。 Dex 优化优化为 1961 字节削减 10%
在此次优化中我花费了多个小时研究 Dex 文件格式
意在了解诸如校验码和偏移量等各种机制它们是手工编辑文件中的难点。
但是长话短说被我证实的是只要存在classes.dex文件APK 文件就能安装。因此只要简单地删除原始文件并在终端运行touch classes.dex使用这一空文件就能获得近 10% 的规模削减。
有时看上去最愚蠢的方法反而最有效。
理解 Manifest 文件优化为 1961 字节削减 0%
非签名 APK 中的 Manifest 文件是二进制的 XML 格式该格式看上去并没有官方的文档。我们可以使用 HexFiend编译器去修改文件内容
(https://github.com/ridiculousfish/HexFiend) 。
我们可以猜测出位于文件头部的数个感兴趣项。头四个字节编码了38是与 Dex 文件所使用的版本相同。随后的两个字节编码为660这无疑是文件的大小。
下面我们尝试通过设置 targetSdkVersion 为1并更新文件大小头部为659去删除一个字节。不幸的是Android 系统拒绝了这个非法的 APK因此看上去这里另有玄机。
无需理解 Manifest 文件优化为 1777 字节削减 9%
下面我们让我们对整个文件输入虚字符然后在不更改文件大小的情况下尝试安装 APK。这将确定校验码是否发挥作用以及更改是否使得文件头部的偏移值失效。
令人惊奇的是下图的 Manifest 文件被解释为一个有效的 APK可运行在运行 Oreo 的 Nexus 5X 手机上 我想我听到了负责维护BinaryXMLParser.java的 Android Framework 工程师对着枕头在大声尖叫。
为最大化收益我们将使用空字节Null替换这些虚字符。这可使简化使用 HexFiend 查看文件的重要部分也将使前期的压缩破解可削减一些字节。
UTF-8 格式的 Manifest 文件
下图给出了一些 Manifest 文件中的重要成分。如果没有这些成分APK 将会安装失败。 一些事情即刻是很明显的例如 Manifest 文件和软件包标记。在字符串池中还可以找到软件包名称和 versionCode。
十六进制的 Manifest 文件 以十六进制查看文件可显示文件头部的值这些值描述了字符串池及其它值例如0x9402是文件的大小。字符串也具有一种有意思的编码。如果字段超出了 8 个字节它们的总长度将在随后的两个字节中指定。
但是看上去我们并不能从中做更进一步的削减。
大功告成优化为 1757 字节削减 1%
让我们查看一下最终的 APK。 终归我们使用 v2 签名在 APK 中留名。让我们创建一个利用压缩破解的新密钥库。 这可削减 20 个字节。
第五阶段最终采纳
现在的1757个字节是相当的小。据我所知这是最小的现有 APK。
但是我完全有理由确信Android 社区中会有人能再做进一步的优化并打破我的记录。 更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》 1.Android车载应用开发系统学习指南附项目实战
2.Android Framework学习指南助力成为系统级开发高手
3.2023最新Android中高级面试题汇总解析告别零offer
4.企业级Android音视频开发学习路线项目实战附源码
5.Android Jetpack从入门到精通构建高质量UI界面
6.Flutter技术解析与实战跨平台首要之选
7.Kotlin从入门到实战全方面提升架构基础
8.高级Android插件化与组件化含实战教程和源码
9.Android 性能优化实战360°全方面性能调优
10.Android零基础入门到精通高手进阶之路
敲代码不易关注一下吧。ღ( ´ᴗ )