随着闲鱼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预创建
广告页优化
闲鱼之前的广告页的流程如下图:
