简介: 满满干货
作者:海潴,锦逸
随着闲鱼 App 端更多新性能、新技术的退出,利用冷启动速度越来越慢,这也意味着用户看到无效内容的工夫被拉长,对用户体验有着很大的挫伤。目前,在内部测试版本中,咱们曾经将 Android 的冷启动工夫从原来的 10s 升高到了 5s 内。
闲鱼是如何疾速将启动工夫缩小一半的呢?分为 建设规范
、 剖析现状
、 抓大放小
三个步骤。
建设规范
做性能优化不是探讨哲学问题,建设正当的数据衡量标准十分重要。只管曾经有了很多对于如何卡口要害函数、如何判断页面第一帧渲染实现的探讨,但从代码层面进行判断始终与用户的感知无奈 100% 得匹配。如何迅速建设起启动工夫的规范?咱们借鉴了手淘的形式和规范,利用外部的魔镜平台,应用视频关键帧的形式记录下 App 图标被点下到首页第一屏渲染实现作为一整个利用冷启动的过程。这与用户看到的启动过程吻合。
对于设施的抉择上,咱们应用 y67 这样一台当初看起来绝对性能较差的机型作为优化的指标机型。低端机存在 CPU 能力弱,IO 速度慢等问题,而慢代码与 IO 恰好是拖慢利用启动最大的起因。定位优化的指标机型能够更加疾速得解决 common 类型的启动问题。
闲鱼现状
咱们先应用日志打点的形式来统计启动过程中耗时的大头,以便能够疾速得将启动性能进步下来。能够看到图中,进入首页渲染前,common
、interactive
两局部占去了大部分的工夫,这是启动器在执行启动工作。而在进入首页后,页面的申请与 view 的排版占用了大部分的工夫。
基于下面的剖析,第一阶段咱们将”启动工作治理“和”首页渲染减速“作为疾速晋升启动工夫的重点来优化。
启动工作优化
闲鱼的 Android 端在 16 年的时候上线了一个基于 DAG(有向无环图)的启动器,它将启动工作编排为一个 DAG,并应用多核多线程并发的执行工作。下面说到的 common
与interactive
属于启动器执行工作的两个阶段,它们都会让主线程期待阶段中的工作全副执行完,所以这两个阶段的工作,咱们叫它 阻塞型工作
。
目前为止,整个闲鱼 Android 在启动阶段有 77 个工作须要执行,其中阻塞型工作有 61 个,y67 上的总执行耗时在 8s 以上,并发后须要将近 2.5s 的工夫。
对于启动阶段阻塞型工作,最快的优化形式有三点:
- 局部工作提早执行
- 升高工作自身的耗时
- 拆分大工作
工作拆分与提早执行
缩小阻塞型工作的数量,是减速启动最间接的伎俩。这里须要依据工作的 DAG 进行依赖剖析,可能无伤被提早执行的工作最显著的特色就是”没有其余工作依赖于它“。如果工作之间有依赖,则须要依据后续首页对于模块的应用状况来决定是否将整个依赖链上的工作全副提早。
闲鱼的首页金刚位大部分是 weex、web 和小程序的入口,另外首页也会用到端智能相干的性能。然而这四个 sdk 的初始化,广泛都在 300-500ms 左右,属于比拟”硬核“工作。在将这四个工作挪动到异步非阻塞阶段后,整个启动升高了 500ms(当然要设置最高优先级以保障用户尽量少的等待时间)。
非阻塞工作的触发机会
工作启动的机会就像跟女生表白一样,不是你想启动,启动就启动的。谬误的机会大概率造成灾难性得结果。
在咱们将几个大工作挪动到非阻塞阶段后发现,如果阶段启动的时候首页还没开始渲染或者没有渲染实现,整个首页的渲染会变得十分迟缓,图片的加载也随之变慢。总之就是谁碰到谁晦气。实测中,非阻塞阶段启动的时机会对首页的渲染产生将近 1s 左右的稳定,使得启动工夫一直得在 危险的边缘
疯狂试探。
这是因为非阻塞阶段会在进入首页后的第一个 queueIdle
回调之后触发。而它的执行占用了多过的系统资源,造成 CPU 占用、网络申请排队、IO 密集等问题。最终导致主线程、渲染变慢的状况。
那么什么工夫才是启动非阻塞工作的适合机会呢?既然咱们抉择首页渲染为最高优先级,非阻塞工作的启动就必须排在前面。于是一咬牙一跺脚——”砍“!
咱们让首页在确认 view 都上屏后,发信号给启动器。启动器这个时候才开始注册 queueIdle
回调,并启动一个提早 6s 的 runnable 作为”备份“,避免 message queue 过忙长时间无奈触发非阻塞阶段的工作。
但这里有个矛盾点,首页上几大金刚位都是通往 weex、web 或者小程序的,如果用户点击这些页面比非阻塞阶段的触发更早,该怎么办呢?当然是原谅 触发 它啊!
这里咱们采纳的形式是,当这些性能被触发的时候,须要先去 check 须要的模块是否曾经初始化实现。如果没有的话,check 非阻塞阶段是否曾经启动。如果已启动,就进入期待,否则强制触发(这个时候首页必然曾经渲染实现了),并期待所须要的工作执行实现。
工作耗时治理
要疾速治理,须要利用一些成熟的工具。能够先对工作中的每一行代码进行工夫统计,筛选出执行工夫较长的调用后,应用零碎提供的 method trace
进行更细粒度的剖析。
既然是要快,那么肯定是找通用类型的问题下手:
- 对于 IO 进去的值,尽量做内存缓存,防止屡次 IO
- 防止产生大的 SharedPreference 文件,尽可能得将对 commit 的调用换成 apply
- 留神一些异步接口回调的线程,如果是主线程,也须要保障回调后的代码疾速执行完
首页启动优化
优化前,闲鱼的首页须要先进行三个排队的网络申请,弹出广告页,接着进行动静模板的渲染与数据绑定,总耗费工夫在 3.5s 以上,这外面还不包含图片上屏的工夫。
闲鱼首页局部的启动优化,次要也从三个方面来做:
- 广告页
- 数据事后加载
- View 的预创立
广告页优化
闲鱼之前的广告页的流程如下图:
先拉起启动页,而后启动页拉起首页,首页再拉起广告页,广告页起来先展现默认图,而后同时去做是否有广告的判断,而后再去做广告的展现,这个过程如果没有广告,也会让默认的广告页展现 3 秒钟再敞开。
这个过程显然是不合理的,广告有本人的疲劳期,那么在没有广告的时候,拉起广告页就是一个节约。其次广告页作为一个 Activity 拉起,须要经验一些 IPC 的调用,整个操作也是比拟重的。
基于这两点,咱们在广告页这块,先在初始化的时候就做提前的资源拉取和预判,这样如果的确没有广告资源,那么广告页间接不做启动,节俭启动资源。其次,咱们将广告页由一个 activity,革新成一个全屏展现的 Dialog,进一步来节俭广告拉起时资源耗费,让首页其余内容的加载有更短缺的系统资源。
数据事后加载
在性能优化中,空间换工夫与提前预加载就像广为人知的”两头加一层“一样好用。
闲鱼首页必须的两个接口,冷启和热启接口耗时在 1 秒左右,而他们是在首页第一帧回调回来之后的机会才开始申请的。这里齐全能够把申请的机会提前到初始化的过程中并行去做,从而为闲鱼启动 -1s。
于是咱们设计了针对这种状况下的预取模块,在初始化的时候,就去做首页数据的预加载,整体的模块的时序如下:
这一步做完之后,本地机器测试后果大概节约了 950ms 的启动工夫。
View 预创立
在解决完数据的问题之后,咱们通过魔镜平台,会发现在 y67 上,首页展现之后,有大量的白屏的工夫,view 的创立和渲染,在这里耗费了大量资源,并占用了很长的工夫(这里每一帧是 100ms),均匀大略在 1400ms
于是咱们自然而然的想到了在初始化的过程中去提前创立 view,然而如果是在初始化过程中的主线程去创立 view,那么势必会跟启动页和广告页等 ui 元素竞争主线程的应用,根本等于白干。
于是这里咱们采纳在子线程事后创立 view 并执行 mesure 与 layout 操作。期待首页渲染时,应用对应的 id 进行取出和应用。做完之后,会发现 view 的上屏工夫,在 y67 上缩短到 600ms,缩小了一倍的的工夫:
总结与下一步优化
通过下面的形式,整个启动阶段的工夫从 2.5s 升高到了 1.3s,升高了将近一倍的工夫。另外启动工作所耗费的总工夫从 8s 升高到了 3s。首页的渲染简直达到了秒出。整体启动工夫升高到了 4.5s 左右。
这个阶段次要是对启动过程中的工作与首页代码自身的优化。下一阶段,咱们会对整个启动过程中的运行环境进行优化:
- 对启动时候的资源耗费进行整顿,缩小不必要的网络申请与 IO 以及线程切换。
- 对启动器中的线程负载进行优化,目前启动的任务分配形式间隔实践上的最优值 (平均值) 大概还有 50% 的空间。
- 应用 dex-relayout、PGO 减速启动