问题
在React Native中打开一个WebView会有非常明显的白屏时间。
分析问题
在网上看了一些关于WebView的优化,参考下面这个WebView打开过程:
我们可以总结为:
1、节约初始化的时间,预先初始化一个WebView。
2、原生端请求页面资源,优化白屏时间。
3、页面离线化,原生端直接从本地加载网页,优化白屏时间。
这三种方案都需要原生端进行改造,而且搞的不好可能出现内存泄漏的风险。页面离线化体验是最好的,但实现最复杂。
根据实际的业务场景,比如我们只想优化几个主页界面WebView的打开速度,笔者尝试把1和2结合起来使用,在用户进入需要打开WebView的页面前,先初始化并加载好一个页面,当用户触发打开WebView时直接把初始化好的页面呈现出来。
想法是美好的,但是能否真的可行呢,先得验证一下,我们从iOS端开始。
实验思路
iOS里打开一个webView(现在用的是WKWebView组件,它在iOS 8开始用来替换以前的UIWebView组件)需要三个步骤:
1、实例化一个WKWebView对象。
2、使用这个WKWebView对象加载URL。
3、把这个WKWebView当作子视图加入到父视图中。
我们实验组的思路是在父视图加载完毕以后,就执行前两步,在父视图里放一个按钮,用户点击按钮的时候执行第三部。当然还要弄一个对照组,就是用户点击按钮的时候执行1,2,3步,然后我们来对比两个组打开WebView的时间。
实验组:
先初始化WebView并加载页面,用户点击以后显示WebView。
代码:
界面:
当我们点击打开百度按钮时,界面会白屏1s左右,然后百度的网页就显示出来了。
对照组:
先不初始化WebView,用户点击以后进行初始化,加载和显示WebView。
代码:
和上面实验组当代码相比,我们只是移动了[self initWebView]这行代码的调用位置。
界面和实验组一样。
我们点击打开百度的按钮时,会出现白屏,3s以后才会显示百度页面。
实验总结
通过这个实验,我们知道这样做是完全可行的,特别是针对特定页面。比如我们的APP主要页面就是用WebView呈现的。但是我们在React Native里如何做到这一点呢?我们先来看看RN里使用WebView的情况。
react-native-wkwebview
https://github.com/CRAlpha/react-native-wkwebview
这个RN组件在RN本身不支持WKWebView时相当有用,但是目前有了https://github.com/react-native-community/react-native-webview以后,这个组件估计是不怎么维护了,不过没有关系,我们来看看这个组件是如何使用WebView的,并尝试给这个组件添加一个上面我们实现的功能,比如提供一个函数可以预先初始化好一个WebView,然后使用组件时通过传递参数指定打开预先初始化好的WebView。
这个组件原生代码的核心文件如下:
CRAWKWebView.m实现了WKWebView,以及里面的各种方法。它提供了一个函数initWithProcessPool来实例化一个真正的WKWebview。
CRAWKWebViewManager.m按照React-Native的规范,实现了一个可以和JS交互的对象。这个对象里,调用initWithProcessPool获得正在的WKWebview。
使用组件时,比如这样写:
<WKWebView style={styles.webView} source={{uri: 'http://www.baidu.com'}} />
实际对应的Native代码如下图,是这个view函数。
也就是说,每次render一个WKWebView组件,就会生成一个新的CRAWKWebView。我们来改造一下,让这个函数返回一个成员变量看看会发生什么。
如下图,我们新加一个pWebView属性,然后在view函数里返回这个_pWebView。
那这个pWebView变量在哪里进行初始化呢?我们可以导出一个函数让JS代码来初始化,如下图,添加一个initWebView函数:
这个函数对_pWebView进行初始化。然后我们就可以在js代码里就进行调用了。
如下图所示,我们一开始就调用WKWebViewManager对webView进行初始化,然后渲染一个WKWebView在界面上。同时界面上会有一个按钮,点击按钮会再次渲染一个新的WKWebView组件。
点击Press me之前
点击Press me按钮以后会这样显示,如下图:
网页一瞬间显示到下面去了,上面的却不见了~。这个现象刚好说明,我们的修改生效了,我们让每个WKWebView组件都返回了是同一个View。但是这个View一下从上面到了下面,还不知道是咋回事。
这样我们就可以针对特定需要打开的WebView进行优化了,具体怎么做呢?上面演示的每次只返回同一个webView明显不满足需求呀。
疑惑
1、iOS里一个viewController是如何被其他view进行代理的,生命周期是如何进行的。
2、如果销毁我新建的一个WebView。
总结
1、对iOS APP的基本构成有基础认识,比如入口函数,UIWindow对象,它的rootViewController。UIViewController等,学会了单视图开发流程。Object-C的函数声明和调用能看懂了。
2、对iOS APP如何实现多View切换还未了解,各种用到的系统类不熟悉。
3、对RN的允许机制有了更多的了解,RN运行的时候有一个jsruntime在运行JS代码,JS的代码通过UI组件、函数和native代码进行交互,但是只能交互特定的native组件,如果这个组件没有,就需要自己实现。自己实现的过程复杂度并不低于直接写原生。这就给开发者设置了一个障碍,如果你的业务复杂,对性能细节追求卓越,RN不是一个好的选择。如果你的业务能直接使用官方的和社区现有的组件就能实现,那使用RN能节约开发时间,但是如果你的业务会扩展到需要自己开发原生组件,那么就不太适合使用RN了。
4、RN 0.60以后和前面的版本差别比较大,导致升级困难。
4、每一样东西要系统的学都很困难,如何快速搞清楚一个概念需要从多个角度去观察,好的讲解能让人快速理解新的概念。
参考文章: