干货 | Trip.com APP 启动优化实践

SpringBoot+Vue+MySQL 高校学科竞赛平台 完整可运行版(源码+文档) 本项目基于SpringBoot+Vue+MySQL构建高校学科竞赛管理平台,覆盖学生、教师、领队教师、管理员四大角色,实现竞赛发布、报名、刷题、评分、公示等全流程闭环管理。系统架构清晰,技术主流,配套完整源码、数据库脚本、万字论文、开题报告、答辩PPT及部署文档,支持一键运行,适用于毕业设计、课程设计及院系二次开发,真正实现“开箱即用”。 阅读详情

作者简介

 

Shanks,携程移动开发专家,关注移动端基础技术。

引言

启动是用户对App的第一印象,对于用户体验尤为重要,所以我们花了很多时间在启动时间的优化上。本文将分享Trip.com App的启动优化实践,从分析App启动的过程开始,在了解启动流程的基础上制定大的优化原则和小的具体方案,希望能对大家有所帮助。


一、App启动的流程分析

想做启动优化,首先要了解清楚启动的各个流程,然后才能对各个环节去做针对性措施。

借用WWDC对启动阶段的定义图:

1.1 System Interface

  • 加载App可执行文件

  • Load dylibs

加载动态链接器dyld ,dyld会递归加载App依赖的动态库,然后执行符号绑定Rebase, Bind。一般应用会加载 100 到 400 个 dylib 文件,幸运是大部分是系统库,且系统会在操作系统启动时计算和缓存系统动态库。

Apple为了解决安全问题,引入ASLR和Code Sign,如果不作符号修正,程序将没法正常运行,所以会有Rebase和Bind过程。

  • Rebase

在镜像内部调整指针的指向,其实就是将内部指针都加上偏移量(Slide=实际新地址-旧地址)

  • Bind

修正指外部的指针,比如上图中malloc,这个符号不存在于我们App的Mach-O中,需要从外部的镜像中获取,这时候就需要Bind操作把这个关联起来。

  • libSystem init

调用系统的一些初始化方法,这部分一般时间比较固定,可以不用太关注。


1.2 Runtime Init

  • Objc和Swift的初始化

通过_dyld_objc_notify_register注册回调,在image加载完时初始化语言相关。

  • 加载category

在上面语言初始化完之后,会加载所有category,处理category的所有方法,协议和属性等。

  • 调用所有+load

也是通过向 dyld 注册回调,在image加载完时,通过load_images 触发,处理该image相关的所有+load方法,按照继承层级依次调用:父类+load→子类+load→category +load,注意category的+load不会覆盖原类。

  • 调用C++的构造函数属性函数 attribute((constructor)) 


1.3 UIKit Init

  • 实例化 UIApplication 和 UIApplicationDelegate

  • 开始事件处理和系统集成


1.4 Application Init

这部分是我们熟悉的 UIApplicationDelegate 的几个生命周期调用:

  • application:willFinishLaunchingWithOptions:

  • application:didFinishLaunchingWithOptions:

  • applicationDidBecomeActive:

  • scene:willConnectToSession:options:

  • sceneWillEnterForeground:

  • sceneDidBecomeActive:


1.5 Initial Frame Render

这里是App渲染第一帧,主要做了创建、布局和绘制视图的工作,并把准备好的第一帧提交给渲染层渲染。这里面布局计算,图片解码,图层树的递归commit到Render Server等都是可能影响耗时的点,所以要特别注意。


1.6 Extended

这里按照苹果的定义,是异步获取数据展示界面的逻辑。比如我们首页要从网络请求数据然后展示最新数据在页面上。


二、针对启动的各个流程我们能做什么


2.1 总体原则

不管哪个流程,我们都想尽量遵循下面两个原则:

删

删的原则是指,对App启动和运行不是必须的任务,或者跟首页渲染第一帧无关的任务,都从启动流程中删除。对于删除的任务,可以进行懒加载的形式,需要时再调用;也可以换到其他的时机去触发,比如首页渲染完之后。

压

压的原则是指,对App启动和运行必须的任务,或者直接影响首页渲染第一帧的任务,都尽可能压缩其运行时间。至于做法,可以是优化方法内的实现,使其运行更快;也可以将方法执行的线程切换到子线程,以并发的形式降低其对整个启动过程的影响。


2.2 具体方案


2.2.1 减少动态库

动态库的加载在启动阶段是必须的,所以我们要尽量减少非必要的动态库。对此我们做了以下几点:

1)梳理所有动态库,将用不到的或者可以简单替代的动态库删除

可以通过otool -L xxx.app/xxx 或者打开打包后的产物,从xxx.app/Frameworks路径中找到所有动态库,逐个筛选,将其中可以废弃和替代的动态库删除。

2)通过推进社区(第三方SDK)将现有动态库转成静态库

因为依赖了第三方SDK,我们是不包含源码的,所以这部分需要推进社区提供静态库的版本,或者通过cocoapods等工具打包SDK的静态库版本。

3)将我们自己的SDK编译成静态库

对于我们自己的SDK,因为有源码,所以直接修改MACH_O_TYPE 为Static Library 重新打包即可。

4)App最低支持系统版本升级到12.2

因为iOS在12.2版本及以上才内置了Swift的支持,所以在此之前Swift的动态库都是随着 App下发的,也在xxx.app/Frameworks 里。

当然,这个决策是会直接应用到用户和订单的,所以是要有数据支持的,我们是根据用户占比到达某个阈值才支持12.2的。如果允许,甚至可以升级到iOS 13,因为iOS13以上dlyd3做了很多加载和缓存的优化。


2.2.2 删除无用代码

如果符号越多,很显然Rebase和Bind的处理时间就会越长,Objc的初始化也受影响,所以我们需要尽可能减少代码:

1)通过逆向二进制或者生成linkmap,解析所有方法(TEXT.text)和引用到的方法(__DATA _objcselrefs),找出无用方法删除

2)解析所有类(DATA.objcclasslist)和引用到的类(DATA.objcclassrefs),找出无用的类删除

3)使用第三方工具或者clang扫描重复代码,精简去重

4)使用LLVM_LTO和GCC_OPTIMIZATION_LEVEL等其他编译选项优化二进制大小


2.2.3 合并category

合并category,可以减少category加载时的耗时。不过这部分收益不大,并且也会影响编程习惯,所以我们并没有投入很多时间,不再赘述。


2.2.4 删除+load

以前会有很多代码为了省事,加到了+load中,这部分很显然占用启动时间,所以尽量要把这其中的代码转移,可以放到initialize中懒加载,或者放到启动任务中并发执行,尽量减少这部分的影响。

Xcode调试时,可以通过正则添加所有+load方法的断点br s -r "\+\[.+ load\]$" ,然后使用br list打印出所有+load列表,这样方便我们定位所有+load。


2.2.5 UIApplication子类优化

为了减少UIKit Init的时间,可以对UIApplication的子类初始化工作优化。我们这部分不存在,所以没有做什么工作。


2.2.6 启动任务并发

想象一下,如果application:didFinishLaunchingWithOptions:里面执行的所有启动任务不作任何处理,那么代码框架将会很乱,你的优化也只能单点单点去做。

所以我们将application:didFinishLaunchingWithOptions:阶段所有方法任务化,一个任务做一种类型的事。任务拆分好之后,就可以根据任务之间的相关性,选择哪些任务是可以并发执行,哪些任务是必须有依赖关系前后执行。

以前:

现在:

当然,任务的拆分颗粒度也很重要,拆分太粗的话,很难达到最优的组合,可能一个任务里的方法之间仍然有并行的空间。拆分太细的话,也有可能导致同一时间并发数太多,造成额外的线程切换开销。


2.2.7 I/O处理

尤其要注意启动阶段的I/O,一般出现于读取磁盘中的文件,比如配置文件等。

使用 Instrument→App Launch 去查看启动过程就会发现,如果主线程执行出现很多灰色的块,那就是I/O,找到这些I/O产生的方法,尽量在子线程并发执行,避免阻塞主线程。


2.2.8 首页数据的预加载和懒加载

首页上有很多数据要加载,比如图片、上次缓存在本地的数据等等,这些数据的加载如果在写代码时不作特殊处理,那会在主线程执行,不知不觉就会有很多耗时。

1)预加载

对首页渲染必须的数据,比如一个icon,或者一个翻译的数据,我们通过在启动任务(之前提到的拆分的并发任务)中新增加一个预加载启动任务,专门负责在application:didFinishLaunchingWithOptions: 的过程中并发执行数据的获取。因为获取数据大多比较耗时,所以放在子线程充分利用启动阶段的空闲。同时这类任务大多数是I/O操作,并不会占用太多CPU资源。

更进一步,其实可以对首页用到的资源在运行时作个标记,记录到磁盘,下次启动的时候读取这个记录,对用到的资源进行提前预加载,这样避免hard code很多资源名在代码中。

2)懒加载

首页的数据往往很多,但并不是一开始要全部用到。可以对数据作区分,和第一屏展示无关的,使用懒加载,真正用到的时候再去加载。


2.2.9 二进制重排

1)page fault 

由于虚拟内存的机制,应用启动时不会把所有数据加载到内存,而是以页为单位逐步从磁盘中加载,内存中的虚拟地址和磁盘中的物理地址有个映射关系。当程序执行时,如果发现要访问的东西不在内存里,就会触发一次page fault ,去磁盘中加载新的一页。

启动阶段有很多方法要调用,而这些方法在Mach-O中的位置又是在编译时确认的。如果有10个方法刚好在不同页,可能就要产生10次page fault 。

二进制重排要做的就是将启动阶段要用到的方法,在编译时提前确定,通过.order文件告诉编译器,这样这些方法会排布在Mach-O的最前面,之前的10次page fault 很可能就变成一两次page fault。

通过在Other C Flags中添加-fsanitize-coverage=func,trace-pc-guard 再通过__sanitizer_cov_trace_pc_guard记录启动阶段所有方法的调用,再将这些写入到.order文件中,在Xcode的ORDER_FILE 设置中配置即可生效。

通过测试,我们的二进制重排大概优化100-200ms。


2.2.10 其他通用手段

针对启动任务和首页渲染阶段,通用的手段是通过instrument,profile出耗时长的任务,对任务针对性地做方法优化。如果有的方法是第三方库的,那就需要推进社区去更新。我们在做的过程中给Firebase和Google的一些SDK提了很多issue,对方开发人员配合很积极,对我们帮助很大。


三、成果如何

通过长期的优化,以上手段全部用完之后,我们的启动时间从原来的2秒,优化到1秒以内。


总结

在优化启动时间的过程中,我们的收获不仅是对启动时间的优化,也对系统的启动机制有了更深的了解,同时优化了我们自己的代码,使其变得更加更加健壮和高性能。

团队招聘信息

我们是携程国际业务研发团队,致力于国际业务的探索和深耕,在技术上追求极致的用户体验。

如果你向往在一个国际化团队中学习成长,期待技术上有突飞猛进的提升,欢迎加入我们。目前我们客户端/前端/后台/数据/测试开发均有开放职位。
 
简历投递邮箱:tech@trip.com,邮件标题:【姓名】-【携程国际业务】- 【职位】

【推荐阅读】

 “携程技术”公众号

  分享,交流,成长

硅基半导体量子点:单电子自旋量子比特技术手册 现在我们已经了解了从硅片到容错计算的全部环节,让我们用一张总览图把碎片拼成完整拼图。fill:#333;important;important;fill:none;color:#333;color:#333;important;fill:none;fill:#333;height:1em;important;important;important;color:#000!important;important;important;important;important;color:#000! 阅读详情

相关推荐

Zynq 【SDK裸机开发之PS】——DDR的内存分配使用详解

Zynq如果跑操作系统的话,内存管理的工作都会交给系统来操作,但是如果是裸机的话,则需要自己对DDR3的内存进行管理。

shizhibuyi1234的博客 7377

携程移动端优化

携程移动端优化

qin20082012的专栏 5210

MQTT(轻量级消息中间件)基本使用指南

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是一种轻量级的发布 / 订阅模式(Pub/Sub)消息传输协议,专为低带宽、不稳定网络环境设计,广泛应用于物联网(IoT)、移动设备、传感器网络等场景(如空气净化器、智能家居设备的远程控制)。Eclipse Mosquitto 是一款轻量级、开源的 MQTT 消息服务器(Broker),由 Eclipse 基金会维护,专为低资源消耗和易用性设计。

墨鸦的博客 1万+

携程移动App架构优化之旅

本文为携程移动开发总监陈浩然在2015年10月份的ArchSummit全球架构师峰会上的演讲总结。由于面向受众为架构师,因此不会涉及到很多技术细节。通过本文,你可以了解携程通过哪些手段来优化它的App架构的。     『携程旅行App』作为携程超级App产品,是公司全品类旅行产品的核心售卖入口,过去两年为了更好支撑无线业务的快速发展,携程移动App在产品和技术架构方面也做了大量的优化。

AlexAoMin的博客 3791

阿里巴巴达摩院人工智能训练师(高级)

阿里巴巴达摩院人工智能训练师(高级)

huang0612123的博客 1万+

振动分析中相位的确定

更是定位不平衡量角度的关键。是确定振动信号与转子旋转位置之间时间关系的核心步骤,而。以内,以确保校正配重安装角度的有效性。在实际动平衡中,相位角的误差需控制在。在振动分析和动平衡中,

轩辕霸天L 2735

携程App的网络性能优化实践

编者按:在4月23日~25日举行的QCon全球软件开发大会(北京站)上,携程无线开发总监陈浩然分享了《移动开发网络性能优化实践》,总结了携程在App网络性能优化方面的一些实践经验。在2014年接手携程无线App的框架和基础研发工作之后,陈浩然面对的首要工作就是App客户端性能优化,尤其是网络服务性能,这是所有App优化工作的重中之重。以下为正文。 首先介绍一下携程App的网络服务架构。由于携

Secret Island 1895

Mac上IDEA的PlantUML插件报错‘找不到Graphviz’?手把手教你用Homebrew搞定(附阿里云镜像避坑)

本文详细解决了Mac上IDEA的PlantUML插件报错‘找不到Graphviz’的问题,通过Homebrew安装Graphviz并配置阿里云镜像源,特别针对Apple Silicon芯片用户提供了优化方案。文章还涵盖了IDEA中PlantUML插件的正确配置和常见问题排查,帮助开发者高效使用PlantUML进行UML图表设计。

weixin_42619742的博客 468

干货 | 降低20%链路耗时,Trip.com APP QUIC应用和优化实践

作者简介竞哲,携程资深后端开发工程师,关注网络协议、RPC、消息队列以及云原生等领域。一、背景QUIC 全称 quick udp internet connection,即“快速 UDP 互联网连接”(和英文 quick 谐音,简称“快”),是由 google 提出的使用 udp 进行多路并发传输的协议,是HTTP3的标准传输层协议。近几年随着QUIC协议在IETF的标...

携程技术 3884

IEEE S&P 2026 大模型安全论文整理

本文梳理了 IEEE S&P 2026(第47届 IEEE 安全与隐私研讨会)中与 LLM Safety 相关的论文,涵盖大模型越狱攻击、防御与对齐、隐私保护、RAG 安全、多模态大模型安全、智能体安全、图-LLM 安全、工具链安全及生成式 AI 安全与隐私等方向。

CSPhD-winston的博客 1334

嵌入式工程师成长之路(0)----认识元件

在电子设计与维修中,认识元件是成功的关键!面对琳琅满目的电阻、电容、电感与二极管,样子形式千奇百怪,你是否也曾陷入困难? 本文基于行业权威分类标准,结合十年工程师实战经验,深度解析4大类、30+子类电子元件的核心特性、应用场景与避坑指南,助你从“小白”秒变“电子高手”!

云轩阁 11万+

干货 | Trip.com 智能自动化探索测试

作者简介祥星,携程Android开发工程师,对Android自动化测试有深入的研究。一、简介快速的业务迭代要求快速的App发版节奏,随之而来的是质量保障压力的增大。而增大自动化程度,提...

携程技术 1391

搞定最强大模型Gemini学生订阅,白嫖一年,包教包会

白嫖现在最强大模型

BUG猿的博客 2万+

Oracle - 序列的高级用法,缓存与步长优化

Oracle序列高级用法:缓存与步长优化 摘要 Oracle序列是生成高性能主键的核心机制,但基础用法难以满足高并发和分布式场景需求。本文深入解析序列的缓存机制本质,揭示CACHE参数如何通过预分配内存区间减少I/O开销,同时带来ID间隙问题。重点探讨INCREMENT BY步长在分布式ID分片中的应用,提供Java实现示例。同时对比ORDER/NOORDER在集群环境的差异,分析事务回滚对序列的影响。通过Mermaid流程图、性能测试数据和实战代码,展示如何根据业务场景平衡连续性、性能和可靠性需求,实现O

千淘万漉虽辛苦,吹尽狂沙始到金 1万+

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

【保姆级喂饭教程】WinGet介绍、安装、命令及UniGetUI介绍、安装、使用教程

之前看文章看到过使用winget安装软件,一直没有仔细了解过,前段时间看到有教程说可以用winget无魔法安装Claude code,不清楚怎么实现的,正好研究一下。WinGet文档官网WinGet 全称Windows Package Manager(Windows 包管理器),是微软官方推出的 Windows 系统原生命令行软件管理工具。你可以把它理解成「命令行版的微软应用商店。

AlienProgrammer的博客 661

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

InnoDB 内存结构总览:Change Buffer、AHI 与 Log Buffer

本文系统梳理InnoDB内存结构中的ChangeBuffer、Adaptive Hash Index(AHI)与LogBuffer。ChangeBuffer缓存二级索引页不在内存时的变更,减少随机IO,仅在页加载时合并;聚簇索引修改立即执行,因数据本身不可延后。AHI基于高频等值查询自动构建哈希索引,加速定位,仅支持等值操作。LogBuffer为redo log内存缓冲,配合磁盘日志实现崩溃恢复。三者均服务于性能优化,其中ChangeBuffer与AHI共享BufferPool空间,而LogBuffer独立

bksczm的博客 284

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

V8 机制、手写核心函数与 TypeScript 类型体操

或固定初始化(如数组先定长、对象先给全字段)保持类型与形状稳定;这些都比"换框架"更基础。只能处理纯数据(丢函数、Date、RegExp、循环引用报错)。生产级深拷贝要处理:原始值短路、循环引用、V8(Chrome / Node 的 JS 引擎)不是"解释一行跑一行"那么简单,它用。图:V8 执行流程——解释执行 + 热点优化,优化失败会 Deopt 回退。JS 内存自动管理,但"自动"不等于"不会漏"。是"固定间隔最多执行一次"。手写 Promise 是"懂异步"的试金石。是"停下后才执行最后一次",

qq_38468358的博客 346

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

要努力去发光,而不是被照亮~ 126

Spring Boot 数据源与 MyBatis(-Plus) 自定义配置详解

本文详解 Spring Boot 中单数据源环境下 HikariCP 与 MyBatis-Plus 的自定义配置。通过手动声明 SqlSessionFactory,实现下划线转驼峰、日志输出、一级缓存策略等精细化控制。结合 HikariCP 核心参数调优(如最大连接数、连接生命周期),确保高性能与稳定性。适用于仅需访问单一数据库的场景,避免多数据源复杂性,兼顾性能与可维护性。

BADAO_LIUMANG_QIZHI的博客 196

LeetCode Hot 100 二叉树专题(二)3 题(Java)

本篇文章共 3 题:543. 二叉树的直径、102. 二叉树的层序遍历、108. 将有序数组转换为二叉搜索树。 每道题都统一按「① 题目分析 & 解题思路 → ② 常见解法(附代码)及讲解 → ③ 知识点总结」的顺序组织;代码使用 Java 编写,节点定义沿用 LeetCode 默认的 TreeNode。

2401_88612756的博客 365

【从零理解 Spring AI · 12】MCP 的两种角色:做客户端还是做服务端

上一篇讲了 MCP 的概念:标准协议、三大角色、M+N 替代 M×N。

nice_lcj520的博客 490

JDK 下载安装:装完 Java 提示「不是内部或外部命令」?环境变量没配

JDK 是写 Java 程序必须装的东西。它包含运行 Java 的虚拟机(JRE)和编译 Java 的编译器(javac)——装了它,能跑、.java文件能编译成可执行程序。免费,学习和小规模生产都不花钱。

科小怪的博客 631

java框架大全

java框架大全

qq_39326137的博客 183

Java 多态 学习笔记

多态是面向对象编程的重要特性,指同一操作作用于不同对象时产生不同行为。其核心在于“父类引用指向子类对象”,结合方法重写实现运行时动态绑定。多态使代码更灵活、可扩展,如用Person p = new Student();统一处理各类人员注册,无需修改调用逻辑。但多态无法直接调用子类特有方法,需通过instanceof判断并强制转换。编译时看左边(类型),运行时看右边(实际对象),实现解耦与维护便利。

AA_AA114514的博客 359

SecureRandom做随机源,这个Java随机字符串工具类有3个方法

本文分享一款JDK17环境下的Java随机字符串工具类,内置3个开箱即用的方法,全部采用SecureRandom加密安全随机源,不存在弱随机、可预测、取模偏差等问题。工具类支持默认62字符集随机串、纯数字随机串、自定义字符集随机串三种生成模式,支持Unicode码点级别的字符集处理,兼容Emoji等增补字符,边界处理严格,异常信息明确,适合验证码、邀请码、盐值、随机口令、短链码等场景,源码可直接复制使用。

Java后端 | AI智能体应用实战派 | 电脑技能行家 298

IDEA 简介:从零认识 IntelliJ IDEA

IDEA 全称,是由 JetBrains 公司开发的一款主流 Java 集成开发环境(IDE)。它集代码编辑、编译、调试、测试、版本控制、构建管理等功能于一体,被大量 Java 开发者视为日常开发的首选工具。所谓 IDE(Integrated Development Environment,集成开发环境),就是把写代码所需的各类工具整合到同一个界面中。相比只用文本编辑器加命令行编译,IDE 能显著减少重复操作,让开发者把精力集中在业务逻辑上。

ztI10的博客 131
上一篇: 干货 | 携程 Web CI/CD 实践
下一篇: 干货 | 10分钟给上万客服排好班,携程大规模客服排班算法实践
携程技术
博客等级 码龄10年 3716粉丝 324原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值