Alibaba.com瘦包40MB——业界最全的iOS包大小技术总结

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 前言包大小是衡量APP性能的一项重要指标,它直接影响用户的下载点击率(包太大不想下)、下载安装成功率(下载慢不用了)、APP卸载率(太占空间先删掉)。包大小的计算逻辑很简单,它是各种类型的文件占用磁盘大小相加。APP瘦身的技术却很复杂,代码文件的复杂度和编译器策略决定了可执行文件的大小,业务功能和工程架构决定了代码文件的复杂度。iOS APP瘦身,需要掌握的技能有XCode构建技术、LLVM编译器

前言

包大小是衡量APP性能的一项重要指标,它直接影响用户的下载点击率(包太大不想下)、下载安装成功率(下载慢不用了)、APP卸载率(太占空间先删掉)。包大小的计算逻辑很简单,它是各种类型的文件占用磁盘大小相加。APP瘦身的技术却很复杂,代码文件的复杂度和编译器策略决定了可执行文件的大小,业务功能和工程架构决定了代码文件的复杂度。iOS APP瘦身,需要掌握的技能有XCode构建技术、LLVM编译器技术、CocoaPods构建技术、图片压缩技术、持续集成技术。本文总结提炼了Alibaba.com App的瘦身的技术和策略,系统化地介绍APP瘦身的业务价值、分析技术、瘦身技术、防劣化机制,让读者可以系统化地了解APP瘦身的技术体系。本文还基于实践经验,介绍各种瘦身技术的ROI,让读者可以避免踩雷,将资源浪费在效果不佳的技术上。希望对你有所帮助。

业务价值

包体大小每上升6MB,应用下载转化率就会下降1%

在2019谷歌开发者大会上,谷歌给出了一个很详细的数据,包体大小每上升6MB,应用下载转化率就会下降1%。不同地区转化率略有差异,APK包体大小每减少10MB ,全球平均下载转化率会提升1.75%,新兴国家代表印度和巴西下载转化率提升2.0%以上,高端市场代表美国和德国下载转化率提升1.5%。

上图标题:APK减少10MB,在不同国家转化率增长

数据来源:google play 内部数据

详细材料:

白鲸出海:2019谷歌开发者大会首日看点:Google Play的新变化

googleplaydev:Shrinking APKs, growing installs

上述数据调研分析包括是2019年以前,已经有所滞后,仅供参考

包大小影响下载转化率可能有3个原因:

  1. 蜂窝网络环境下,用户不愿意支付流量费用。包大小超过200MB时,App Store会弹框提醒用户下载可能会产生流量费用。
  2. 下载时间太长,用户不愿意等就取消了
  3. 下载过程中出现网络连接问题

虽然Google Play没有给出不同APP类目的数据,但是从以上三个原因推断,不同类目包大小对下载转化率的影响估计差不多。App Store的用户人群比较高端,可以参考美国和德国的数据。

20%的人因为存储空间有限而卸载应用程序

clevertap在2021年做了一项调查,他们调查了2000多个移动应用程序用户,询问了他们卸载移动应用程序的主要原因,其中有20%的人因为存储空间有限而卸载应用程序。

最主要的3个原因是:

  1. 他们不再使用该应用程序
  2. 有限的存储空间
  3. 太多的广告。

详细材料

clevertap:Why Users Uninstall Apps

App Store 发布和下载限制

兼容iOS8的App,主二进制文件的Text段不能超过60MB, 否则将无法提交App Store。App Store下载包超过200MB,无法使用蜂窝流量下载和更新。

分析技术

APP瘦身最终目标是减少App Store的安装包大小和下载包大小,但研发阶段对比XCode构建包大小会更方便,需要理清楚他们之间的口径差异。

结果指标:App Store安装包大小和下载包大小

查看路径是App Store Connect->TestFlight->交付版本->构建版本元数据->App Store文件大小

过程指标:XCode构建包大小

XCode构建产物ipa包的大小和App Store的安装大小口径差异很大,通过模拟Apple处理的流程可以得到它们的关系。开发者上传的ipa包里有多个架构的产物,多套尺寸的图片资源,苹果会进行裁剪和二次分发,转化为App Store下载的ipa包。

构建产物ipad包大小:静态库二进制文件(arm64、armv7)、动态库(arm64、armv7)、asset.car(@2x、@3x)、其他资源文件

App Store的安装大小:静态库二进制文件(单架构)、动态库(单架构)、asset.car(单尺寸)、其他资源文件

使用lipo工具拆分单架构

lipo "originalExecutable" -thin arm64 -output "arm64Executable"

使用assetutil工具拆分asset

xcrun --sdk iphoneos assetutil --scale 3 --output "$targetFolder/Assets3.car" "$sourceFolder/Assets.car"

构建包组成

学习如何治病前,得先了解人体的构成。同样的道理,我们首现要了解iOS工程结构和IPA包结构。

iOS工程结构:iOS工程由壳工程和Pod模块组成,模块有静态库和动态库两种类型。壳工程的构成有主Target、Apple插件Target。模块内部的构成有源代码文件(OC、C、C++的.h和.m)、nib、bunlde、xcassets、多语言文件、各种配置文件(plist、json)。IPA包结构:iOS上传到App Store是IPA包,IPA包解压后是一个文件夹,内部由各种类型的文件构成,主要包括MachO可执行文件、.framework(动态库)、Assets.car、.appex(Apple插件)、.strings(多语言)、.bundle、nib、json、png...。从iOS工程到IPA包:iOS工程构建为IPA包,核心的变化是编译和文件拷贝,静态库里的源代码会被编译为MachO可执行文件,xcassets文件夹会被转化为Assets.car,其他都可以简单理解为文件拷贝。

通过分析构建包的组成,可以判断有哪些优化空间。如果动态库占比太高,就可能有大量可裁剪代码。建议根据资源大小敏感程度划分,比如:MachO executeable(包含所有静态库)、动态库、Apple Extension、assets.car、bundle、nib、音频、视频。

Pod模块大小

APP瘦身会涉及大量业务模块,离不开业务团队的参与。因此,我们需要分析出每个Pod模块的大小,从而可以横向比较各个业务团队的包大小占比。静态库和动态库计算的原理不同。对于静态库,先解析linkmap数据,计算出Pod模块代码大小,在解析Pods-targetName-resource.sh的资源拷贝代码,计算出拷贝到Pod模块的资源大小。对于动态库,先使用lipo拆分动态库的二进制文件,计算出单架构的代码大小,然后再计算动态库framework内的资源文件,得到动态库的资源文件大小。

运行时Objc类覆盖率

如果能知道App运行时有哪些类被使用过,就可以下线掉无用的模块或代码文件,Objc类覆盖率指标可以帮到我们。APP运行时,某1个Pod模块被加载的类数量除以所有类数量,可以称为这个模块的Objc类覆盖率。核心技术是判断一个类是否被加载过,下面介绍一个经过线上验证的轻量级方案。ObjC的类第一次被使用时会调用+initialize方法,类被加载过后cls->isInitialized会返回True。isInitialized方法读取了metaClass的data变量里的flags,如果flags里的第29位为1,则返回True。

// objc-class.mm
Class class_initialize(Class cls, id inst) {
    if (!cls->isInitialized()) {
        initializeNonMetaClass (_class_getNonMetaClass(cls, inst));
    }
    return cls;
}


// objc-runtime.h
#define RW_INITIALIZED        (1<<29)
bool isInitialized() {
    return getMeta()->data()->flags & RW_INITIALIZED;
}

未被使用的图片资源

技术原理是先解析出所有拷贝到构建产物的资源文件,再解析出代码中实际引用到的资源文件,两者的差集就是无用资源。

第一步获取全量资源文件。在Cocoapos工程中,“Pods-targetName-resource.sh”脚本负责拷贝Pod里的文件资源到构建产物,包括所有文件类型bundle、xcassets、json、png。解析该脚本可以得到每个Pod模块都拷贝了哪些图片资源。

// Pods-targetName-resource.sh
install_resource "${PODS_ROOT}/APodName/APodName.framework/APodName.bundle"
install_resource "${PODS_ROOT}/BPodName/BPodName.framework/BPodName.xcassets"
install_resource "${PODS_ROOT}/BPodName/BPodName.framework/xxx.png"

第二步,获取代码中实际引用到的资源文件。OC代码中引用资源文件都是以字符串字面量的形式声明,构建后存放在Mach-O文件"__cstring" section。利用strings解析framework的二进制文件就可以得到代码中所有的字符串声明,然后基于代码的各种引用方式匹配“xxx”,"xxx@2x.png","xxx.png","xxx.bundle/xxx.png"

strings executable | grep 'xxx' > cstrings.txt

编译时未被引用的类

iOS编译的产物是Mach-o格式的,文件里 __DATA __objc_classrefs 段记录了所有引用过的类的地址,__DATA __objc_classlist段记录了所有类的地址,两者Differ可以得到未被引用类的地址。然后将地址符号化,就可以得到未被引用类信息。因为Objc是动态语言,如果使用runtime动态调用某个class,这种情况扫描不出来。(比如Target Action和 JS Core)

otool -v -s __DATA __objc_classrefs xxxMainClient  #读取__DATA Segment中section为__objc_classrefs的符号
otool -v -s __DATA __objc_classlist xxxMainClient #读取__DATA Segment中section为__objc_classlist的符号
nm -nm xxxMainClient

扫描未使用类的开源工具

瘦身技术

包大小瘦身可以从纯技术视角瘦身,也可以从逻辑视角瘦身。

从纯技术视角有两种思路,第一种思路是优化编译逻辑, 第二种思路是删减各种其他类型(非编译产物)的文件。编译优化对业务逻辑无侵入式,风险和成本比较低,但收益通常也不高。删减文件则比较复杂,删减资源文件收益高但成本不小,逐个删减源码文件风险高且收益小。

相比而言,从逻辑的视角瘦身效果会更明显。站在逻辑的视角,工程是由许多功能模块组成的,主要可以分为业务功能模块(首页、搜索、收银台、订单)、基础功能模块(网络库、图片库、中间件、各种三方库...)。大型工程通常几百甚至上千个模块组成,小模块的有几十KB,大的模块有1MB~10+MB。功能模块内聚性强,当某个功能模块可以废弃或被替代时,整体下线的风险和成本比较低,ROI很高。

瘦身技术大图

根据项目经验,我梳理出各种优化方法的ROI,下面依次介绍。

组件瘦身

组件瘦身的ROI:类加载率0%的组件 > 无用功能组件 > 重复功能组件

“类加载率0%的组件”是一个完整的Pod模块,模块的代码和资源可以被整体删除,ROI最高。“无用功能组件”通常是模块内某个文件目录,需要处理一些耦合,梳理资源的归属,ROI其次。“重复功能组件”需要适当重构,迁移成本和风险最高,ROI相对较低。

类加载率0%的组件 

“类加载率0%的组件”是指运行时没有任何一个类被加载过的组件,它在运行过程中完全没有用到,可以整体下线。统计类加载率时一般会进行采样,有些低频业务组件可能会误报,下线前需要和业务Owner二次确认。

无用功能组件

“无用功能组件”是逻辑上已经没用的组件,它可能代码还有耦合,但逻辑已经没用,通过重构就可以下线。比如AB实验没效果的业务代码、往年大促的业务代码、业务改版后遗留的老业务、已经过时的三方库。

重复功能组件

对于大规模团队,不同业务线可能会引入“重复功能组件”,比如图片选择器、缓存库、UI组件。重复的功能组件会带来不必要的包大小压力,同时也会带来维护成本。

资源瘦身

资源瘦身ROI:大资源>有损压缩>重复资源>iconfont>多语言文案瘦身>无用资源

大资源

对大资源进行单点优化收益很大,优先分析100KB以上的资源。比如音频文件,我们工程中音频铃声900KB,优化后去掉了700KB。

有损压缩

XCode构建时会做“compile asset catalog”,会重新对图片进行无损压缩。因此使用imageoptim等工具进行无损压缩效果不明显,其中压缩png图片没有效果,压缩jpg图片有一定效果。根据实践经验,icon做有损压缩并不影响视觉体验,压缩率可以达到70%~80%。比如使用tinypng算法压缩一张425.9 KB的png图片,压缩后79.8 KB,压缩率达到81%。业界有不少png压缩工具,我们使用到的有tinypngpngquantpngcrushoptipng(无损)、advpng。实测发现不同的图片,压缩效果最好的工具是不一样,所以压缩图片时可以用每个工具尝试,然后取效果最好的。需要注意的是,压缩APNG图片可能会变成非动图,尽量避免对APNG图片进行压缩,压缩前先识别是否APNG图片。

# 判断是否APNG格式,APNG格式不压缩
function isApng() {
    ret=`grep "acTL" "$1"`
    lastWord="${ret##* }"
    if [[ $lastWord == "matches" ]]; then
        echo "YES"
    else
        echo "NO"
    fi
}

# 使用各种工具压缩png图片
# pngquant
pngquant  256 -s5 --quality 70 --output "${tmpFileName}" -- "${tmpOrigName}" 
# pngcrush
pngcrush -brute -rem alla -nofilecheck -bail -blacken -reduce -cc -- "$tmpOrigName" "$tmpFileName"
# optipng
optipng -o6 -i0 -out "$tmpFileName" -- "$tmpOrigName"
# advpng
cp "$tmpOrigName" "$tmpFileName"
advpng -4 -z -- "$tmpFileName"

# 逐个比较压缩效果,保留效果最好的压缩图片
sizeBefore=`stat -f%z "${tmpOrigName}"`
sizeBefore=`expr $sizeBefore`
sizeNow=`stat -f%z "${tmpFileName}"`
sizeNow=`expr $sizeNow`

if [ "$sizeNow" -lt "$sizeBefore" ]; then
	# 大小变小了
	echo "[advpng] $tmpOrigName"
	retImg="$tmpFileName"
fi

# 记录hash值
img_sum=`/usr/bin/shasum -a 256 "$retImg" | cut -d " " -f1`
cache_file="$cacheFolder/$img_sum.png"

无用资源

业务长期迭代会积累许多无用的资源。通过代码静态扫描,可以分析出没有被引用的图片资源。我们有工程无用资源有12MB,其中有20个模块无用资源超过100KB。

重复图标

不同业务需求会引入重复资源,长期积累也会造成很大浪费。解决方案是在构建时计算资源的哈希值,去重相同哈希资源,并保留源文件名和哈希值的映射表。运行时Hook 资源加载的”imageNamed“方法,根据映射表替换资源名称。

ODR

iOS不能像Android一样,运行时更新Framework。iOS的ODR技术(On Demand Resource)提供了运行时动态下载的能力。如果启动用不到的资源文件,可以通过ODR处理。

iconfont

iconfont支持缩放、修改颜色,它size小,适合用于箭头、占位图等图标场景,使用iconfont可以减少包大小也能提高开发视觉体验的统一性。首先,基于设计规范出一套完整的iconfont,封装iconfont组件,提供易用的API。然后禁止新业务场景增加图片资源,团队形成开发习惯后再逐步替换存量的图片。

多语言文案

对于国际化App,多语言文案也是大头。我们App支持20个语种,每个语种4000多条文案,文案总大小6MB,瘦身后减少了2MB。

编译优化:

编译优化ROI:Optimization Level > 动态库复用主二进制静态库> 链接器产物压缩

XCode编译优化选项中,Optimization Level效果最明显,建议所有模块构建都开启Oz选项。如果APP有动态库,并且依赖了openssl等基础静态库,建议和APP工程共享,并通过EXPORTED_SYMBOLS_FILE的选型,确保动态库中需要用到的符号都在编译过程保留。如果团队技术储备强,可以自研链接器产物压缩技术,效果也很大。具体效果如下。

主工程

OC模块

62880行代码

C++模块

LTO

<10KB

开启前

modulesize:2020KB,

MainProjectSize:6463KB

开启后

modulesize:1582KB

MainProjectSize:6888KB

收益:13KB

/

剥离符号表:Strip Linked Product

<10KB

/

/

精简编译产物Oz:Optimization Level

100-200KB

Os:2261KB

Oz:2020KB

收益:241KB

/

Symbols Hidden by Default

<10KB

优化前:2020KB

优化后:2020KB

收益:0KB

/

剔除未引用的C/C++/Swift代码:Dead Code Stripping

<10KB

/

/

Asset Catalog Compiler

<10KB

动态库共享APP基础静态库

/

/

动态库A依赖了公共的静态库库B,通过设置只导出必要符号

收益:1367KB

C++全局静态数组改成动态分配内存

/

收益:200KB

C++去掉RTTI支持

/

收益:1MB

精简编译产物Oz:Optimization Level

Optimization Level多个级别,-Oz比-O3的编译产物体积小10%左右。设置-Oz以后,XCode会优化连续的汇编指令,从而减少二进制大小,但副作用是执行速度会变慢。C++工程建议都开启。

主工程Release
Optimization Level :-Oz

Framework工程
Optimization Level :-Oz

LTO(OC/C++)

LTO 是在LLVM链接时,优化跨模块调用代码。根据大家分享的经验,它不同APP包大小优化效果差异较大,需要具体测试。另外它也有一些副作用,建议只在Release包开启。LTO介绍

优点:

  • 将一些函数內联化
  • 去除了一些无用代码
  • 对程序有全局的优化作用

缺点:

  • 降低编译链接速度,只建议在打正式包时开启
  • 降低 link map 可读性(出现 XX-lto.thin的类

主工程设置

主工程Release
Link-Time Optimization 设置为Incremental

framework工程Release
Link-Time Optimization 设置为Incremental

动态库复用主二进制静态库

C++动态库经常用到一些基础库比如openssl、libyuv、libcurl,他们一般是静态库。如果动态库引用了静态库,它编译时默认会内嵌静态库的所有符号。虽然我们可以在动态库中设置只导出需要用到的静态库符号,但是有可能多个动态库都用到了同一个基础库,这样还是会造成基础库的冗余。比如openssl大小1MB,如果A、B两个动态库依赖了openssl,APP也引用了openssl,最终ipa包实际有3个openssl,有2MB大小是冗余的。

这种场景下,最佳解决方案是共享符号表,让动态库可以调用主二进制的基础库符号,从而可以去掉内置的静态库。只要修改XCode的Link配置,无需额外的代码开发。

动态库工程:

1设置当遇到未定义的函数时,动态查找APP主二进制符号表。

2 关闭bitcode

Other Linker Flags -> -undefined dynamic_lookup
Enable Bitcode -> No

3导出动态库需要调用的外部符号,写到一个文件exported_symbols内

nm -u  xxx.framework/xxx > exported_symbols.txt

APP工程:

1配置需要导出exported_symbols文件内的所有符号,避免编译时动态库需要用到的符号被strip掉。

2关闭bitcode。

// exported_symbols.txt是需要被导出的符号文件路径
EXPORTED_SYMBOLS_FILE -> exported_symbols.txt
Enable Bitcode -> No

如果C++动态库里依赖了外部静态库,该静态库的符号默认会被全部导出。实际上动态库只用了其中的部分符号,可以设置导出符号的白名单,从而减少导出没有用到的静态库符号。

注意事项:1APP和framwork工程都要关闭bitcode,否则编译不过。2如果多个动态库都使用同一个基础库,导出的符号表需要取并集。3动态库升级要及时更新符号表,基础库升级要测试兼容性。

链接器产物压缩(黑科技)

iOS工程构建产物是MachO文件,MachO文件中的TEXT段存放了各种只读的数据段,__cstring段存放了普通的C String,__objc_methtype和__objc_methname存放了Objc的方法签名和方法名。比入Objc代码中声明的@"Hello world",底层会产生一个CFString,构建后存放在__cstring中。这些数据很占空间,一般工程至少会有10MB以上,压缩的收益很可观。我们上线后,App Store安装包大小从191MB优化到174MB,减少了16MB。

技术原理:

链接时将TEXT段数据移到__DATA段并压缩,运行时先执行解压代码,解压TEXT段数据存到自定义段中,将代码中对字符串的引用的地址修正为解压后的自定义段。

剥离符号表:Strip Linked Product

Strip Linked Product设置会剥离特定的符号,Debug环境不要设置YES,否则调试时看不到符号。

主工程Release
Deployment Postprocessing :YES
Strip Linked Product :YES
Strip Style :All Symbols(剥离所有符号表和重定向信息)

Framework工程
Deployment Postprocessing :YES
Strip Linked Product :YES
Strip Style :Non-Global Symbols(剥离包括调试信息等非全局的符号,保留外部符号)

说明:

1静态库不能将Strip Style 设置为All Symbols,因为剥离了所有符号的静态库是无法被正常链接的

2去除符号不影响 dSYM 文件中的符号信息,查看崩溃日志时,可以从 dSYM 文件中找对应符号

Symbols Hidden by Default

Symbols Hidden by Default用于设置符号默认可见性,如果设置为YES,XCode会把所有符号都定义为”private extern”,包大小会略有减少。动态库设置为NO,否则会有链接错误。

主工程Release
Symbols Hidden by Default :Yes

Framework工程 静态库/动态库
Symbols Hidden by Default :NO

剔除未引用的C/C++/Swift代码:Dead Code Stripping

Dead Code Stripping开启后会在链接时移除未使用的代码,它对静态语言C/C++/Swift有效,对动态语言OC无效。

主工程
Dead Code Stripping :Yes

Asset Catalog Compiler

Optimization有三个选项,空、time和Space,选择Space可以优化包大小

主工程
Asset Catalog Compiler->Optimization设置为space

C++只导出必要符号:Symbol Visibility 

C++的静态库和动态库都只导出必须的符号,默认设置为隐藏所有符号,然后用Visibility Attributes单独控制需要导出的符号

默认隐藏所有C++符号

Other C++ Flags->添加-fvisibility=hidden

设置需要导出的符号

__attribute__((visibility("default"))) void MyFunction1() {} 
__attribute__((visibility("default"))) void MyFunction2() {} 
....

代码下线

根据Objc类覆盖率统计的结果,可以逐步下线掉未被使用的类。代码文件的量级比较小,下线代码需要仔细确认,避免引起功能问题或crash,ROI比较低。

Flutter专项

Flutter是单独构建的,引入的内容包括Flutter引擎产物、Flutter业务代码产物。APP如果引入Flutter,需要对Flutter进行专项瘦身。

精简编译产物Oz:Optimization Level

-Oz选项相比Os,收益预估11%,但首屏性能1%~9%的损耗

Dart符号剔除和混淆

根据官方文档,可以通过--dwarf-stack-trace选项去除Dart标准的调试符号。通过--obfuscate 选项,可以将较长的符号替换为短符号,副作用是符号会被混淆。实践效果:Release环境下,--dwarf-stack-trace和--obfuscate选项开启后减少14%的大小

Flutter icon摇树优化

--tree-shake-icons

实践效果:Alibaba.com App开启后减少了300KB左右大小

去除NOTICES文件

实践效果:压缩前700KB,压缩后80KB

防劣化机制

增量标准和集成卡口

包大小瘦身的技术就像各种减肥运动,跑步、跳舞、燃脂瑜伽。但想达到减肥的目标,只靠运动是不够的,还得控制卡路里的摄入,管住嘴的人最后才能减肥成功。因此,我们还需要”新增卡路里的规范”(包大小增量规范)和监工(集成卡口)。持续集成当中加入一个卡口插件,分析构建包的linkemap文件得到模块的大小可,然后对比基线数据,如果违反了包大小增量标准,则禁止集成。

包大小增量规范(参考):

  1. 基线数据:基于特定版本,通过上文提到的”计算模块大小“的方法,计算出每个模块的基线数据
  2. 存量模块:模块增量超过基准数据100KB时禁止集成。设置补偿机制,如果能对存量模块瘦身,可以抵消新增的模块大小。对于特殊情况可以走特殊审批,加入审批流程是启发大家反思。增加那么多是否有价值?有没有带入不必要的资源?
  3. 新模块: 新业务功能需要走审批流程,评估新业务价值和包大小增量是否合理。

横向对比业务健康度

当包大小从技术层面已经优化到极致时,想要进一步瘦身,只能从业务价值的角度去挖掘。如果一个模块磁盘尺寸大,用户使用量却少,那可以认为它对业务的价值较小。因此我们可以定义一个技术指标来衡量模块单位大小的业务贡献度。基于容积率指标,我们就可以横向对比各个业务,要求容积率低的业务做包大小瘦身,或下线不太重要的业务功能。

容积率 = Business PV / Business size

总结

总结一下包大瘦身的实施路径。第一步,制定目标,跟踪APP下载转化率(App Store Connect Analytics)、APP安装包大小、XCode构建包大小等结果指标。第二步,建设分析体系,包括Pod模块大小分析、Objc类覆盖率分析、无用图片资源分析等。第三步,根据ROI使用各项瘦身技术,组件瘦身>资源瘦身>编译优化>代码下线.第四步,建设防劣化机制,包括增量标准、集成卡口能力、健康度分析。

经过半年的努力,Appstore安装包大小从190MB下降到150MB,感谢念纪、牧汉、必言提供中台技术产品,也感谢ICBU业务同学的支持优化治理。

参考

白鲸出海:2019谷歌开发者大会首日看点:Google Play的新变化

http://www.baijingapp.com/article/24808

googleplaydev:Shrinking APKs, growing installs

https://medium.com/googleplaydev/shrinking-apks-growing-installs-5d3fcba23ce2

clevertap:Why Users Uninstall Apps

https://clevertap.com/blog/uninstall-apps/

Pngcrush

https://pmt.sourceforge.io/pngcrush/

pngquant

https://pngquant.org/

OptiPNG

http://optipng.sourceforge.net/

advpng

http://www.advancemame.it/doc-advpng.html

相关文章
|
6天前
|
安全 Android开发 iOS开发
安卓与iOS的较量:技术特性与用户体验的深度解析
在移动操作系统的战场上,安卓和iOS一直占据着主导地位。本文将深入探讨这两大平台的核心技术特性,以及它们如何影响用户的体验。我们将从系统架构、应用生态、安全性能和创新功能四个方面进行比较,帮助读者更好地理解这两个系统的异同。
34 3
|
13天前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
2月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发:平台间的技术较量与融合
在移动应用开发的广阔天地中,安卓和iOS这两大操作系统如同两座巍峨的山峰,各自占据着半壁江山。它们在技术架构、开发工具、用户体验等方面展现出独特的魅力与差异,同时也在相互借鉴与融合中共同推动着移动技术的发展。本文将深入探讨安卓与iOS开发中的技术差异与融合趋势,为开发者提供一场思想的盛宴。
|
6天前
|
安全 Android开发 iOS开发
安卓与iOS的较量:技术特性与用户体验的深度剖析
在移动操作系统的战场上,安卓和iOS一直是两个重量级选手。本文将深入探讨两者的技术架构、安全性、应用生态以及用户体验等方面的差异,并尝试从用户和开发者的角度出发,分析这两个系统的优势与不足。通过比较,我们不仅能更好地理解各自的特点,还能洞察未来移动技术的发展趋势。
|
14天前
|
移动开发 搜索推荐 Android开发
安卓与iOS开发:一场跨平台的技术角逐
在移动开发的广阔舞台上,两大主角——安卓和iOS,持续上演着激烈的技术角逐。本文将深入浅出地探讨这两个平台的开发环境、工具和未来趋势,旨在为开发者揭示跨平台开发的秘密,同时激发读者对技术进步的思考和对未来的期待。
|
17天前
|
安全 Android开发 Swift
安卓与iOS开发:平台差异与技术选择
【8月更文挑战第26天】 在移动应用开发的广阔天地中,安卓和iOS两大平台各占一方。本文旨在探索这两个系统在开发过程中的不同之处,并分析开发者如何根据项目需求选择合适的技术栈。通过深入浅出的对比,我们将揭示各自平台的优势与挑战,帮助开发者做出更明智的决策。
|
26天前
|
移动开发 开发工具 Android开发
探索安卓与iOS开发的差异:技术选择的影响
【8月更文挑战第17天】 在移动应用开发的广阔天地中,安卓和iOS两大平台各领风骚。本文通过比较这两个平台的编程语言、开发工具及市场策略,揭示了技术选择对开发者和产品成功的重要性。我们将从开发者的视角出发,深入探讨不同平台的技术特性及其对项目实施的具体影响,旨在为即将步入移动开发领域的新手提供一个清晰的指南,同时给予资深开发者新的思考角度。
|
30天前
|
编解码 Android开发 iOS开发
安卓与iOS开发:平台差异下的技术创新之路
在数字时代的浪潮中,移动应用开发如同两股潮流——安卓与iOS,各自携带着独特的技术生态和文化基因。本文将深入探讨这两大平台的开发环境、编程语言和工具的差异,以及它们如何塑造了不同的用户体验和技术趋势。通过比较分析,我们旨在揭示跨平台开发的可能性和挑战,同时探索未来技术创新的方向。让我们一起跟随代码的足迹,穿越安卓的开放草原和iOS的精密园林,发现那些隐藏在平台差异之下的创新机遇。
28 1
|
1月前
|
移动开发 Java Android开发
安卓与iOS开发:选择的艺术与技术的较量
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据着半壁江山。本文将探讨这两个平台在开发过程中的异同,以及它们如何影响开发者的选择。我们将从技术栈、市场份额、用户群体等方面进行分析,并结合案例来说明不同平台的优势与挑战。无论你是初涉移动开发领域的新手,还是经验丰富的老手,这篇文章都将为你提供有价值的见解。让我们一起探索这片充满机遇与挑战的土地吧!
|
11天前
|
iOS开发 Android开发 MacOS
从零到全能开发者:解锁Uno Platform,一键跨越多平台应用开发的神奇之旅,让你的代码飞遍Windows、iOS、Android、macOS及Web,技术小白也能秒变跨平台大神!
【8月更文挑战第31天】从零开始,踏上使用Uno Platform开发跨平台应用的旅程。只需编写一次代码,即可轻松部署到Windows、iOS、macOS、Android及Web(通过WASM)等多个平台。Uno Platform为.NET生态带来前所未有的灵活性和效率,简化跨平台开发。首先确保安装了Visual Studio或VS Code及.NET SDK,然后选择合适的项目模板创建新项目。项目结构类似传统.NET MAUI或WPF项目,包含核心NuGet包。通过简单的按钮示例,你可以快速上手并构建应用。Uno Platform让你的技术探索之旅充满无限可能。
17 0