Code前端首页关于Code前端联系我们

百度APP开发团队:Android H5首屏优化实践

terry 2年前 (2023-09-23) 阅读数 70 #移动小程序

1.背景

百度App在2016年上半年尝试了Feed流业务模式。 2017年下半年,经过10次版本迭代,产品已基本具备初审形态。在整个Feed流形态的闭环中,新闻信息页面(本文称为登陆页面)是重要的一环。如果打开页面后加载时间过长,会严重影响用户体验。这就是为什么我们对H5的首屏速度(例如登陆页面)进行了长期优化。这篇文章涵盖了整个优化思路和技术细节

2.方法论

分析用户反馈,我们发现登陆页面当时是一键点击的。第一个屏幕平均需要 3 秒才会出现。每当用户想要浏览自己感兴趣的文章时,他们都会因为加载时间长而不耐烦地选择back。为了改善用户体验,我们做了以下工作:
百度APP开发团队:Android H5首屏优化实践

  • 通过用户反馈、QA测试等渠道,发现首页首屏加载缓慢的问题
  • 设置首屏性能指标(首屏有图,图片加载有效;首屏无图,文字渲染为主)
  • 不适用,核心与H5共享H5的加载过程,专注上报
  • 统计这边是根据三位首长汇报的数据来的。生成平均值和第80个百分位值的性能报告
  • 分析性能报告,找到不合理的耗时点并进行优化
  • 使用AB试验方法比较优化前后的性能报告数据,以获得优化结果。用户体验等相关指标
  • 根据长期优化的方法,我们将不断分析、定位和优化性能瓶颈,通过AB实验评估影响,最终实现我们秒级打开落地页的目标

3. 一个简单的混合解决方案 说到性能瓶颈

(1)解决方案的简单描述
在优化之前,像大多数该领域的应用一样,在起始页交叉的技术选型上。 -平台和动态要求,我们实现了Hybrid,这是一个相对成熟的解决方案。混合,顾名思义,是一种半原生、半Web方式的混合开发。页面复杂的交互功能是通过端到端的特性以及调用原生API来实现的。价格实惠,灵活性好,适合注重数据展示的H5场景。
下图为 Hybrid 在百度应用中的实现机制及下载流程
百度APP开发团队:Android H5首屏优化实践
(2)性能瓶颈 分析首屏 Hybrid 解决方案慢的原因,查找 In针对某些性能瓶颈,客户端和用户界面对自身下载过程中的关键节点进行统计,并通过性能监控平台日志进行展示。下图是某天全网所有用户登陆页面首屏速度的监听。第 80 个百分位数数据
百度APP开发团队:Android H5首屏优化实践
每个阶段的性能点可以根据混合充电过程来划分。可以看到,从点击到首屏大约需要2600ms,其中NA组件初始化需要350ms,Hybrid初始化需要170ms。 H5 的用户界面需要 1400ms 执行 JS 获取文本和渲染,700ms 加载和渲染图像
让我们更详细地分析性能下降主要发生在哪四个阶段:
1)平台 NA 组件
来自 单击完成主页框架的初始化。主要工作是初始化WebView,尤其是第一次进入时(第一次创建WebView的平均时间为500ms)
2)混合初始化
此阶段工作主要有两个部分,一是根据混合模型的协议中传递的相关参数发送到本地进行检查和解码,大约需要100ms。此外,WebView.loadUrl 执行后,它开始解析混合模板的标头和正文。与身体。这时就需要下载解析页面所需的JS文件,并使用JS播放器开始文字处理。在数据请求中,当客户端从服务器接收到数据时,它通过 JsCall 将其发送回用户界面。 UI需要解析客户端发送过来的JSON格式的文本数据,并形成DOM结构,从而启动核心渲染过程。 ;这个过程有请求、加载、解析、执行JS等几个步骤,还有调用后端属性、解析JSON、构造DOM等操作,比较耗时
4)上传image
章节 步骤(3)中用户界面接收到的文本数据中包含起始页的图像地址集合。当文本渲染完成后,UI 需要能够再次发出图像请求。客户按顺序收到图像地址和请求。服务器,当下载完成后,客户端调用一次IO将文件写入缓存,同时返回对应的图片本地地址给UI,最后通过内核启动另一次IO操作获取图片用于渲染的数据流;全面的 ,图像渲染时间取决于用户界面的解析效率、终端的性能效率、下载速度、IO速度等因素。
通过分析,扩展了关于Hybrid解决方案的想法:

  • 为什么渲染这么慢
  • 图像请求无法前进
  • 串行逻辑能否切换到并行
  • WebView初始化时间还能优化吗

百度app首页优化方案

(1)CloudHybrid
根据之前对混合性能的分析,根据分析,我们内部孵化了一个名为CloudHybrid的项目,来解决首屏显示慢的问题。首页。用一句话来描述CloudHybrid解决方案,它采用后端直接输出+预取+捕获的方式来简化页面渲染过程。提升并并行化Web请求逻辑,提高H5首屏速度

1。直接后台打印-快速渲染第一屏
a。静态直接页面打印
混合解决方案的预设和完成的HTML 文件只是一个包含一些简单JS 和CSS 文件的模板文件。最后加载HTML后,需要运行JS通过end属性向服务器异步请求文本数据。获得数据后,您仍然需要对其进行解析。经过构建 JSON、DOM 和应用 CSS 样式等几个耗时的步骤后,核心终于可以渲染屏幕了。为了提高首屏的显示速度,可以使用后台渲染技术(smarty)来集成文本数据和用户界面代码。直接取首屏内容,直接关机后,HTML文件包含首屏所需的内容和样式,可以直接由内核渲染;首屏之外的内容(包括相关推荐、广告等)可以在核心渲染完首屏后进行渲染。执行JS,使用preact进行异步渲染

百度APP直接导出方案:

百度APP开发团队:Android H5首屏优化实践
从CDN拉取到客户端的html已经渲染到服务器首屏了。这类内容不需要二次处理,显示速度可以大大提高。只是一点,移动 Feed 登陆页面首屏的性能数据从 2600ms 优化到了 2000ms 以下
b。动态数据后填充
为了保证首屏渲染结果的准确性,在服务器端除了需要文本内容和用户界面代码的融合之外,还需要一些影响渲染结果的客户端状态信息页面,比如第一张图片的地址、字体大小、夜间模式等。
这里使用的是Dynamic。在 postfill 方法中,用户界面定义了一系列特殊字符,用作传出 HTML 中的占位符;在加载URL之前,客户端使用常规搜索来查找这些占位符字符,并根据协议将它们与最终数据进行匹配;客户填写的html内容已经有屏幕了显示第一屏幕的所有条件
c。动画之间的渲染
我们先看一下优化前后(上图:优化前,下图:优化后):
百度APP开发团队:Android H5首屏优化实践百度APP开发团队:Android H5首屏优化实践
一般情况下,直接导出后的页面显示速度已经很快了;但在实际开发中,你可能会遇到这样的问题:无论你的数据加载速度有多快,在动作切换过程中,H5页面都无法渲染(可以通过状态联系开发者减慢动画时间,以确保),导致视觉上的白屏效果(如上图所示)
查看源码,我们发现系统在处理视图绘制时,有一个属性setDrawDuringWindowsAnimating。顾名思义,该属性用于控制动画过程中窗口是否可以正常绘制。在Android 4.2和Android N之间,系统考虑更换组件。该字段不正确。我们可以使用反射手动编辑它。该功能的改进效果可以在上图


/**
     * 让 activity transition 动画过程中可以正常渲染页面
     */
    private void setDrawDuringWindowsAnimating(View view) {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M
                || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            // 1 android n以上  & android 4.1以下不存在此问题,无须处理
            return;
        }
        // 4.2不存在setDrawDuringWindowsAnimating,需要特殊处理
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            handleDispatchDoneAnimating(view);
            return;
        }
        try {
            // 4.3及以上,反射setDrawDuringWindowsAnimating来实现动画过程中渲染
            ViewParent rootParent = view.getRootView().getParent();
            Method method = rootParent.getClass()
                    .getDeclaredMethod("setDrawDuringWindowsAnimating", boolean.class);
            method.setAccessible(true);
            method.invoke(rootParent, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * android4.2可以反射handleDispatchDoneAnimating来解决
     */
    private void handleDispatchDoneAnimating(View paramView) {
        try {
            ViewParent localViewParent = paramView.getRootView().getParent();
            Class localClass = localViewParent.getClass();
            Method localMethod = localClass.getDeclaredMethod("handleDispatchDoneAnimating");
            localMethod.setAccessible(true);
            localMethod.invoke(localViewParent);
        } catch (Exception localException) {
            localException.printStackTrace();
        }
    }

2中看到。智能预取-提前网页请求
直接转换后,我们可以提前向CDN请求部分首页html,以更快地获得首屏,并减少与过程相关的网络请求。按照一定的策略和时序,缓存在本地,这样当用户点击查看新闻时,只需要从缓存中下载即可

手百预取服务架构图

百度APP开发团队:Android H5首屏优化实践
目前移动预取服务支持多种业务群,例如图像、文本、地图、视频和广告。根据不同的业务场景,可以修改触发时间,也可以按照我们默认的更新和滑动停止。 、点击等时机。另外,我们对预取的内容进行优先级排序(按资源类型、触发时机),并根据手机当前的状态信息动态进行并发控制和流量控制。在某些降级场景下,服务器还可以通过云控制来控制预取和预取数量
3。通用抓取——缓存共享、请求并行
在落地页上,除了文字,图片也是重要的一部分直接导出解决了文本显示速度的问题,但是图像的加载和渲染速度仍然不理想,尤其是对于图像为首屏的文章。第一张图的渲染速度就是实际的首屏时间点
传统混合 该方案中,首页通过客户端调用NA图片加载功能来缓存和渲染图片。虽然客户端和UI图像缓存是共享的,但是由于JS执行时序较晚以及多个客户端函数调用的效率问题,导致图像渲染延迟
百度APP开发团队:Android H5首屏优化实践
原改进方案:改善加载为了图片速度,减少JS调用耗时,图片请求改为纯H5。虽然速度得到了提升,但是客户端和界面缓存无法共享。当点击图片调出NA图片查看器时,无法实现沉浸式效果,还需要重新加载图片,导致流量浪费
最终解决方案:使用核心shouldInterceptRequest回调 ,拦截登陆页面图像请求,客户端调用 NA -图像加载框架以通过管道
百度APP开发团队:Android H5首屏优化实践
加载并填充其在 WebResourceResponse 中的核心
百度APP开发团队:Android H5首屏优化实践
此解决方案将客户端与客户端的接触分开。图像渲染速度。在UI代码中,客户端充当请求和缓存图片的服务器,保证UI和客户端能够共享图片缓存。修改后的方案无首屏显示过程,无侧延迟,首屏显示的第80个百分位值缩短。 80ms~150ms
效果如下(上:优化前的混合方案;下:优化后的通用捕获方案):
百度APP开发团队:Android H5首屏优化实践百度APP开发团队:Android H5首屏优化实践
4。整体求解流程
百度APP开发团队:Android H5首屏优化实践
(2)新的优化尝试

1. WebView预生成
为了挽救WebView的性能下降,我们可以在合适的时候预生成WebView,并在页面需要显示内容时缓存起来。 ,直接从缓存池中获取生成的WebView。根据效率数据,WebView预览可以使首屏渲染时间增加200ms+
百度APP开发团队:Android H5首屏优化实践
以feed的起始页为例。当用户进入应用程序并触发输入屋顶动作后,我们创建第一个WebView。当用户来到登陆页面时,将其从缓存池中移除以渲染H5页面。这样既不影响页面加载速度,同时又保证下次登陆页面在缓存中仍然可用。对于WebView组件,我们在每次页面加载(pageFinish)或者back离开起始页面时,都会启动预先创建的WebView的逻辑
因为WebView的初始化必须绑定到上下文上才能实现实现预先创建的逻辑编辑,我们需要确保上下文一致性。作为一般实践,我们考虑使用片段来实现托管 H5 页面的容器。这样,上下文可以使用外部活动实例,但是更改片段本身的流动性存在一定的问题,这是有限的。 WebView 预渲染的适用场景 为此,我们找到了更完整的替代方案,即 MutableContextWrapper

ContextWrapper 的特殊版本,它允许在初始设置后修改基本上下文。更改此 ContextWrapper 的基本上下文。然后,所有调用都将委托给基础上下文。与 ContextWrapper 不同,即使已经设置了基本上下文,也可以更改它。
简单来说,它是一个新的上下文包装类,允许外部修改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 的速度

输入登陆页场景,因为我们的登陆页包含 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 标签时,就会启动 MainThread 的布局作业,并将布局后​​获得的高度与屏幕。反之,如果当前布局高度大于屏幕高度,我们认为第一屏内容已经布局完毕,可以触发屏幕渲染逻辑。无需等待所有 HTML 代码完全解析后再加载屏幕,从而增加了首屏的渲染时间。 在第 80 个百分位数处,内核渲染优化可以将首屏速度提升 100ms ~ 200ms
百度APP开发团队:Android H5首屏优化实践
b. 预加载 JS:
预创建 WebView 后,预加载 JS(JS 内容与核心端执行JS时,只执行初始化函数),发起WebView初始化逻辑,缩短后续从URL加载的时间; 80。百分位数性能提升约80ms

5.新问题-流量与速度平衡有效率只有15%
解决方案:
&nbsp&nbsp1.压缩预取数据包大小,减少下行流量
&nbsp&nbsp2.减少预取或不预取
(1) 精简预取信息:
图形:优化 HTML 数据内部 css、图标等,数据大小减少约 %% (2) 智能背景预取:


1)图文:通过评估图文资源,判断4G是否需要预取,多个AB组测试最优效果下降9.5ms
2)视频:平衡性能和性能当视频部分的流量在可接受的性能下降范围内(视频开始时间下降100ms)时,采用高峰时段不进行预取的策略。视频总流量减少7%左右,总带宽峰值降低3%
(3)智能AI预取
通用用户操作行为,输入预取AI预测,减少了操作量数据预取错误。

六、总结与展望

(一)优化总结
总结之前,我们先看一下整体优化前后的效果对比(上:优化前;下:优化后):
百度APP开发团队:Android H5首屏优化实践百度APP开发团队:Android H5首屏优化实践
可以看到,经过多次优化后,落地页达到了即开即开的效果。回顾我们所做的事情,从分析用户反馈到定位性能瓶颈以及各种优化工作,我们发现所有相应的性能优化方法都可以从以下几点入手:

  • 提前做好:包括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;
        }

    并行处理:包括直接图像导出和捕获加载、在帧初始化阶段启动异步线程准备数据等。

  • 轻量级:对于前端来说,需要将页面大小减小为尽可能多地删除不必要的 JS 和 CSS。不仅可以减少网页请求时间,还可以提高内核解析时间
  • 简化:对于简单的数据展示页面以及内容动态性要求不高的场景,可以考虑使用直接输出,而不是混合输出。内容可以直接渲染。无需JS异步加载

百度APP开发团队:Android H5首屏优化实践
(2) TODO

  • 页面更新机制。目前的解决方案只适用于静态页面。对于动态要求较高的公司,必须提供页面刷新机制,保证始终显示。前面的路注定是坎坷的。希望大家支持

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门