快了2.5倍,Hummer引擎到底用了什么办法?把时间拨回半年前,Pixel XL手机启动App的时候,首屏还得卡个600多毫秒。现在同样是这台手机、同样的操作,从按下按钮到看到画面只要240毫秒。Hummer是怎么做到的?这就把引擎的具体优化过程拆开来讲一讲。01第一个动作就是去掉了字体库查找的那笔“冤枉账”。以前的Flutter系统有四个线程在忙活:Platform管底层的系统调用,UI负责调度,Raster做图形光栅化,IO处理图形资源。字体渲染这块活儿就交给Raster线程了。但是在Pixel XL上,FreeType库会反复调用dlopen和dlsym去查找函数符号,就像上班打卡一样反复检索。光是这一步就要花掉90毫秒的时间,直接拖慢了首屏加载速度。后来呢,Hummer干脆把这些函数指针缓存起来,只在一开始初始化一次。以后每次调用直接“查表”就好,把这90毫秒的耗时一下子降到了10毫秒。虽然这是个小众机型的问题,但是解决的方法简单实用,其他团队也可以照做。02第二个招数是救回那些被系统扔掉的首帧。引擎拿到Framework发过来的一帧数据后,并不会马上显示到屏幕上,还得经过两步检查:布局是否合法、surface是否就位。在Android系统里,window的大小是系统把FlutterView布置好之后才会返回的;而surface更是要等到遍历View层级的时候才准备好。所以经常会发生这样的情况:引擎在第二步就把收到的“早产”首帧给丢掉了。现在Hummer想了个办法:把第一步缓存的数据先放在一边不管。等系统那边返回来有效的布局和surface之后,再把这帧补上去画出来。实际测下来,Pixel XL的首帧显示时间提前了大约50毫秒。不过得注意一下:如果是宿主App自己卡死了主线程,这时候首帧还是会慢下来;这时候得先把宿主App给“瘦身”一下。03第三个大招是把Rasterizer的装配过程改造成并行的了。按照原来的标准流程:Platform线程先创建GrContext→UI线程收到通知→Raster线程接收surface,一步接一步地走。后来发现运行的时候问题来了:UI线程忙着生成Element和Render树的时候正好把Raster线程给堵住了。原本前面抢到的那点时间全都浪费了。于是Hummer把“通知”和“接收”这两个指令拆开成两条路来走:UI线程继续造树的时候,Raster线程直接把surface塞给Rasterizer。这样一来Pixel XL的主线程耗时减少了30毫秒左右。业务越复杂的话,并行带来的好处就越明显。现在这个改动已经同步回官方仓库里了。04最后再总结一下经验:把优化做成肌肉记忆才是硬道理。给大家留下四条口诀:1.熟用工具:Trace就像照妖镜一样。2.提前预热:用空间换时间把慢活儿放到后台去做。3.异步并行:Flutter有四条腿走路却只用两条跑是不行的。4.空间换时间:多占点内存可以把延迟压下去。照这四步走下去,首屏时间就能切成原来的三分之一——用户看着开心,手机也不用太费劲了。