使用localstorage和预加载做到webview秒开

2018-10-27 17:24:45

参考地址   点击查看

提到网页加载速度优化,大家都会想到静态资源上CDN,CSS和JS文件合并,图片合并成雪碧图等常用手段;但是在某些特殊情况下这些常用方法也无法达到理想的效果。比如,在国际化场景下,很多国家还停留在2G网络阶段,无论如何优化,都无法避免过慢的网络请求。最近一直在做国际化(主要是印尼和泰国)背景下的webview性能优化,也算有一些经验。由于我们的产品是面向android用户的,而android手机对H5支持很好,因此我们主要是应用H5的新特性。

1.H5的menifest缓存的局限

H5的menifest缓存机制是大家经常提起的方法,这是一种使用起来很简单的机制,其基本原理就是:当menifest文件有更新时,就会更新整个应用,否则就不会请求网络而是使用本地缓存的资源。但是我们使用起来却有不少问题:比如即使menifest文件发生变化,应用也不能及时的更新。对于menifest机制我们的结论是:menifest可以用于缓存基本不发生变化的文件(比如基本样式表base.css,javascript类库jquery.js等),对于业务代码一定不要使用menifest缓存,否则可能导致业务代码无法更新

新浪微博中大量的使用了menifest缓存机制,但是他们也只缓存了类库和基本样式表,而没有缓存业务代码。这是一个例子 http://card.weibo.com/article/h5/s#cid=1001603928694585472076,其menifest文件如下:

CACHE MANIFEST
# v = 8f708b371de7949de35b8cec4dac678b
CACHE:
# CSS and CSS resource image
http://u1.sinaimg.cn/apps/media/css/article_201511121505.css
http://u1.sinaimg.cn/apps/media/img/bg_cricle.png
http://u1.sinaimg.cn/apps/media/img/bg_criclein.png
http://u1.sinaimg.cn/apps/media/img/bg_imgslayer.png
http://u1.sinaimg.cn/apps/media/img/font/myfont_v3.eot
http://u1.sinaimg.cn/apps/media/img/font/myfont_v3.woff
http://u1.sinaimg.cn/apps/media/img/font/myfont_v3.ttf
http://u1.sinaimg.cn/apps/media/img/font/myfont_v3.svg
http://u1.sinaimg.cn/apps/media/img/lib/icons.svg
http://u1.sinaimg.cn/apps/media/img/lib/icon_SmartisanNote.svg
http://u1.sinaimg.cn/apps/media/img/lib/icon_Smart.svg

# Placeholder image
#http://ww3.sinaimg.cn/small/6f21f059jw1e4vxadm276j20f008ca9w.jpg

# js file
http://u1.sinaimg.cn/apps/media/js/jquery.min.js
http://u1.sinaimg.cn/apps/media/js/page/article/show/offline_201511121505.js

# Emoticons top 10
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/6a/laugh.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/0b/tootha_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/6d/lovea_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/c4/liwu_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/9d/sada_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/40/hearta_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/d9/ye_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/19/heia_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/8c/hsa_org.gif
http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/d0/z2_org.gif

NETWORK:
*

2.使用localstorage缓存js和css文件

localstorage设计之初是为了缓存应用数据的,关于是否应该使用它缓存js和css文件,知乎上有一篇讨论: 静态资源(JS/CSS)存储在localStorage有什么缺点?为什么没有被广泛应用?。其中得票最多的基本上概况了localstorage的适用场景以及优缺点:

PC上因为localstorage兼容性不好,而且网速较快,因此实用价值不大
移动端单页面应用(webapp),因为localstorage兼容性好,网速慢,所以值得尝试
localstorage的安全性问题

UC的前端团队推出了基于localstorage存储前端静态资源的Scrat-webapp模块化开发体系,而且基于这个模块化体系开发了很多项目:神马视频NBA直播。大家可以通过chrome的开发者工具查看localstorage的使用情况。

基于以上的行业经验,我们也选用了localstorage作为自己的前端资源缓存,并实现了自己的基于localstorage的静态资源加载器,其实现原理可以参考我的博客:在网页中异步加载javascript 的XHR Eval方法。具体代码如下:

;(function (global) {
    'use strict';

    //检查文件类型
    var TYPE_RE = /\.(js|css)(?=[?&,]|$)/i;
    function fileType(str) {
        var ext = 'js';
        str.replace(TYPE_RE, function (m, $1) {
            ext = $1;
        });
        if (ext !== 'js' && ext !== 'css') ext = 'unknown';
        return ext;
    }

    //将js片段插入dom结构
    function evalGlobal(strScript){
        var scriptEl = document.createElement ("script");
        scriptEl.type= "text/javascript";
        scriptEl.text= strScript;
        document.getElementsByTagName("head")[0].appendChild(scriptEl) ;
    }

    //将css片段插入dom结构
    function createCss(strCss) {
        var styleEl = document.createElement('style');
        document.head.appendChild(styleEl);
        styleEl.appendChild(document.createTextNode(strCss));
    }

    // 在全局作用域执行js或插入style node
    function defineCode(url, str) {
        var type = fileType(url);
        if (type === "js"){
            //with(window)eval(str);
            evalGlobal(str);
        }else if(type === "css"){
            createCss(str);
        }
    }

    // 将数据写入localstorage
    var setLocalStorage = function(key, item){
        window.localStorage && window.localStorage.setItem(key, item);
    }

    // 从localstorage中读取数据
    var getLocalStorage = function(key){
        return window.localStorage && window.localStorage.getItem(key);
    }

    // 通过AJAX请求读取js和css文件内容,使用队列控制js的执行顺序
    var rawQ = [];
    var monkeyLoader = {
        loadInjection: function(url,onload,bOrder){
            var iQ = rawQ.length;
            if(bOrder){
                var qScript = {key: null, response: null, onload: onload, done: false};
                rawQ[iQ] = qScript;
            }
            //有localstorage 缓存
            var ls = getLocalStorage(url);
            if(ls !== null){
                if(bOrder){
                    rawQ[iQ].response = ls;
                    rawQ[iQ].key = url;
                    monkeyLoader.injectScripts();
                }else{
                    defineCode(url, ls)
                    if(onload){
                        onload();
                    }
                }
            } else {
                var xhrObj = monkeyLoader.getXHROject();
                xhrObj.open('GET', url, true);
                xhrObj.send(null);
                xhrObj.onreadystatechange = function(){
                    if(xhrObj.readyState == 4){
                        if(xhrObj.status == 200){
                            setLocalStorage(url, xhrObj.responseText);
                            if(bOrder){
                                rawQ[iQ].response = xhrObj.responseText;
                                rawQ[iQ].key = url;
                                monkeyLoader.injectScripts();
                            }else{
                                defineCode(url, xhrObj.responseText)
                                if(onload){
                                    onload();
                                }
                            }
                        }
                    }
                }
            }
        },

        injectScripts: function(){
            var len = rawQ.length;
            //按顺序执行队列中的脚本
            for (var i = 0; i < len; i++) {
                var qScript = rawQ[i];
                //没有执行
                if(!qScript.done){
                    //没有加载完成
                    if(!qScript.response){
                        console.error("raw code lost or not load!");
                        //停止,等待加载完成, 由于脚本是按顺序添加到队列的,因此这里保证了脚本的执行顺序
                        break;
                    }else{//已经加载完成了
                        defineCode(qScript.key, qScript.response)
                        if(qScript.onload){
                            qScript.onload();
                        }
                        delete qScript.response;
                        qScript.done = true;
                    }
                }
            }
        },

        getXHROject: function(){
            //创建XMLHttpRequest对象
            var xmlhttp;
            if (window.XMLHttpRequest)
                {
                    // code for IE7+, Firefox, Chrome, Opera, Safari
                    xmlhttp=new XMLHttpRequest();
                } else {
                    // code for IE6, IE5
                    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
                }
                return xmlhttp;
        }
    };


    global.monkeyLoader = monkeyLoader.loadInjection;

})(this);

在页面中我们可以使用monkeyLoader加载自己的静态资源

monkeyLoader('a.js',callbackA,true);
monkeyLoader('b.js',callbackB,true);

其中monkeyLoader的三个参数分别是:资源文件路径、资源加载完成后的回调函数、以及资源是否需要有序。

3.基于fis3的localstorage集成解决方案

FIS3作为一个优秀的前端工程化解决方案,其在业界的影响无处不在。作为狼厂的一员,我们在项目中也大量使用FIS3,下面我们讨论一下如何将localstorage集成到FIS3的编译流程中。

我们的目的是将通过FIS3工程化处理后的文件列表传递到monkeyLoader,以实现基于localstorage的加载。FIS3提供了静态资源映射表,当某个文件包含字符 RESOURCE_MAP,就会用表结构数据替换此字符,而该表记录了各个原始静态资源经过工程化后的目标文件。基于此,我们只需要对monkeyLoader进行简单的封装即可。下面是具体的封装代码:

fisMonkeyLoader = function(resourceMap,resourceList){
        var pkgList = [];
        for(var idx in resourceList){
            var resourceItem = resourceList[idx];
            var pakItem = resourceMap['pkg'][resourceMap['res'][resourceItem]['pkg']]['uri'];
            pkgList[pakItem] =  1;
        }
        for(var pkgIdx in pkgList){
            monkeyLoader(pkgIdx,null,true);
        }
}

而使用方法也很简单:

__inline('../js/lib/monkeyLoader.js');
fisMonkeyLoader(__RESOURCE_MAP__,[
    'css/animate.css',
    'css/app.css',
    'js/lib/zepto-1.1.3.min.js',
    'js/app/app.js',
]);

fisMonkeyLoader根据RESOURCE_MAP找到原始资源对应的经过工程化处理以后的资源,并通过monkeyLoader进行加载。

也许你已经注意到了,使用localstorage只能加载同域的js和css文件,因为我们没办法通过js代码读取其他域下的静态资源的内容。而且localstorage有大小限制,一般情况下每个域名下最多使用5M的本地存储空间,因此我们务必要处理超出大小限制是的异常情况,各个浏览器抛出的异常不太一致,这里给出了一个跨浏览器的异常检测方案

4.通过预加载解决首次加载过慢问题

针对目前常见的hybrid加载,我们可以通过让native客户端预加载webview从而提升首次速度。我们的实现是:客户端在启动时打开一个1px大小(用户不可见)的webview用于提前加载某些重要的页面,而这些页面通过上文中的localstorage机制存储了几乎全部js和css静态资源,从而使得当用户打开webview时得到秒开的体验。

本文同时发表在我的博客积木村の研究所 :http://foio.github.io/localstorage-webview/


  • 2021-04-13 09:52:18

    Kafka学习之路 Kafka的简介

    Kafka是最初由Linkedin公司开发,是一个分布式、分区的、多副本的、多订阅者,基于zookeeper协调的分布式日志系统(也可以当做MQ系统),常见可以用于web/nginx日志、访问日志,消息服务等等,Linkedin于2010年贡献给了Apache基金会并成为顶级开源项目。

  • 2021-04-13 09:53:12

    nodejs操作消息队列RabbitMQ

    消息队列(Message Queue,简称MQ),从字面意思上看,本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已。 其主要用途:不同进程Process/线程Thread之间通信。

  • 2021-04-15 10:07:49

    Chrome屏蔽Your connection is not private

    使用Fiddler时如何屏蔽Chrome的证书警告:"Your connection is not private"/"您的连接不是私密连接"(如图1所示)? 启动chrome的时候加上--ignore-certificate-errors命令行参数(如图2所示)即可。

  • 2021-04-15 10:10:00

    Puppeteer 系列踩坑日志—3—开启支持插件

    在使用puppeteer自动化的过程中,会发现其实开启的chrome往往自动禁用了插件功能,如果我们想在自动化测试的过程中,再去使用一些常用的插件提升效率(偷懒)的话,就行不通了,其实解决办法还是有的,我们今天就来讲解这个问题。

  • 2021-04-15 10:11:17

    Puppeteer拦截修改返回值

    page.setRequestInterception(true)拦截器的使用方法和场景 现附上Puppeteer的Api的链接https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md

  • 2021-04-15 10:32:18

    怎么给 headless chrome添加cookies

    In puppeter you have access to the session cookies through page.cookies(). So once you log in, you could get every cookie and save it in a json file:

  • 2021-04-15 10:51:21

    如何通过Devtools协议拦截和修改Chrome响应数据

    在日常研究中,我们经常碰到大量JavaScript代码,我们首先要深入分析才能了解这些代码的功能及具体逻辑。这些代码代码可能会被恶意注入到页面中,可能是客户送过来需要我们帮忙分析的脚本,也可能是我们的安全团队在网页上找到的引用了我们服务的某些资源。这些脚本通常代码量不大、经过混淆处理,并且我们总是需要经过多层修改才能继续深入分析。