Android WebView性能分析与优化

一、简介

一提到App内的WebView加载网页,大家的第一印象就是:慢、耗流量、体验比原生差。但WebView加载网页也有其天生的优势:动态,跨平台,开发周期短。

那能如何解决WebView加载网页慢和体验差的问题呢?可以思考下面两个问题:

  • 从打开浏览器到网页完全展示都发生了什么?
  • 如何给WebView加载网页提速?

二、整体思维导图

WebView分析优化.png

三、衡量标准

快慢是一个相对量,如何衡量WebView的快慢呢?

3.1 用户体验的时间尺度

从用户角度来看,如下图是2018年份百度移动端的统计数据:

页面放弃率与页面加载事件的关系图.png

2018年份百度移动端的统计数据

谷歌pc相关网页统计.png

Google PC相关网页的数据统计

根据上面的统计折线图,得出如下表格:

页面相关时间统计折线图.png

可以发现:

  • 小于1s,用户更容易接受,关闭率更低。
  • 用户对移动端的容忍比PC端更低,要求更高。

3.2 加载时长标准

加载时长 = 加载结束 - 加载开始

加载开始很好界定,当用户点击feed流里面的item开始,就开始计时。

加载结束呢?WebView有个WebViewClient#onPageFinished回调方法,这个方法是在页面完全加载结束时候回调的,但是页面DOM渲染完页面就已经有内容,对于用户来说算是页面已经展示出来了。统计加载时长以DOM渲染完更好些

3.3 统计标准

通过收集真正的用户使用数据,才能更好的根据用户的情况进行优化。那如何才能反应用户的真实情况呢?

通常有两种方式:

  • 平均数,容易被较长的加载时间给拉高,不容易反应真实情况。
  • 中位数,能很好的反应大多数用户的情况,但是中位数的要求较低,可以将其提高到80分位,或者90分位。

在们项目进行数据统计时候可以先采用80分位,检测下优化效果,后续再提高要求,使用更高分位如95分位等。

根据现网数据可知,当95分位的用户页面加载时长为1s以内时,80分位的用户页面加载时长为0.35s以内时,APP内网页的体验最佳。

具体最佳时间可以根据真实的上报数据的统计结果进行调整。

四、问题分析

前端页展示一般分两种:

  • 前后端分离,前端加载资源后,通过js请求展示的数据并在前端渲染展示。
  • 页面直出,页面数据由服务器填充完成后,直接下发到前端,由前端直接展示。

现在较多的采用前后端分离的方式,下面都以这种方式为例讲解。

4.1 WebView渲染过程

WebView渲染大致需要如下几步:

  • 解析 HTML 文件
  • 加载 JavaScript 和 CSS 文件
  • 解析并执行 JavaScript
  • 构建 DOM 结构
  • 加载图片等资源
  • 页面加载完毕
WebView渲染过程

4.2 WebView耗时统计方法

统计可从两方面入手,一是网页层统计,二是App层统计。

4.2.1 网页层统计:WebView中网页耗时统计方法

WebView加载url到完全展示出各个部分耗时情况,可以根据w3c标准中网页performance参数获取具体耗时统计参数信息,详细的页面加载过程见下图:

timing-overview.png

根据performance统计情况可以得出如下数据:

  • 重定向耗时:redirectEnd - redirectStart
  • DNS查询耗时 :domainLookupEnd - domainLookupStart
  • TCP链接耗时 :connectEnd - connectStart
  • HTTP请求耗时 :responseEnd - responseStart
  • 解析dom树耗时 : domComplete - domInteractive
  • 白屏时间 :responseStart - navigationStart
  • DOMready时间 :domContentLoadedEventEnd - navigationStart
  • onload时间:loadEventEnd - navigationStart,也即是onload回调函数执行的时间。

4.2.2 App层统计:App层统计WebView耗时

Android可以通过WebViewClient#onPageFinished回调统计页面整个加载时长,开始时间以WebView创建开始算,严格一点可以从feed流中点击item开始算。这个统计只能算整个加载时长,加载到用户可见的时长以DOM渲染完页面为准,后者比前置时长更短一些。前置供参考,以后者为准。

根据上图可以获取的统计数据:

  • WebView创建耗时:navigationStart - createWebView(以初始化开始时间为准,下同)
  • 交互开始到页面可见耗时:onClickItem - createWebView
  • 页面加载到可见耗时:domContentLoadedEventEnd - createWebView
  • 页面完全加载耗时:onPageFinished - createWebView

4.3 资讯统计数据

测试资讯连接1:测试文章1

测试数据1.png

测试资讯连接2:测试文章2

测试数据2.png

五、优化方案

从统计数据看,WebView首次加载耗时较多2s左右,二次加载耗时也有0.5s左右。

总结数据.png

5.1 离线化

同我们现有的离线包一样,将页面用的公共资源html,css,js等模板化,将模板打成压缩包形成离线包内置或动态下发到App端,在App中访问访问到具体的页面时候优先加载本地的模板资源。

通过WebViewClient#shouldInterceptRequest方法拦截WebView的资源加载,匹配到本地模板中的资源就直接加载本地资源,没有匹配本地模板资源再去加载线上资源。genWebResourceResponse用于实现具体的匹配策略。

override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
     return genWebResourceResponse(request, view)
}

模板注意事项:

  • 精简模板,移除不必要的js、css,进行异步拉取
  • 模板内联js、css,减少io
  • js尽量放到最后,避免阻碍DOM解析

5.2 数据与模板加载

5.2.1 并行执行:数据请求与模板加并行

虽然进行了本地化网页模板化,但整体的页面加载依然是串行执行的。为了进一步提高页面的加载速度,可以让数据请求由app端代理。使数据加载与模板加载并行执行,待数据加载完成时通过JsBridge回填到网页中。效果如下图:

数据与模板加载.png

5.2.2 数据预加载

既然数据请求已经由app代理了,当然也可以通过一定的策略预加载数据,当页面打开时候直接使用缓存数据。这样整个网页加载过程完全离线化不受网络影响。

本地加载.png

5.3 WebView预创建

由上面统计数据可知,WebView创建与二次创建耗时相差甚远,如下图总结:

总结数据.png

原因是Webview所有的逻辑处理都是通过WebViewProvider来实现的,它需要加载Webview内核,这是一个重量级的操作,内核是以apk的形式存在。而内核加载后在同一页面是共享的,因此后续的初始化时间就很少了。

可以通过预创建WebView来加速这一过程,预创建会消耗一定量的内存,如何平衡预创建和内存消耗问题还需实践把握衡量,具体方式:

WebView池(或统一全局WebView):在app启动时候后台创建WebView池,当app需要展示网页的时候直接拿已创建的WebView,需要在页面销毁时候清除页面数据。池结构如下:

WebView池

预创建WebView注意事项:

  • WebView初始化需要传context,需要注意内存泄漏
  • WebView创建需要较大内存,需要注意内存耗费
  • WebView复用需要清除数据,需要注意状态维护

5.4 模板预热

经过前面几步处理后,网页加载过程可以实现全部本地化后,但每次打开网页的时候还需要重复加载模板数据。DOM解析耗时,如下图:

模板预热

为了避免重复加载模板,则需要在WebView池的基础上,让池中的WebView预先加载本地模板。当需要展示网页时候直接拿到已经加载过本地模板的WebView,并通过JsBridge注入数据。池中结构如下:

模板预热池结构

网页加载的整个过程如下:

模板预热加载

5.5 图片加载

WebView在加载大量图片时候表现不佳,重复进入时还会重复加载图片,体验不好且浪费浏览。

5.5.1 App代理图片加载

该方式需要借助图片加载库如Glide,在WebViewClient#shouldInterceptRequest方法拦截WebView的资源加载,判断要加载的资源url是否为图片,是就走Glide加载并生成加载图片的WebResourceResponse,通过Glide来达到缓存图片目的,避免多次打开页面重复加载线上图片资源,genWebResourceResponse用于实现具体的匹配策略。这种方式有点是不需要前端配合,客户端完全自己处理即可。

在api>=21时,可以通过WebResourceRequest获取请求中的accept字段获取返回值类型,用于区分url类型。

override fun shouldInterceptRequest(
    view: WebView?,
    request: WebResourceRequest?
): WebResourceResponse? {
    val url = request.url.toString()
    if (checkImageRequest(request)) {
        val imageFile = Glide.with(view.context)
            .asFile()
            .load(url)
            .submit()
            .get()

        return WebResourceResponse(
            "image/png,*/*",
            "UTF-8",
            FileInputStream(imageFile)
        )
    }
    return super.shouldInterceptRequest(view, url)
}

在api<21时,只能通过url来判断来判断类型。

override fun shouldInterceptRequest(view: WebView?, url: String?): WebResourceResponse? {     // 处理资源匹配
     return genWebResourceResponse(url, view)
}

注:示例代码仅展示用,细节需要自己处理

5.5.2 hybrid

使用网页和原生控件的混合开发模式,网页中文字部分让WebView渲染,网页中的图片视频等使用原生控件展示。优点即可以避免重复加载图,又能提升图片浏览体验;缺点实现成本高,需要前后端协调处理。今日头条8.0.3版本同样采用了这种方式加载展示图片。

具体思路:

  • 图片展示容器与WebView上下叠放,大小一致
  • WebView中预留图片占位div
  • 获取网页中图片的url、大小以及位置信息
  • 通过js或其他方式通知App
  • App加载图片并根据WebView中占位div位置设置原生图片位置
  • 原生控件与WebView同步滚动

六、总结

Android中WebView还存在较大的优化空间,可以进一步提升资讯、活动页等h5页面的浏览体验。本文涉及的优化方式仅是方向性的,为后续Android App的WebView优化提供方向性指引,实际操作会涉及到多端配合,细节较多,需要不断迭代优化。

参考文章:

【白皮书4.0解读】页面加载速度的重要性

Does Page Load Time Really Affect Bounce Rate?

10的幂:用户体验的时间指标

web页面加载用户等待时间的性能指标

应用:前端性能监控performance

深入理解前端性能监控—Performance

今日头条品质优化 - 图文详情页秒开实践

WebView性能、体验分析与优化

VasSonic

ht-candywebcache-android

Android混合开发之——WebView中使用原生组件替换标签元素

iOS 牛牛圈文章详情页hybrid方案预研

w3c标准

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容