}
}

2.智能预取-提前化网络请求

经过直出的改造之后,为了更快的渲染首屏,减少过程中涉及到的网络请求耗时,我们可以按照一定的策略和时机,提前从CDN中请求部分落地页html,缓存到本地,这样当用户点击查看新闻时,只需从缓存中加载即可

手百预取服务架构图

目前手百预取服务支撑着图文、图集、视频、广告等多个业务方,根据业务场景的不同,触发时机可以自定义,也可以遵循我们默认的刷新、滑停、点击等时机,此外,我们会对预取内容进行优先级排序(根据资源类型、触发时机),会动态的根据当前手机状态信息进行并发控制和流量控制,在一些降级场景中,server还可以通过云控的方式来控制是否预取以及预取的数量

3.通用拦截-缓存共享、请求并行

在落地页中,除了文本外,图片也是重要的组成部分。直出解决了文字展现的速度问题,但图片的加载渲染速度仍不理想,尤其是首屏中带有图片的文章,其首图的渲染速度才是真正的首屏时间点 传统Hybrid方案,前端页面通过端能力调用NA图片下载能力来缓存和渲染图片,虽然实现了客户端和前端图片缓存的共享,但由于JS执行时机较晚,且多次端能力调用存在效率问题,导致图片渲染延后

初步改进方案:为了提升图片加载速度,减少JS调用耗时,改为纯H5请求图片,速度虽然有所提升,但是客户端和前端缓存无法共享,当点击图片调起NA图片查看器时,无法做到沉浸式效果,且仍需重复下载一次图片,造成流量浪费 终极方案:借由内核的shouldInterceptRequest回调,拦截落地页图片请求,由客户端调用NA图片下载框架进行下载,并以管道方式填充到内核的WebResourceResponse中

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

此方案在满足图片渲染速度的同时,解耦了客户端和前端代码,客户端充当server角色,对图片进行请求和缓存控制,保证前端和客户端可以共用图片缓存,改造后的方案,非首图展现流程,页面不卡顿,首屏80分位值缩短80ms~150ms

效果如下:

优化前Hybrid方案

优化后通用拦截方案

4.整体方案流程

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(二)新的优化尝试

1.WebView预创建

为了减少WebView的性能损耗,我们可以在合适时机提前创建好WebView,并存入缓存池,当页面需要显示内容时,直接从缓存池获取创建好的WebView,根据性能数据显示,WebView预创建可以减少首屏渲染时间200ms+

=

具体以Feed落地页为例,当用户进入手百并触发Feed吸顶操作后,我们会创建第一个WebView,当用户进入落地页后,会从缓存池中取出来渲染H5页面,为了不影响页面的加载速度,同时保证下次进入落地页缓存池中仍然有可用的WebView组件,我们会在每次页面加载完成(pageFinish)或者back退出落地页的时机,去触发预创建WebView的逻辑

由于WebView的初始化需要和context进行绑定,若想实现预创建的逻辑,需要保证context的一致性,常规做法我们考虑可以用fragment来实现承载H5页面的容器,这样context可以用外层的activity实例,但Fragment本身的切换流畅度存在一定问题,并且这样做限定了WebView预创建适用的场景。为此,我们找到了一种更加完美的替代方案,即MutableContextWrapper

Special version of ContextWrapper that allows the base context to be modified after it is initially set. Change the base context for this ContextWrapper. All calls will then be delegated to the base context. Unlike ContextWrapper, the base context can be changed even after one is already set. 简单来说,就是一种新的context包装类,允许外部修改它的baseContext,并且所有ContextWrapper调用的方法都会代理到baseContext来执行

下面是截取的一段预创建WebView的代码

/**

  • 创建WebView实例
  • 用了applicationContext
    /
    @DebugTrace
    public void prepareNewWebView() {
    if (mCachedWebViewStack.size() < CACHED_WEBVIEW_MAX_NUM) {
    mCachedWebViewStack.push(new WebView(new MutableContextWrapper(getAppContext())));
    }
    }
    /
    *
  • 从缓存池中获取合适的WebView
  • @param context activity context
  • @return WebView
    */
    private WebView acquireWebViewInternal(Context context) {
    // 为空,直接返回新实例
    if (mCachedWebViewStack == null || mCachedWebViewStack.isEmpty()) {
    return new WebView(context);
    }
    WebView webView = mCachedWebViewStack.pop();
    // webView不为空,则开始使用预创建的WebView,并且替换Context
    MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
    contextWrapper.setBaseContext(context);
    return webView;
    }
2.NA组件懒加载

a. WebView初始化完成,立刻loadUrl,无需等待框架onCreate或者OnResume结束 b. WebView初始完成后到页面首屏绘制完成之间,尽量减少UI线程的其他操作,繁忙的UI线程会拖慢WebView.loadUrl的速度

具体到Feed落地页场景,由于我们的落地页包含两部分,WebView+NA评论组件,正常流程会在WebView初始化结束后,开始评论组件的初始化及评论数据的获取。由于此时评论的初始化仍处在onCreate的UI消息处理中,会严重延迟内核加载主文档的逻辑。考虑到用户进入落地页的时候,评论组件对用户来说并不可见,所以将评论组件的初始化延迟到页面的pageFinish时机或者firstScreenPaintFinished;80分位性能提升60ms~100ms

3.内核优化

a. 内核渲染优化: 内核中主要分为三个线程(IOThread、MainThread、ParserThread),首先IOThread会从网络端或者本地获取html数据,并把数据交给MainThread(渲染线程,十分繁忙,用于JS执行,页面布局等),为了保证MainThread不被阻塞,需要额外起一个后台线程(ParserThread)用来做html的解析工作。ParserThread每解析到落地页html中带有特殊class标记的一个div标签或者P标签(图中的first、second)时,就会触发一次MainThread的layout工作,并把layout后得到的高度与屏幕高度进行对比,如果当前layout高度已经大于屏幕高度,我们认为首屏内容已经完成布局,可以触发渲染上屏逻辑,不必等到整篇html全部解析完成再上屏,提前了首屏的渲染时间;80分位下,内核的渲染优化可以提升首屏速度100ms~200ms

b. 预加载JS: 预创建好WebView后,通过预加载JS(与内核约定好的JS内容,内核侧执行该JS时,只做初始化操作),触发WebView初始化逻辑,缩短后续加载url耗时;80分位性能提升80ms左右

五、新的问题-流量和速度的平衡

频繁预取会带来流量的浪费:预取的命中率虽然达到了90%以上,但有效率仅有15%

解决思路:

  1. 压缩预取的包大小,减少下行流量
  2. 少预取或者不预取
(一)精简预取数据:

图文:优化直出html中内联的css、等数据,数据大小减少约40%

(二)后端智能预取:
  1. 图文:通过对图文资源进行评分,来决定4G是否需要预取,多组AB试验最优效果劣化9.5ms 2)视频:为了平衡性能和流量,在性能劣化可接受的范围内(视频起播时间劣化100ms),针对视频部分采用流量高峰期不预取的策略,减少视频总流量约7%,整体带宽峰值下降3%
(三)AI智能预取

通用用户操作行为,对Feed预取进行AI预测,减少无效预取的数量。

六、总结&展望

(一)优化总结

在总结之前,先来看下整体优化的前后效果对比:

优化前

优化后

可以看到,经过一系列的优化手段,落地页已经实现了秒开效果。回顾所做的事情,从分析用户反馈到定位性能瓶颈,再到各种优化尝试,发现所有类似的性能优化手段都可以从以下几点入手:

  • 提前做:包括预创建WebView和预取数据
  • 并行做:包括图片直出&拦截加载,框架初始化阶段开启异步线程准备数据等
  • 轻量化:对于前端来说,要尽量减少页面大小,删减不必要的JS和CSS,不仅可以缩短网络请求时间,还能提升内核解析时间
  • 简单化:对于简单的信息展示页面,对内容动态性要求不高的场景,可以考虑使用直出替代hybrid,展示内容直接可渲染,无需JS异步加载

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

总而言之,成功是留给准备好的人的。无论是参加什么面试,都要做好充足的准备,注意好面试的礼仪和穿着,向面试官表现出自己的热忱与真诚就好。即使最后没有过关,也要做好经验的总结,为下一次面试做好充足准备。

这里我为大家准备了一些我在面试后整理的面试专题资料,除了面试题,还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料,免费分享给大家,希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

毕竟不管遇到什么样的面试官,去面试首先最主要的就是自己的实力,只要实力够硬,技术够强,就不怕面试拿不到offer!

想要面试顺通嘛,赶紧领取下面的面试资料为之后的面试做足准备叭!这里提前祝各位面试成功!

资料领取方式:  Android架构设计

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!

[外链图片转存中…(img-otU1XTAG-15527649518)]

[外链图片转存中…(img-LdESUTPo-15527649518)]

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!