据相关数据显示,截至2017年底,中国手机新闻客户端用户规模达到6.36亿人,移动App已经成为新闻和内容传播的最重要途径之一。而伴随着行业的竞争和发展,App中的内容页在提升App品质、提升使用时长及提升用户黏性等方面,扮演着更为重要的角色,同时也面临着更大的挑战。

  1. 内容页在呈现上越来越丰富。新闻资讯作为内容页的主体,逐渐增加了更多的文字样式、内容形式、富媒体、以及广告、投票等更为丰富的元素。
  2. 内容页需要更多扩展区域来提高使用时长及用户黏性。在资讯主体之外,各个App逐渐打造了例如关注模块、推荐阅读模块、评论模块、运营模块等越来越多的扩展阅读区域。
  3. 短视频、直播的争夺越来越激烈。越来越多的新闻App都将视频作为独立的模块和独立的内容页进行展示。
  4. 同质化产品竞争激烈。要求更快的迭代速度、更优质的用户体验、更小的实现成本。

所以,新闻类App内容页架构的设计和技术的优化,也要配合产品形态的发展,在越来越复杂的需求挑战下,拥有快速响应的能力和稳定优质的体验。

本文结合分析目前主流(DAU)新闻类App如今日头条、腾讯新闻、天天快报、一点资讯等内容页技术方案的选择,一起探索新闻类App内容页的技术实现和优化。



插播广告 —— 几十行代码完成新闻类App多种形式内容页

HybridPageKit :一个针对新闻类App高性能、易扩展、组件化的通用内容页实现框架。

基于ReusableNestingScrollviewWKWebViewExtension、以及本文中关于内容页架构和性能的探索。



1. 概念定义

结合目前主流的内容页实现方式,我们把内容页分为上下两个部分,为了方便后续的阅读先简单定义下关键的名词。

  1. 上部分通常用WebView实现。常规包括标题 + 作者(关注)+ 资讯内容,我们称为WebView内容区
  2. 下半部分主要是平行于WebView的各种扩展内容,常规包括点赞打赏、广告推广、相关推荐,热门评论等等,我们称为Native扩展区
  3. WebView中每个复杂UI呈现、扩展区中每个独立模块,我们都称为一个模块组件

完整来看,整个内容页右侧(右滑)普遍为评论页。无论是之前流行的ScrollView右滑还是近期流行的Push新页面,这两种方式实现起来都比较简单且较为独立,故本文暂时忽略右侧(右滑)评论的部分。

2. 目录

- 技术方案选择 -


1.WebView类型选择

不同于微博,新闻类App的内容以段落性的文字为主,配合段落间的图片、富媒体等。同时为了满足跨平台的一致呈现、PC网页的文章转载、不同平台文章的抓取,以及注重阅读而非交互等原因,使用WebView加载渲染本地的HTML字符串数据已经成为了新闻类App通用的方案。

1. UIWebView VS WKWebView

2. 修复、扩展WKWebView

通过以上的分析,WkWebView从系统级的稳定性、性能以及后续扩展性都有很大的优势。通过WKWebViewExtension扩展修复原生WKWebView,结合HybridPageKit中WKWebView的回收复用逻辑,极大程度上解决了原生WKWebView的问题,起到了很好的效果。

2. WebView内容区与Native扩展区的衔接

对于目前的主流App来说,单纯的WebView已经无法满足复杂的呈现和逻辑。如何在页面中合理的处理WebView与扩展区中的多种View协同滚动,灵活扩展,并且支持下拉刷新、上拉加载等操作,不同的新闻类App也有不同的技术方案。

1. 结合TableView

2. ScrollView嵌套

3. WebView内复杂UI、复杂交互模块的展示

随着核心的WebView内容区逐渐支持复杂的呈现方式,单纯的H5基础渲染已经满足不了现有的需求,比如视频的交互、音乐的续播、以及各种地图、投票等组件。同时Web中复杂的UI和逻辑也极大降低了WebView的渲染速度,增加了开发和维护的成本。

1. 复杂UI及逻辑实现困难

2. 简单图片的展示耗时

3. Native化全部非文字类组件

为了减少实现复杂UI、复杂交互模块的开发、维护成本、减少模块在Web和Native间的逻辑流程,提高Web中模块的加载展示速度,在HybridPageKit中将Web中全部非文字类模块全部Native化。

4. 内容页全部组件的滚动复用

在Native化全部非文字类组件之后,面对文章中图片、富媒体数量的增多,以及Native扩展区元素的增加,没有复用回收的内容页从滚动性能及内存两个两个方面都面临着挑战。同时,为了更好的提升用户体验,需要对各个组件滚动时的位置进行计算,从而区分不同的区域进行诸如预处理、延迟释放等逻辑。

1. 主流滚动复用框架

2. WebView中组件的滚动复用

综上,通过ReusableNestingScrollview只需将模块对应Model扩展增加协议,滚动视图扩展Delegate,就可实现任何滚动视图中子View的回收复用功能。

3. 内容页中全部组件的滚动复用

在解决了内容WebView中非文字类组件的Native化、滚动复用之后,我们将实现思想运用到包含Native扩展区的,内容页整体架构中。如果从内容页的维度去看,内容WebView也可以算作一个组件,它和扩展区的各种组件一起作为Container的子View,也可以运用上面提到的ReusableNestingScrollview进行实现和管理。

所以整个内容页就是从两个维度、运用ReusableNestingScrollview中的实现方法两次实现滚动复用回收、数据驱动、组件自管理以及组件状态切换逻辑。

5. 组件异步拉取与动态调整

面对复杂的需求、以及按需加载、异步拉取等优化体验的策略,在HybridPageKit中也针对相应的场景做了高效的处理。

1. WebView字体大小调整

当WebView中字体大小调整时,需要同时调整全部Native组件的位置。我们监听WebView的ContenSize变化,当变化发生时,重新执行获取组件位置的JS语句获得全部组件的新位置。基于滚动复用的逻辑,只需要对在屏幕中的组件View的位置进行调整,其余只需要重新对组件对应Model的Frame进行赋值,极大提升了效率。在此基础上,要动态的检测ContenSize是否小于屏幕高度,高度小于一屏幕时,要同时调整Native扩展区组件的位置。

2. WebView中组件异步拉取数据渲染

对于异步拉取数据的组件,由于初始化时占位Div的高度为0,当数据获取成功,并渲染好组件后,需要首先执行JS动态修改对应占位Div的大小,之后按照以上的逻辑,重新赋值Native组件位置。

3. Native扩展区组件异步拉取数据渲染

Native扩展区中的组件不同于WebView中的组件,不依赖WebView自身渲染。所以当动态调整大小时,之需调整全部Native扩展区组件数据Model中保存的Frame信息,同时调整在屏幕中的组件位置即可。



插播广告 —— 几十行代码完成新闻类App多种形式内容页

HybridPageKit :一个针对新闻类App高性能、易扩展、组件化的通用内容页实现框架。

基于ReusableNestingScrollviewWKWebViewExtension、以及本文中关于内容页架构和性能的探索。



- 内容页组件化架构 -


在实现了以上技术关键点的基础上,如何合理的设计内容页通用的架构,快速响应内容页的各种需求调整,使整体架构易扩展、易维护,同时有较高的性能及较小的内存占用,成为了整个内容页架构实现的重点。在HybridPageKit中,我们围绕灵活复用、高内聚低耦合、易于实现扩展三个重点的方向,设计实现了基于组件化的内容页整体架构。

1. 组件化解耦及组件通信

为了满足内容页业务的相对独立,支持快速响应迭代及组件整体复用,内容页整体的结构应满足通用性、易于扩展、以及高内聚低耦合的特点。所以在ReusableNestingScrollview的支持下,采用组件化的方式实现全部内容页业务模块。

1. 组件化解耦

为了达到组件的高内聚、与内容页的低耦合,在HybridPageKit中拆分业务逻辑为独立的组件化的处理单元,每个处理单元通过MVC模式实现。其中Model作为组件的数据,只需要在实现解析逻辑同时,实现对应delegate即可。Controller只需要实现组件间通信的delegate,选择性的实现例如controller生命周期、webview关键回调、以及滚动复用相关的方法即可。通过组件的自管理及复用,组件可以集成统一的上报逻辑、业务逻辑到自己的Controller中,并且在不同类型的页面灵活复用。

2. 组件通信

为了更好的实现组件化的结构,组件的Controller需要在内容页初始化时进行注册。内容页在每个关键的生命周期或业务节点,采用中心化通信,广播执行相应的方法,组件的Controller按需实现处理即可。对于新增、删除功能,只需扩展delegate中的方法,内容页中触发方法、组件中实现方法即可。

2. 组件及WebView的复用管理

1. WebView & 组件View全局复用

为了提高WKWebView渲染速度,通过建立全局WKWebView复用回收池来复用WKWebView。除了基本的线程安全、复用状态管理等,在进入回收池前要load特殊Url以维护整个backFowardList。组件的View也是通过全局的复用回收池进行管理,使得相同的组件View可以灵活的出现在内容页、列表页等App内各个页面,极大的减少了开发成本,提高运行效率。

2. 自动回收 & 内存管理

WebView及组件View实现自动回收逻辑,每次在申请新View时检测活动队列中View的SuperView是否为nil,是则自动回收防止内存泄露,同时增加View最大数量阈值、内存告警自动释放逻辑等。

3. 内容页整体架构

1. 易于扩展业务节点 & 组件类型

对于增加关键的业务节点用于组件业务处理,我们只需扩展delegate中的方法,在相关组件中实现。内容页Controller中在相应位置,通过统一函数触发广播代理方法即可。对于增加组件来说,只需创建组件完全独立的MVC代码,实现数据解析Model并实现滚动复用delegate,在组件Controller中实现delegate中需要的方法等待调用,以及初始化时在内容页注册即可。删除组件完全无需操作内容页,删除独立的MVC结构并停止注册即可。

2. 易于扩展内容页类型

为了实现内容页扩展区的灵活复用,在HybridPageKit中也扩展了非WebView类型的内容页。就像文中之前提到的,如果将WebView看做一个整体作为一个组件,基于ReusableNestingScrollview的位置动态管理,完全可以替换成普通的View(类似Banner视频内容页),或者可扩展收起的View(问题回答页面)甚至tableView等。所以整个App内各种类型的内容页只需要简单的配置,便可进行实现和组件复用。

3. 内容页架构

结合ReusableNestingScrollviewWKWebViewExtension以及组件化的设计思路,HybridPageKit整体的架构如下:

通过继承特殊的内容页Controller并进行简单的配置,即可生成不同类型的内容页整体架构。框架内集成基本的Mustache解析和渲染。结合后台数据,只需实现对应页面中组件MVC逻辑即可。其中Model只需实现对应Protocol,Controller在内容页中注册,实现对应Protocol即可。



插播广告 —— 几十行代码完成新闻类App多种形式内容页

HybridPageKit :一个针对新闻类App高性能、易扩展、组件化的通用内容页实现框架。

基于ReusableNestingScrollviewWKWebViewExtension、以及本文中关于内容页架构和性能的探索。



- 首屏加载速度优化 -


新闻类App内容页,在Native的页面框架下,基于WebView进行加载和渲染。所以,从优化的角度就延伸出两个维度,即从Web的维度优化,以及从Native的维度优化。

1. Web维度的优化

2. Native维度的优化

3. 整体优化方法

综上,从一个内容页在列表上的点击,到WebView渲染结束,最后到用户的滚动操作,按照时间的顺序,全部的优化策略如下图:



插播广告 —— 几十行代码完成新闻类App多种形式内容页

HybridPageKit :一个针对新闻类App高性能、易扩展、组件化的通用内容页实现框架。

基于ReusableNestingScrollviewWKWebViewExtension、以及本文中关于内容页架构和性能的探索。



- 拾遗及Tips -


对于新闻类App内容页的完整的解决方案,还有一些基本的技术点,比如模板引擎及模板拼接的模块、JSApi注入及管理的模块等等,由于篇幅所限,暂且不做深入的展开。

内容页整体的实现和优化,依赖整个App的技术实现和结构,在实现和优化的过程中,还有许多权衡和妥协,以及许多通用的、细节的优化,这里就不一一赘述。


- 写在最后 -


文章全部的探索及分析的实现,除对应业务逻辑外,应用封装成三个框架:HybridPageKitReusableNestingScrollview以及WKWebViewExtension。最终可以通过几十行代码,完成新闻类App多种形式的、高性能、易扩展、组件化的内容页实现。

有任何疑问,欢迎提交 issue, 或者直接修改提交 PR!



插播广告 —— 几十行代码完成新闻类App多种形式内容页

HybridPageKit :一个针对新闻类App高性能、易扩展、组件化的通用内容页实现框架。

基于ReusableNestingScrollviewWKWebViewExtension、以及本文中关于内容页架构和性能的探索。