百度APP iOS端包体积50M优化实践(三) 资源优化

news2024/11/24 18:34:02

在这里插入图片描述

01 前言

百度APP iOS端包体积优化系列文章的前两篇重点介绍了包体积优化整体方案、各项优化收益和图片优化方案,图片优化是从无用图片、Asset Catalog和HEIC格式三个角度做深度优化。本文重点介绍资源优化,在百度APP实践中,资源优化包括大资源优化、无用配置文件和重复资源优化。不管是资源优化还是代码优化,都需要分析Mach-O文件,以获取资源和代码的引用关系,本文先详细介绍Mach-O文件。

百度APP iOS端包体积优化实践系列文章回顾:

《百度APP iOS端包体积50M优化实践(一)总览》

《百度APP iOS端包体积50M优化实践(二) 图片优化》

02 Mach-O文件详解

2.1 简介

Mach-O为Mach Object文件格式的缩写,用于记录可执行文件、目标代码、动态库和内存转储的文件格式,是运用于Mac以及iOS系统上。

2.2 分析Mach-O文件的工具

2.2.1 MachOView分析

  • MachOView下载地址:http://sourceforge.net/projects/machoview/

  • MachOView源码地址:https://github.com/gdbinit/MachOView

用MachOView能查看MachO文件信息,启动MachOView,在状态栏中点击file,打开MachO文件,如下图所示。

图片

2.2.2 otool命令查看

mac自带otool工具,otool -arch arm64 -ov xxx.app/xxx,可获取所有项目的类结构及定义的方法,示例代码如下所示:

Contents of (__DATA,__objc_classlist) section
0000000100008238 0x100009980
isa        0x1000099a8
superclass 0x0 _OBJC_CLASS_$_UIViewController
cache      0x0 __objc_empty_cache
vtable     0x0
data       0x1000083e8
flags          0x90
instanceStart  8
instanceSize   8
reserved       0x0
ivarLayout     0x0
name           0x100007349 ViewController
baseMethods    0x1000082d8
entsize 24
count   11
name    0x100006424 test4
types   0x1000073e4 v16@0:8
imp     0x100004c58
name    0x1000063b4 viewDidLoad
*****

下面列举otool常见命令:

图片

2.3 查看文件格式

采用file命令可以查看文件格式,lipo -info可查看该Mach-O文件支持的具体CPU架构。

~ % file /Users/ycx/Desktop/demo.app/demo
/Users/ycx/Desktop/demo.app/demo: Mach-O 64-bit executable arm64
~ % lipo -info /Users/ycx/Desktop/demo.app/demo
Non-fat file: /Users/ycx/Desktop/demo.app/demo is architecture: arm64

2.4 文件结构

2.4.1 总体结构

图片

Mach-O文件主要由三部分组成Header、LoadCommands、Data,在MachO文件的末尾,还有Loader Info信息,表示可执行文件依赖的字符串表,符号表等信息。

2.4.2 Header(头部)

2.4.2.1 数据结构

Header(头部): 用于描述当前Mach-O文件的基本信息(CPU类型、文件类型等),XNU代码路径:EXTERNAL_HEADERS/mach-o/loader.h,数据结构如下所示:

struct mach_header_64 {
  uint32_t  magic;    /* mach magic number identifier */
  cpu_type_t  cputype;  /* cpu specifier */
  cpu_subtype_t  cpusubtype;  /* machine specifier */
  uint32_t  filetype;  /* type of file */
  uint32_t  ncmds;    /* number of load commands */
  uint32_t  sizeofcmds;  /* the size of all the load commands */
  uint32_t  flags;    /* flags */
  uint32_t  reserved;  /* reserved */
};

2.4.2.2 查看字段值

命令otool -hv可查看Header每个字段值。

% otool -hv demo
demo:
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64    ARM64        ALL  0x00     EXECUTE    22       3040   NOUNDEFS DYLDLINK TWOLEVEL PIE

用MachOView查看Header数据值:

图片

2.4.2.3 字段具体含义

各个字段具体含义如下所示:

图片

2.4.3 LoadCommands(加载命令)

2.4.3.1 数据结构

LoadCommands(加载命令): 用于描述文件的组织架构和在虚拟内存中的布局方式,告诉操作系统如何加载Mach-O文件中的数据。XNU代码路径:EXTERNAL_HEADERS/mach-o/loader.h,数据结构如下所示,其中cmd代表加载命令类型,cmdsize代表加载命令大小,在load_command数据结构后面加一个特定结构体信息,不同的cmd类型,结构体也不同。

struct load_command {
  uint32_t cmd;    /* type of load command */
  uint32_t cmdsize;  /* total size of command in bytes */
};
/* Constants for the cmd field of all load commands, the type */
#define  LC_SEGMENT  0x1  /* segment of this file to be mapped */
#define  LC_SYMTAB  0x2  /* link-edit stab symbol table info */
#define  LC_SYMSEG  0x3  /* link-edit gdb symbol table info (obsolete) */
#define  LC_THREAD  0x4  /* thread */
#define  LC_UNIXTHREAD  0x5  /* unix thread (includes a stack) */
#define  LC_LOADFVMLIB  0x6  /* load a specified fixed VM shared library */
#define  LC_IDFVMLIB  0x7  /* fixed VM shared library identification */
#define  LC_IDENT  0x8  /* object identification info (obsolete) */
#define LC_FVMFILE  0x9  /* fixed VM file inclusion (internal use) */
#define LC_PREPAGE      0xa     /* prepage command (internal use) */
#define  LC_DYSYMTAB  0xb  /* dynamic link-edit symbol table info */
#define  LC_LOAD_DYLIB  0xc  /* load a dynamically linked shared library */
#define  LC_ID_DYLIB  0xd  /* dynamically linked shared lib ident */
#define LC_LOAD_DYLINKER 0xe  /* load a dynamic linker */
#define LC_ID_DYLINKER  0xf  /* dynamic linker identification */
#define  LC_PREBOUND_DYLIB 0x10  /* modules prebound for a dynamically */
*****

2.4.3.2 查看字段值

用otool -lv命令可以看到该字段全部信息,如左下图所示,此外,我们也可用MachOView工具可更直观地观察具体字段,如右下图所示。

图片

2.4.3.3 cmd类型及其具体作用

常见的cmd类型及其具体作用如下面表格所示:

图片

2.4.3.4 LC_SEGMENT_64

2.4.3.4.1 数据结构

在众多cmd命令中,我们需要重点关注的是LC_SEGMENT/LC_SEGMENT_64,LC_SEGMENT是32位,LC_SEGMENT_64是64位,目前主流机型是LC_SEGMENT_64。LC_SEGMENT_64作用是如何将Data中的各个Segment加载入内存中,而和我们APP相关的代码及数据,大部分位于各个Segment中。其数据结构名称是segment_command_64,XNU代码路径:EXTERNAL_HEADERS/mach-o/loader.h,源码如下所示:

struct segment_command_64 { /* for 64-bit architectures */
  uint32_t  cmd;    /* LC_SEGMENT_64 */
  uint32_t  cmdsize;  /* includes sizeof section_64 structs */
  char    segname[16];  /* segment name */
  uint64_t  vmaddr;    /* memory address of this segment */
  uint64_t  vmsize;    /* memory size of this segment */
  uint64_t  fileoff;  /* file offset of this segment */
  uint64_t  filesize;  /* amount to map from the file */
  vm_prot_t  maxprot;  /* maximum VM protection */
  vm_prot_t  initprot;  /* initial VM protection */
  uint32_t  nsects;    /* number of sections in segment */
  uint32_t  flags;    /* flags */
};

图片

Mach-O文件有多个段(Segment),每个段有不同的功能,每个段又按不同功能划分为多个区(section),四个Segment为__PAGEZERO、__TEXT、_DATA和_LINKEDIT,下面详细介绍。

2.4.3.4.2 _PAGEZERO

图片

__PAGEZERO Segment是空指针陷阱段,主要是用来捕捉NULL指针的引用,是Mach内核虚拟出来的,是Mach-O加载进内存之后附加的一块区域,maxprot和initprot值都为VM_PROT_NONE,表示它不可读,不可写,如果访问__PAGEZERO段,会引起程序崩溃。从上图可以发现,VM Size是4GB,但是真实的File Size大小是0,它只是一个逻辑上的段,在Data中,根本没有对应的内容,也没有占用任何硬盘空间。

2.4.3.4.3 _TEXT

图片

__TEXT Segment对应的就是代码段,下图是一张示例截图,其有11个Section,该段对应的内容加载到内存的过程是:从File Offset开始加载大小为File Size的文件,从虚拟地址VM Address开始装填,大小也是VM Size,VM Size跟文件大小File Size是相同的,我们发现其File Offset为0,在Mach-O文件布局中,__TEXT类型的Segment前面有_PAGEZERO类型的Segment,但_PAGEZERO段的File Offse和File Size为0,所以__TEXT段的File Offset为0。

maxprot和initprot值都为VM_PROT_READ和VM_PROT_EXECUTE,代码段权限是只读和可执行,防止在内存中被修改。

2.4.3.4.4 _DATA

图片

__DATA Segment对应的就是数据段,maxprot和initprot值都为VM_PROT_READ和VM_PROT_WRITE,数据段权限是可读和可写。

2.4.3.4.5 _LINKEDIT

图片

__LINKEDIT Segment用于描述链接信息段,指向存放 link 操作必要的数据段。

2.4.4 Data(数据段)

图片

Mach-O的Data部分,其实是真正存储APP二进制数据的地方,前面的header和load command,仅是提供文件的说明以及加载信息的功能。

Data(数据段): 主要是代码、数据,包含了Load commands中需要的各个段(Segment)的数据,每个Segment可以有多个Section,下面列举一些常见的 Section。在Data(数据段)中,大写的字符串(如__TEXT)代表的是Segment,小写的字符串(如__objc_methtype)代表的是Section。

图片

03 资源优化

3.1 简介

作为一个航母级别的APP,百度APP技术栈丰富多样,市面上常见的技术框架都有使用,如Hybrid框架、小程序框架、React Native框架、KMM和端智能。此外,百度APP作为日活过亿的APP,为满足用户复杂多变的需求,具有的功能包罗万象,如搜索、Feed、短视频、直播、购物、小说、地图、网盘、美颜、人脸识别、AR库等,导致内置的大块资源(大于40K)就有26M,具有很大的优化空间,资源优化分为三个部分,分别是大资源优化、无用配置文件和重复资源优化,本章节接下来详细介绍各个模块的优化方案。

3.2 大资源优化

3.2.1 获取大资源

资源是指plist、js、css、json、端智能模型文件等,因这些文件和图片在优化方式差异很大,所以把两者区分开来。获取大资源主要途径是递归遍历ipa包的所有资源,体积大于指定阈值的文件就是我们要针对性优化的大资源,在百度APP优化实践中我们选取了40K作为阈值,参考脚本如下所示:

def findBigResources(path,threshold):
    pathDir = os.listdir(path)
    for allDir in pathDir:
        child = os.path.join('%s%s' % (path, allDir))
        if os.path.isfile(child):
            # 获取读到的文件的后缀
            end = os.path.splitext(child)[-1]
            # 过滤掉dylib系统库和asset.car
            if end != ".dylib" and end != ".car":
                temp = os.path.getsize(child)
                # 转换单位:B -> KB
                fileLen = temp / 1024
                if fileLen > threshold:
                    #print(end)
                    print(child + " length is " + str(fileLen));
        else:
            # 递归遍历子目录
            child = child + "/"
            findBigResources(child,threshold)

3.2.2 优化方法

  • 异步下载:只要APP首次启动时不需要加载该资源,或者即使首次启动需要加载但是使用频率不高,那么该资源就可以走异步下载;

  • 资源压缩:当APP首次启动需要加载且频率较高的情况下,可以对大块资源先进行压缩内置APP,启动阶段异步线程解压再使用;

3.2 无用的配置文件

3.3.1 获取配置文件

从ipa包中获取plist、json、txt、xib等配置文件,百度技术方案采用的是排除法,因为实践中发现配置文件格式千奇百怪,很多业务模块出于安全考虑自定义各种后缀文件,无法穷举,所以采用了排除法。针对图片资源我们有专门的优化方法,所以首先将png、webp、gif、jpg排除掉,JS&CSS资源是一般HTML加载的,在mach-o文件中TEXT字段静态字符串常量不会有体现,所以也需要排除掉,最后获取到的就是我们需要的配置文件,参考脚本如下所示:

def findProfileResources(path):
    pathDir = os.listdir(path)
    for allDir in pathDir:
        child = os.path.join('%s%s' % (path, allDir))
        if os.path.isfile(child):
            # 获取读到的文件的后缀
            end = os.path.splitext(child)[-1]
            if end != ".dylib" and end != ".car" and end != ".png" and end != ".webp" and end != ".gif" and end != ".js" and end != ".css":
                print(child + " 后缀 " + end)
        else:
            # 递归遍历子目录
            child = child + "/"
            findProfileResources(child)

3.3.2 mach-o文件获取静态字符串常量

我们加载配置文件的代码经过编译链接最后都会以字符串形式存储到mach-o文件中,具体是TEXT字段静态字符串常量__cstring中,用otool命令可以获取,参考脚本如下所示:

 lines = os.popen('/usr/bin/otool -v -s __TEXT __cstring %s' % path).readlines()

3.3.3 获取无用配置文件

前面获取的集合做diff,获取无用配置文件,确认无误后删除以减少包体积。如果你的资源名是拼接使用的,就无法命中,所以删除资源一定要逐个确认。

3.3.4 JS&CSS无用文件排查

JS&CSS文件具有特殊性,OC代码可以引用,HTML文件也可以加载引用,图片也是这种情况,但是上面提到的mach-o文件中TEXT字段只能覆盖OC文件的引用方式,而HTML加载才是主流场景,为此针对这种case百度APP采用跟无用图片检测类似的解决方案。

3.4 重复资源优化

从iPA包中获取所有资源文件,通过MD5判断资源是否重复,参考脚本如下所示:

def get_file_library(path, file_dict):
    pathDir = os.listdir(path)
    for allDir in pathDir:
        child = os.path.join('%s/%s' % (path, allDir))
        if os.path.isfile(child):
            md5 = img_to_md5(child)
            # 将md5存入字典
            key = md5
            file_dict.setdefault(key, []).append(allDir)
            continue
        get_file_library(child, file_dict)

def img_to_md5(path):
    fd = open(path, 'rb')
    fmd5 = hashlib.md5(fd.read()).hexdigest()
    fd.close()
    return fmd5

04 总结

资源优化是包体积优化的重头戏,优化的过程中影响面可控,所以落地收益比较容易,百度APP经过两个季度的优化落地12M的收益,基本解决存量资源的优化问题,同时建立资源使用规范和相应的检测流水线解决增量问题。

本文对Mach-O文件格式做了系统阐释,并且详细介绍了百度APP大资源优化、无用配置文件和重复资源优化方案,后续我们会针对其他优化详细介绍其原理与实现,敬请期待。

—— END——

参考资料:

[1]、Mach内核介绍:https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/KernelProgramming/Mach/Mach.html

[2]、《深入解析Mac OS X & iOS操作系统》

[3]、XNU源码:https://github.com/apple/darwin-xnu

[4]、Mach-O介绍:https://alexdremov.me/mystery-of-mach-o-object-file-builders/

[5]、初识Mach-O文件:https://www.jianshu.com/p/81928c705c88

推荐阅读:

代码级质量技术之基本框架介绍

基于openfaas托管脚本的实践

百度工程师移动开发避坑指南——Swift语言篇

百度工程师移动开发避坑指南——内存泄漏篇

增强型语言模型——走向通用智能的道路?

基于公共信箱的全量消息实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/614796.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

存储过程和函数的区别

目录 零、基本格式 一、返回值 二、参数传递 1、存储过程 2、函数 三、执行方式 四、事务处理 1、存储过程 2、函数 五、数据库兼容性 课上老师提出的讨论题:存储过程和函数的区别? 有同学回复:在数据库后端编程中,存储…

Python数据攻略-Pandas的数据创建与基础特性

大家好,我是Mr数据杨!今天将进入Python的Pandas数据世界,就像三国演义中的英雄们,用聪明才智塑造自己的命运。 记得三国中,周瑜曾利用兵法巧妙策划火烧赤壁,击败曹军。这就像创建一个Pandas DataFrame&…

布局量子计算工业应用!D-Wave正在“偷偷”干大事

​ (图片来源:网络) D-Wave 致力于让用户从量子计算中即时受益,而不必等到长远的未来。几十年来,这家加拿大公司一直努力将设备商业化,多家企业客户都在使用其量子计算来优化业务运营。例如,Pay…

Spark RDD容错机制

文章目录 一、RDD容错机制(一)血统方式(二)设置检查点方式 二、RDD检查点(一)RDD检查点机制(二)与RDD持久化的区别(三)RDD检查点案例演示 三、共享变量&#…

mysql数据库出现Too many connections以及磁盘满了的查看方式

Too many connections问题 这问题是数据库连接数太多了导致的, 两个排查方向 1、当用户数量大的时候 先查看最大连接数show variables like ‘%max_connections%’; 这里的最大连接数就是2000,够用了,一般500-1000就够了,内存多…

【干货分享】3D模型可视化、格式转换引擎和Parasolid如何集成?

​今天分享一个示例项目,该示例项目使用HOOPS链轮将HOOPS Exchange和Siemens Parasolid实施到HOOPS Visualize中。 HOOPS中文网http://techsoft3d.evget.com/↓ 点击下方视频查看详情 ↓ HOOPS Visualize - Exchange和Parasolid集成视频 正如您在上面的视频中看到…

小白必看:零基础入门网络安全

1、什么是网络安全? 官方的回答:指网络系统的硬件、软件及其系统中的数据受到保护,不因偶然的或者恶意的原因而遭受到破坏、更改、泄露,系统连续可靠正常地运行,网络服务不中断。 具有保密性、完整性、可用性、可控性…

chatgpt赋能python:Python如何分成两栏写入Word文档

Python如何分成两栏写入Word文档 在进行文本排版时,有些时候我们需要将文字分成两栏来排版,这样可以让文章更加美观,易读。 本文将介绍一种使用Python将文本分成两栏写入Word文档的方法。在介绍具体实现方法之前,我们先来了解一…

【SLAM】ROS平台下三种自主探索算法总结

目录 前言 一、frontier_exploration 二、explorate_lite 三、rrt_exploration 总结 前言 探索是指当机器人处于一个完全未知或部分已知环境中,通过一定的方法,在合理的时间内,尽可能多的获得周围环境的完整信息和自身的精确定位&#…

自动化测试支持

自动化测试支持 自动化测试是现代软件开发中不可或缺的一环。它可以帮助开发团队快速、精确地检测软件中的缺陷,提高软件质量和开发效率。 自动化测试可以在代码变更频繁、测试用例数庞大时,显著地减少测试时间和工作量。相对于手动测试,自动…

集权设施攻防兵法:实战攻防之堡垒机篇

一、黑客视角下的堡垒机 堡垒机是一种网络安全设备,用于保护和管理企业内部网络与外部网络之间的访问。它作为一种中间节点,提供安全的访问控制和审计功能,用于保护内部网络免受未经授权的访问和攻击。堡垒机通常被用作跳板服务器&#xff0…

计算机网络实验:RIP路由协议配置

目录 前言实验目的实验内容相关知识点实验设备实验过程总结 前言 计算机网络是指由多台计算机通过通信设备和通信线路互联起来,实现信息交换的系统。计算机网络中的路由器是一种专用的网络设备,它负责根据目的地址选择最佳的传输路径,将数据…

容器(第二篇)docker网络

Docker 网络实现原理: Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。因为…

6月6日汇报

1. 张量CP分解 三阶张量的CP分解是将其分解为三个矩阵。例如:一个三阶张量 ,则CP分解可以写为 其中, 表示向量外积, 。下图为三阶张量的CP分解: 将上面的CP分解展开,也可以写为: 假设有一个三维…

LS1028/LS1043/LS1046+FPGA+TSN多路时间敏感性网络智能工业网关方案

随着 物联网、大数据、人工智能等技术的快速发展与应用,给传统的云计算模式带来了巨大的挑战,这也催生出了计算模式的变革, 边缘计算由此诞生。 所谓边缘计算,是指在靠近物或数据源头的一侧,采用网络、计算、存储、应用…

From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了

上期主要分享了 From Java To Kotlin 1 :空安全、扩展、函数、Lambda。 这是 From Java to Kotlin 第二期。 带来 表达式思维、子类型化、类型系统、泛型。 From Java to Kotlin 关键在于 思维的转变。 表达式思维 Kotlin 中大部分语句是表达式。 表达式思维是一…

Vue.js 中的数据请求是什么?如何进行数据请求?

Vue.js 中的数据请求是什么?如何进行数据请求? Vue.js 是一款流行的前端框架,它提供了许多方便的工具和 API,用于构建交互式的用户界面。其中,数据请求是 Vue.js 中重要的一部分,它可以让我们从服务器获取…

通过python封装商品ID采集1688商品详情数据,1688商品详情接口,1688API接口

1688是阿里巴巴集团旗下的B2B电商平台,提供海量的商品和服务。通过1688的API接口可以获取到商品的详细数据,并进行采集和分析。 1688的商品详情接口包括以下信息: 商品名称商品图片商品价格商品库存商品属性商品描述商品评价商品销量商品SK…

什么蓝牙耳机通话效果好,介绍几款不错的骨传导耳机

骨传导耳机是一种新型的耳机,相比于传统的耳机,骨传导耳机听歌时不需要将耳朵堵上,不会因为长时间佩戴而对听力造成损害。它不需要入耳也能听到声音,在户外运动时能够及时听到环境音,避免安全隐患。现在在骨传导市面上…

从零开始学习JavaScript:轻松掌握编程语言的核心技能⑤

从零开始学习JavaScript:轻松掌握编程语言的核心技能⑤ 1. JavaScript 函数定义2. JavaScript 函数参数2.1 函数显式参数(Parameters)与隐式参数(Arguments)2.1.1 显式参数(Parameters)2.1.2 隐式参数(Arguments) 2.2 …