zoukankan      html  css  js  c++  java
  • AppCache 离线存储 应用程序缓存 API 及注意事项

    使用ApplicationCache接口实现离线缓存

    原文:http://www.mb5u.com/HTML5/html5_96464.html

    推荐:html5 application cache遇到的严重问题
    在我们的3G版网站的项目中使用了html5 application cache,将大部分图片资源、js、css等静态资源放在manifest文件中,需要了解的朋友可以参考下

    简介 
    离线访问对基于网络的应用而言越来越重要。虽然所有浏览器都有缓存机制,但它们并不可靠,也不一定总能起到预期的作用。HTML5 使用 ApplicationCache 接口解决了由离线带来的部分难题。 
    使用缓存接口可为您的应用带来以下三个优势

    离线浏览 – 用户可在离线时浏览您的完整网站 
    速度 – 缓存资源为本地资源,因此加载速度较快。 
    服务器负载更少 – 浏览器只会从发生了更改的服务器下载资源。 

    应用缓存(又称 AppCache)可让开发人员指定浏览器应缓存哪些文件以供离线用户访问。即使用户在离线状态下按了刷新按钮,您的应用也会正常加载和运行。 
    缓存清单文件 
    缓存清单文件是个简单的文本文件,其中列出了浏览器应缓存以供离线访问的资源。 
    引用清单文件 
    要启用某个应用的应用缓存,请在文档的html 标记中添加manifest 属性: 

    代码如下:
    1
    2
    3
    4
    5
    www.mb5u.com
     
    <html manifest="example.appcache">
    ...
    </html>

      


    您应在要缓存的网络应用的每个页面上都添加 manifest 属性。如果网页不包含 manifest 属性,浏览器就不会缓存该网页(除非清单文件中明确列出了该属性)。这就意味着用户浏览的每个包含manifest 的网页都会隐式添加到应用缓存。因此,您无需在清单中列出每个网页。 
    manifest 属性可指向绝对网址或相对路径,但绝对网址必须与相应的网络应用同源。清单文件可使用任何文件扩展名,但必须以正确的 MIME 类型提供(参见下文)。 

    代码如下:
    1
    2
    3
    ...
    </html>

      


    清单文件必须以 text/cache-manifest MIME 类型提供。您可能需要向网络服务器或 .htaccess 配置添加自定义文件类型。 
    例如,要在 Apache 中提供此 MIME 类型,请在您的配置文件中添加下面一行内容: 
    AddType text/cache-manifest .appcache要在 Google App Engine 的 app.yaml 文件中提供此 MIME 类型,则添加以下内容: 
    - url: /mystaticdir/(.*.appcache) 
    static_files: mystaticdir/1 
    mime_type: text/cache-manifest 
    upload: mystaticdir/(.*.appcache)清单文件结构 
    简单的清单格式如下: 
    CACHE MANIFEST 
    index.html 
    stylesheet.css 
    images/logo.png 
    scripts/main.js该示例将在指定此清单文件的网页上缓存四个文件。 
    您需要注意以下几点: 
    CACHE MANIFEST 字符串应在第一行,且必不可少。 
    网站的缓存数据量不得超过 5 MB。不过,如果您要编写的是针对 Chrome 网上应用店的应用,可使用 unlimitedStorage 取消该限制。 
    如果清单文件或其中指定的资源无法下载,就无法进行整个缓存更新进程。在这种情况下,浏览器将继续使用原应用缓存。 
    我们再来看看更复杂的示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    CACHE MANIFEST
    # 2010-06-18:v2
    # Explicitly cached 'master entries'.
    CACHE:
    /favicon.ico
    index.html
    stylesheet.css
    images/logo.png
    scripts/main.js
    # Resources that require the user to be online.
    NETWORK:
    login.php
    /myapi
    http://api.twitter.com
    # static.html will be served if main.py is inaccessible
    # offline.jpg will be served in place of all images in images/large/
    # offline.html will be served in place of all other .html files
    FALLBACK:
    /main.py /static.html
    images/large/ images/offline.jpg

      


    *.html /offline.html以“#”开头的行是注释行,但也可用于其他用途。应用缓存只在其清单文件发生更改时才会更新。例如,如果您修改了图片资源或更改了 JavaScript 函数,这些更改不会重新缓存。您必须修改清单文件本身才能让浏览器刷新缓存文件。使用生成的版本号、文件哈希值或时间戳创建注释行,可确保用户获得您的软件的最新版。您还可以在出现新版本后,以编程方式更新缓存,如更新缓存部分中所述。 
    清单可包括以下三个不同部分:CACHE、NETWORK 和 FALLBACK。 
    CACHE: 
    这是条目的默认部分。系统会在首次下载此标头下列出的文件(或紧跟在 CACHE MANIFEST 后的文件)后显式缓存这些文件。 
    NETWORK: 
    此部分下列出的文件是需要连接到服务器的白名单资源。无论用户是否处于离线状态,对这些资源的所有请求都会绕过缓存。可使用通配符。 
    FALLBACK: 
    此部分是可选的,用于指定无法访问资源时的后备网页。其中第一个 URI 代表资源,第二个代表后备网页。两个 URI 必须相关,并且必须与清单文件同源。可使用通配符。 
    请注意:这些部分可按任意顺序排列,且每个部分均可在同一清单中重复出现。 
    以下清单定义了用户尝试离线访问网站的根时显示的“综合性”网页 (offline.html),也表明了其他所有资源(例如远程网站上的资源)均需要互联网连接。 
    CACHE MANIFEST 
    # 2010-06-18:v3 
    # Explicitly cached entries 
    index.html 
    css/style.css 
    # offline.html will be displayed if the user is offline 
    FALLBACK: 
    / /offline.html 
    # All other resources (e.g. sites) require the user to be online. 
    NETWORK: 

    # Additional resources to cache 
    CACHE: 
    images/logo1.png 
    images/logo2.png 
    images/logo3.png请注意:系统会自动缓存引用清单文件的 HTML 文件。因此您无需将其添加到清单中,但我们建议您这样做。 
    请注意:HTTP 缓存标头以及对通过 SSL 提供的网页设置的缓存限制将被替换为缓存清单。因此,通过 https 提供的网页可实现离线运行。

    更新缓存 
    应用在离线后将保持缓存状态,除非发生以下某种情况: 
    用户清除了浏览器对您网站的数据存储。 
    清单文件经过修改。请注意:更新清单中列出的某个文件并不意味着浏览器会重新缓存该资源。清单文件本身必须进行更改。 
    应用缓存通过编程方式进行更新。 

    缓存状态 
    window.applicationCache 对象是对浏览器的应用缓存的编程访问方式。其 status 属性可用于查看缓存的当前状态: 

    代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    var appCache = window.applicationCache;
    switch (appCache.status) {
    case appCache.UNCACHED: // UNCACHED == 0
    return 'UNCACHED';
    break;
    case appCache.IDLE: // IDLE == 1
    return 'IDLE';
    break;
    case appCache.CHECKING: // CHECKING == 2
    return 'CHECKING';
    break;
    case appCache.DOWNLOADING: // DOWNLOADING == 3
    return 'DOWNLOADING';
    break;
    case appCache.UPDATEREADY: // UPDATEREADY == 4
    return 'UPDATEREADY';
    break;
    case appCache.OBSOLETE: // OBSOLETE == 5
    return 'OBSOLETE';
    break;
    default:
    return 'UKNOWN CACHE STATUS';
    break;
    };

      


    要以编程方式更新缓存,请先调用 applicationCache.update()。此操作将尝试更新用户的缓存(前提是已更改清单文件)。最后,当 applicationCache.status 处于 UPDATEREADY 状态时,调用applicationCache.swapCache() 即可将原缓存换成新缓存。 

     代码如下:

    1
    2
    3
    4
    5
    6
    var appCache = window.applicationCache;
    appCache.update(); // Attempt to update the user's cache.
    ...
    if (appCache.status == window.applicationCache.UPDATEREADY) {
    appCache.swapCache(); // The fetch was successful, swap in the new cache.
    }

      


    请注意:以这种方式使用 update() 和 swapCache() 不会向用户提供更新的资源。此流程只是让浏览器检查是否有新的清单、下载指定的更新内容以及重新填充应用缓存。因此,还需要对网页进行两次重新加载才能向用户提供新的内容,其中第一次是获得新的应用缓存,第二次是刷新网页内容。
    好消息是,您可以避免重新加载两次的麻烦。要使用户更新到最新版网站,可设置监听器,以监听网页加载时的 updateready 事件: 

    代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // Check if a new cache is available on page load.
    window.addEventListener('load', function(e) {
    window.applicationCache.addEventListener('updateready', function(e) {
    if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
    // Browser downloaded a new app cache.
    // Swap it in and reload the page to get the new hotness.
    window.applicationCache.swapCache();
    if (confirm('A new version of this site is available. Load it?')) {
    window.location.reload();
    }
    else {
    // Manifest didn't changed. Nothing new to server.
    }
    }, false);
    }, false);

      


    APPCACHE 事件 
    正如您所预期的那样,附加事件会用于监听缓存的状态。浏览器会对下载进度、应用缓存更新和错误状态等情况触发相应事件。以下代码段为每种缓存事件类型设置了事件监听器: 

    代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function handleCacheEvent(e) {
    //...
    }
    function handleCacheError(e) {
    alert('Error: Cache failed to update!');
    };
    // Fired after the first cache of the manifest.
    appCache.addEventListener('cached', handleCacheEvent, false);
    // Checking for an update. Always the first event fired in the sequence.
    appCache.addEventListener('checking', handleCacheEvent, false);
    // An update was found. The browser is fetching resources.
    appCache.addEventListener('downloading', handleCacheEvent, false);
    // The manifest returns 404 or 410, the download failed,
    // or the manifest changed while the download was in progress.
    appCache.addEventListener('error', handleCacheError, false);
    // Fired after the first download of the manifest.
    appCache.addEventListener('noupdate', handleCacheEvent, false);
    // Fired if the manifest file returns a 404 or 410.
    // This results in the application cache being deleted.
    appCache.addEventListener('obsolete', handleCacheEvent, false);
    // Fired for each resource listed in the manifest as it is being fetched.
    appCache.addEventListener('progress', handleCacheEvent, false);
    // Fired when the manifest resources have been newly redownloaded.
    appCache.addEventListener('updateready', handleCacheEvent, false);

      

    如果清单文件或其中指定的资源无法下载,整个更新都将失败。在这种情况下,浏览器将继续使用原应用缓存

    应用程序缓存 API(简称“AppCache”)

     

    使用 AppCache 在本地保存资源,你可以通过减少主机服务器的请求数量来改善网页性能;此外,你还可以脱机访问缓存的资源。

    若要在本地缓存资源,请执行下列操作:

    1. 创建一个清单文件,以指定你要保存的资源。
    2. 在要使用缓存资源的每个网页中引用该清单文件。

    H5的一个重要特性就是离线存储,所谓的离线存储就是将一些资源文件保存在本地,这样后续的页面重新加载将使用本地资源文件,在离线情况下可以继续访问web应用,同时通过一定的手法(更新相关文件或者使用相关API),可以更新、删除离线存储等操作;

    H5的离线存储使用一个manifest文件来标明哪些文件是需要被存储的,使用如 <html manifest='offline.manifest'> 来引入一个manifest文件,这个文件的路径可以是相对的,也可以是绝对的,如果你的web应用很多,而且希望能集中管理manifest文件,那么静态文件服务器是个不错的选择。

    对于manifest文件,要求:文件的mime-type必须是 text/cache-manifest类型

    如果你是JAVA工程,在你的web.xml中配置请求后缀为manifest的格式:

    1. 1
      2
      3
      4
      5
      6
      [html] view plaincopy
        
      <mime-mapping> 
              <extension>manifest</extension> 
              <mime-type>text/cache-manifest</mime-type> 
      </mime-mapping>

        

         

    这样可以控制请求到的manifest文件格式为text/cache-manifest的。

    创建清单文件

    清单文件是一个针对网页所使用的资源指定缓存行为的文本文件,参见下面的示例。

     
    CACHE MANIFEST
    
    CACHE:
    # Defines resources to be cached.
    script/library.js
    css/stylesheet.css
    images/figure1.png
    
    FALLBACK:
    # Defines resources to be used if non-cached
    # resources cannot be downloaded, for example
    # when the browser is offline..
    photos/ figure2.png
    
    NETWORK:
    # Defines resources that will not be cached.
    figure3.png
    
    
    
    清单文件分为以下各节:
    • 其中 CACHE 不是必须存在的,可以直接在 CACHE MANIFEST 行之下直接写需要缓存的文件,在这里指明的文件将被缓存到浏览器本地。
    • 在NETWORK之下指明的文件,是强制必须通过网络资源获取的
    • 在FALLBACK下指明的是一种失败的回调方案,比如上述index.php无法访问,那么就发出404.htm请求
    清单文件可包含任意数量的这些节,并且可以重复;但新的节必须以节标题开头,且后面要加冒号,如上面的示例所示。如果未提供节标题,则假定使用 CACHE: 标题。下面的示例演示了一个简单的清单。
     
    CACHE MANIFEST
    script/library.js
    css/stylesheet.css
    images/figure1.png
    
    
    
    此外,清单文件:
    • 必须使用 8 位 Unicode 转换格式 (UTF-8) 字符编码。
    • 必须接受文本/缓存清单 MIME 类型。
    • 必须以“CACHE MANIFEST”行开始。
    • 可以包含注释,但前面必须加英镑标记 (#)。

    有关更多信息,请参阅缓存清单语法

    声明清单

    要将清单与网页关联,需将清单文件的名称分配给 html 元素的 manifest 属性,如下面得示例所示。

     
    <!doctype html>
    <html manifest="appcache.manifest">
     <head>
      <title>A Web Page</title>
      <script src="library.js"></script>
      <link rel="stylesheet" href="stylesheet.css">
     </head>
     <body onload="doSomething();">
      <p>Results go here: <span id="results">Unknown</span></p>
     </body>
    </html>
    
    
    

    在此示例中,网页将“appcache.manifest”作为清单文件进行声明。清单声明可像任何其他文件引用一样被解释。由于此示例使用相对文件名称,所以该清单被假定位于与网页自身相同的目录中。

    注意  清单中的文件引用被解释为清单文件的相对位置,而不是声明它的网页。

    对清单来说,没有必要包含声明该清单的网页名称;声明清单的网页会被自动缓存。

    这样几步就可以完成对离线存储的支持。接下来要思考的,是如何更新离线存储?

    当用户本地再次联网的时候,本地的离线存储资源需要检查是否需要更新,这个更新过程,也是通过manifest的更新来控制的,更新了manifest文件,浏览器会自动的重新下载新的manifest文件并在下一次刷新页面的时候进行资源文件的重新请求(第三次刷新替换本地缓存为最新缓存),而且这个请求是全局性的,也就是所有在manifest缓存列表中的文件都会被请求一次,而不是单独请求某个特定修改过的资源文件,因为manifest是不知道哪个文件被修改过了的。

    对于全局更新的担心是不必要的,因为对于没有更新过的资源文件,请求依旧是304响应,只有真正更新过的资源文件才是200.

    所以控制离线存储的更新,需要2个步骤,一是更新资源文件,二是更新manifest文件,特别的,更新manifest文件是不需要修改什么特定内容的,只要是这个文件随意一处被修改,那么浏览器就会感知,对于我们的资源文件通常名称是固定的,比如xxx.css,更新内容不会带有文件名更新的情况下,需要更新manifest文件怎么操作呢?一个比较好的方式是更新任意一处# 开头的注释即可,其目的只是告诉浏览器这个manifest文件被更新过。

    以上的这些内容,其更新操作都是浏览器自动完成的。同样的,W3C定义了离线存储的API规范:http://www.whatwg.org/specs/web-apps/current-work/#applicationcache

    ApplicationCache 对象

    Internet Explorer 10 还支持 ApplicationCache 对象,该对象提供可用来管理应用程序缓存的属性。此外,你还可以定义显示缓存进程进度的事件处理程序。

    使用 window 对象的 applicationCache 属性(或 worker 对象)以访问 ApplicationCache 对象,如下面的示例所示。

     
    var sCacheStatus = "Not supported";
    if (window.applicationCache) 
    {
       var oAppCache = window.applicationCache;
       switch ( oAppCache.status ) 
       {
          case oAppCache.UNCACHED : 
             sCacheStatus = "Not cached"; 
             break;
          case oAppCache.IDLE : 
             sCacheStatus = "Idle"; 
             break;
          case oAppCache.CHECKING : 
             sCacheStatus = "Checking"; 
             break;
          case oAppCache.DOWNLOADING : 
             sCacheStatus = "Downloading"; 
             break;
          case oAppCache.UPDATEREADY : 
             sCacheStatus = "Update ready"; 
             break;
          case oAppCache.OBSOLETE : 
             sCacheStatus = "Obsolete"; 
             break;
          default : 
            sCacheStatus = "Unexpected Status ( " + 
                           oAppCache.status.toString() + ")";
            break;
       }
    }
    return sCacheStatus;
    
    
    

    此示例使用 status 属性为当前通过网页所加载的文档确定应用程序缓存的状态。

    ApplicationCache 对象支持两种控制缓存的方法。update 方法启动对更新的异步检查,类似于在首次加载网页时执行的操作。在重新加载该网页或调用 swapCache 方法之前,将会一直使用任何现有的缓存。要开始使用已更新的缓存,需要重新加载网页(手动或以编程方式)或调用 swapCache 方法。当重新加载网页时,因为缓存已更新,所以在重新加载或刷新网页之前不需要调用 swapCache 方法。

    提供了如下API:

    1
    2
    3
    4
    5
    6
    // 更新,一般来说更新下载是通过用户代理(如浏览器)自动完成的,但是这个方法适用于一些长期打开的页面,比如邮件系统,可能这个页面是长期打开的,而不会有刷新动作,所以这个就比较适合做自动更新下载 
    void update(); 
    // 取消  
    void abort(); 
    // 替换缓存内容 ,对于manifest文件的改变,通常是下一次的刷新才会触发下载更新,第三次刷新才会切换使用新的缓存文件,通过这个方法,可以强制将缓存替换 
    void swapCache(); 

      

    注意  在重新加载网页(由用户手动或以编程方式使用 window.location 对象的 reload 方法)之前,网页不会使用已更新的缓存。

    ApplicationCache 对象支持以下事件:

    • 当清单已缓存时,将触发 cached 事件。
    • 当检查是否有更新时,将触发 checking 事件。
    • 当下载清单资源时,将触发 downloading 事件。
    • 在下载清单资源期间,将触发 progress 事件。
    • 当出现问题(如 HTML 404 或 410 响应代码)时,将触发 error 事件。当无法下载清单文件时,也会触发该事件。
    • 当缓存有更新的版本可用时,将触发 updateready 事件。
    • 请求更新时将会触发 noupdate 事件,但清单不会发生更改。
    • 当现有的缓存被标记为已过时时,将会触发 obsolete 事件。

    下面的示例演示如何为这些事件注册事件处理程序。

     
    if (window.applicationCache) {
      var appCache = window.applicationCache;
       appCache.addEventListener('error', appCacheError, false);
       appCache.addEventListener('checking', checkingEvent, false);
       appCache.addEventListener('noupdate', noUpdateEvent, false);
       appCache.addEventListener('downloading', downloadingEvent, false);
       appCache.addEventListener('progress', progressEvent, false);
       appCache.addEventListener('updateready', updateReadyEvent, false);
       appCache.addEventListener('cached', cachedEvent, false);
    }

    最后说一个对于manifest比较特别的地方:对于某个文件a.htm,其中有 <html manifest='a.manifest'> ,那么离线存储中,会自动将a.htm加入到列表中,这意味着a.htm的再次刷新将从本地缓存中获取,这样的机制从官方得到的答复是“特别的设计”,而对我们来说,这种强加的特性在后续的开发过程中会有不少问题,比如:

    1、如何计算PV UV,由于当前页面被强制加入manifest,那么PV 和UV的统计,成了一个难题,因为请求不再是发送到服务器;

    2、对于某个使用manifest的文件,其带有的参数可能是随机性的统计参数,如sid=123sss, sid=234fff ,尤其是比如商品详情的id字段等,这样每个页面都自动加入到manifest中,将会带来很大的存储开销,而且是毫无意义的;

    所以伴随而来的,是如何在现有的体系架构下进行数据统计的难题,一个常规的方案是进入离线存储页面后自动发出ajax请求,以告知服务器统计PV UV;


    对于第二个问题,可能就比较棘手,但是将GET请求的方式改成POST的方式确实是个解决问题的方案。

     

     

    慎用manifest

    Html5已经被提过很久了,各种新属性出来,人们也都欢呼了一把,今天要讨论的是一个html5的新属性——manifest,可能有很多人已经用过该方法来优化自己的网站了。今天来分享下我们在使用manifest的时候遇到的问题。

    1.概念

    该特性为HTML5的新特性,能够让web程序指定可以缓存在本地的资源,以及缓存策略,使得程序能够在离线时仍然能够运行,也可以使程序在线时也可以从本地加载资源而不用重新从网络加载。

    2.基本写法

    要用manifest,就要了解其基本写法

    通过在页面的html标签中中引入:

    <html manifest=”/static/index/innovation/cache.manifest”>

    Cache.manifest的文件写法:

    1. CACHE MANIFEST
    2. # v2012 1203v3
    3. http://m.baidu.com/static/index/i.gif
    4. http://m.baidu.com/static/hb/hot.gif
    5. http://m.baidu.com/static/index/logo_index2.png
    6. NETWORK:
    7. *
    8. http://m.baidu.com/su
    9. http://loc.map.baidu.com/wloc
    10. http://m.baidu.com/static/tj.gif

    第一行一定要写上CACHE MANIFEST,然后注释写后面,以后通过修改注释,来实现manifest的更新。

    A.路径写法不支持带端口,所以最好用相对路径吧,要不然线下测试机上开发,有端口不好调试。

    B.*号通配符有时候好像并不好使。如果在某些设备上出现问题,可以排查一下是不是这个导致的。

    C.#以后的内容虽然是注释,但是不能放到第一行,第一行必须是如上的大写字母。

    1.离线后的页面

    页面离线后,好处显而易见,页面可以秒开了(启动速度快)。你会感觉页面加载的速度快了很多。但是也有一些问题。

    A.页面都离线了,如何更新?首先,你可以修改下manifest文件来更新这个页面,这个我就不介绍了,但是作为文章内容页面离线以后,就会存储在本地了,如果你是一篇文章的话,那么这个文章的内容页就被存下来了,你如果以相同的url去访问,不管你文章里面的数据更新没有,这个离线下来的页面都不会更新了(除非你更新manifest文件)。所以,所有的动态数据,你都得用ajax方式去获取,就像客户端一样,离线的页面应该是一个没有数据的空壳,然后通过ajax去拉去数据填补这个空壳。然后要注意的是,ajax的请求地址,要写到manifest的network中,要不然,你可以试试。

    B.页面的参数如何携带?,还是刚才那个问题,文章页的壳如果缓存了,怎么样传数据过来标识这个特点页面呢?比如m.baidu.com/app?a=1&b=2,通常我们用一些参数来标记这个页面,通过参数来渲染页面内容,但是manifest对于上面的方式,会认为不同的参数表示不同的页面。如果你吧内容页做成一个无数据的空壳,这种传参的方式显然不行,好在不一样的hash页面,manifest会认为是同一个页面,比如m.baidu.com/app#detail-111111与m.baidu.com/app#detail-222222会认为和m.baidu.com/app是同一个缓存页面。这样我们就可以通过hash传值了,当然,你也可以通过其它方式传值,比如写入cookie,写入localstorage方式等等。

    4.离线页面的更新

    网站更新了,如果更新用户本地的离线页面呢?与很多文章中说的一样,先上线你的文件,然后修改一下页面中引入的cache.manifest文件即可,比如修改下注释,修改后,如果再访问页面,就会先去校验manifest时候有更新,如有更新,再次刷新页面的时候,页面就会更新了。

    A.长尾问题,就像前面说到的一样,如果你的manifest文件更新了,你访问页面,需要刷新一次,更新的页面才能load进来,那么这样就有一个问题,如果你的后端数据,就是给js ajax接口的数据变化了,你对应的js也修改了。那么你修改manifest上线的时候,第一次开页面,页面就会bug了。再刷一次页面,就好了。那么,这个第一次访问的bug,是我们不想看到的。而且你不能知道用户什么时候第二次再来访问你的页面,所以你的页面一旦使用manifest离线,就像客户端一样,这样就出现了长尾问题。还好,manifest有一些js接口,可以来判断,load更新文件。

    B.刷新页面

    有了js的api,一切都好办了,我们可以干很多事情了。现在你可以用js来判断页面的状态了,可能你已经想到,判断状态后,可以刷新一下页面,那么,就算数据发生了修改,这种处理方式也是ok的,没错,这样确实解决了问题。你也可以在设置了manifest的页面中加入这些方法,然后修改下文件,看看会触发哪些事件,刷新页面的唯一问题就是页面会闪动一下,这点体验当然是不好的。因此,我们想到了loading页面。

    C.Loading页面

    制作一个loading页面,用来检测manifest文件有没有更新如果发现有更新,则更新资源,然后再跳到真实的首页。这个实现起来就比较容易了,在loading页面你可以加一些效果,比如加载效果等等,给用户一个预知。让我们看看manifest修改过和没有修改过各种状态的差别。保证你的manifest文件存在有效,如下:

    1. CACHE MANIFEST
    2. #test11123aaadfsaasdadffdsfaaaffd
    3. http://m.baidu.com/static/js/zepto-event-ajax.js
    4. testb.html
    5. NETWORK:
    6. *

    Html文件如下:

    1. <!DOCTYPE html>
    2. <html manifest=”notes.manifest”>
    3. <head>
    4.     <title>离线存储demo</title>
    5.     <meta http-equiv=”Content-Type” content=”text/html;charset=UTF-8″>
    6. </head>
    7. <body>
    8. Hello world!
    9. <script type=”text/javascript” src=”http://m.baidu.com/static/js/zepto-event-ajax.js”></script>
    10. <script type=”text/javascript”>
    11.    var appCache=window.applicationCache;
    12.    console.log(appCache.status);
    13.    appCache.addEventListener(“obsolete”,function(){
    14.        console.log(“Obsolete,status:”+appCache.status);
    15.    },false);
    16.    appCache.addEventListener(“cached”,function(){
    17.        console.log(“chache,status:”+appCache.status);
    18.    },false);
    19.    appCache.addEventListener(“checking”,function(){
    20.        console.log(“checking,status:”+appCache.status);
    21.    },false);
    22.    appCache.addEventListener(“downloading”,function(){
    23.        console.log(“downloading,status:”+appCache.status);
    24.    },false);
    25.    appCache.addEventListener(‘noupdate’, function(){
    26.        console.log(‘Noupdate,Status:’ + appCache.status);
    27.    }, false);
    28.    appCache.addEventListener(‘updateready’, function(){
    29.        console.log(‘updateready,Status:’ + appCache.status);
    30.    }, false);
    31.     appCache.addEventListener(“error”,function(){
    32.        console.log(“error”);
    33.     },false);
    34. </script>
    35. </body>
    36. </html>

    如果页面没有发生变化:那么以上代码会输出:

    111

    如果对应的manifest有修改,需要更新文件,那么上面的代码会输出:

    222

    如果更新了manifest,会触发downloading和updateready事件。你可以根据这两个事件来处理一些逻辑了,比如在downloading给用户一些提示,正在加载,updateready就跳转到真正的页面,那么这个时候,页面就已经加载好了,就不用再刷新才生效页面了。比如:

    1. appCache.addEventListener(‘updateready’, function(){
    2.        console.log(‘updateready,Status:’ + appCache.status);
    3.        location.href=”testb.html”;
    4.    }, false);

    该页面在loading页面上,其实主要的内容页面在testb.html上。

    D.Loading页面的问题

    这时,你可能认为,这个不错,解决了所有问题。效果也ok,也没有第一次加载页面可能出错的状况。如果对于一个单页面来说,这样确实没问题了,但是如果你是一个完整的站,问题又来了,你不可能给网站每个页面都加loading,就算每个模版页都用一个loading,如果用户进入了你某个页面,如果把你某个页面存到了桌面快捷方式。那么下次启动,可能还是会挂,必须刷一次页面才能好,所以,你可能想到,整个页面都要通过js去build了……,然后又要保持build页面的js要在页面渲染前发生更改,那么,你可以在每个壳模版页面判断manifest的逻辑,然后动态载入的js,通过动态载入的jsbuild整个html页面,那么这样就木有bug了,不过这种处理方式确实恶心了,如果你的模版页的html壳更新不多,仅仅会更新里面的js逻辑的话,那你可以在改html壳中动态载入js即可,也可以直接刷一下页面来解决。

    但是这毕竟不是一个app,如果你的站点突然接到需求要更新得很频繁,比如一天要更新一次,那么,这个manifest离线的意义就仅仅在于第二次载入的时候可以秒开。除此之外,我也想不到其他好处。然后用户如果一天刚好访问一次的话。这个状态好像还没有不用manifest离线的状态好。那么,我需要把manifest下线……

    5.Manifest的回滚与下线

    其实,回滚与下线表现上来说,差不多,当然,我这里所提到的回滚,是第一次上线manifest的回滚,一般如果你上线manifest出现了问题,比如造成的线上严重的bug,那么,这是时候,一般公司就会有回滚机制,用以前的代码来覆盖现在的代码。但是你一旦上线了manifest离线存储,回滚就没那么容易了,就是从有manifest的状态到无manifest的状态,其实这点和manifest下线一样。很多人都知道每次更新了静态文件,更新一下manifest多刷两次页面,页面就更新了。如果manifest文件没有了呢,我们还是拿上面的那个页面做测试。

    第一次刷新会这样:

    333

    第二次再刷新是这样:

    444

    删除manifest以后,浏览器校验manifest文件就会返回一个404,那么这时,manifest就会失效,再刷页面,页面就会又开始自动更新了。也不会缓存了,是不是很兴奋。这不是很简单么?删掉manifest文件就能回滚了,就能下线了。不过现在好像404不流行了,如果你删掉了服务器上某个静态文件,服务器会302跳转到一个错误页,那么这时候,你在怎么刷新页面,页面都不会在更新了。悲剧了……用户手机浏览器中的页面再也更新不了了。此时你只能再次更新manifest让其强制更新。可能你还想到,我删除了manifest文件,把html中manifest引用也删除不就行了么。这个也是不行的。浏览器缓存了该地址的离线信息,除非你清除浏览器的缓存,否则这招无效。

    555

    所以,如果可以,就可以利用manifest文件404把manifest下掉。

    如果这招行不通呢?就是没法返回404。那么你上线manifest的时候就要注意了。前文中提到了loading,那么你得再loading中处理好load manifest文件失效的情况。其实处理方式也很简单,就是跳到本来该跳的页面,然后带上一个参数(前面已经说过,带参数与带hash的区别)也就可以很轻松解决这个问题了,但是需要注意一定,找不到manifest文件,第一次刷新和第二次刷新所触发的事情不一样,第一次会触发obsolete事件,第二次再刷新,就触发error事件,不过这样也好办,我们在这两个事件中,将链接都跳到一个带参数的页面就好了(在error、obsolete中跳到页面带参数,在updateready、noupdate里面直接跳到页面)。所以,利用manifest的js api然后配合删除manifest文件(此时就可以不管页面是不是302或者404了),就可以下线掉manifest文件了,如果用户不清缓存,那段曾经被离线的代码就会起作用,会让用户永远页面带上参数。

    1.  appCache.addEventListener(“downloading”,function(){
    2.        console.log(“downloading,status:”+appCache.status);
    3.        console.log(“downing”);
    4.    },false);
    5.    appCache.addEventListener(‘noupdate’, function(){
    6.        console.log(‘Noupdate,Status:’ + appCache.status);
    7.        location.href=”testb.html”;
    8.    }, false);
    9.    appCache.addEventListener(‘updateready’, function(){
    10.        console.log(‘updateready,Status:’ + appCache.status);
    11.        location.href=”testb.html”;
    12.    }, false);
    13.     appCache.addEventListener(“error”,function(){
    14.        console.log(“error”);
    15.        location.href=”testb.html?a=1″;
    16. },false);
    17.  appCache.addEventListener(“obsolete”,function(){
    18.        console.log(“Obsolete,status:”+appCache.status);
    19.        location.href=”testb.html?a=2″;
    20.    },false);

    5.适用场景小结

    上线,下线,回滚,长尾等等问题都考虑过了,现在我们应该可以正确使用manifest了,现在来讨论一个问题,什么样的页面适合使用manifest呢。关于这点,目前我们还没有具体的数据证明,但是通过manifest离线的页面,第二次访问时启动速度快了很多,而且,如果处理得当,还是有一些收益的,每次更新manifest文件以后,manifest中未修改的文件是不会被重新下载的,会返回304,而且页面中的其他静态文件,如css和img等,也能享受到传统缓存的方式。

    如果你的应用是一个html5的游戏、或者是一个html的app应用,明显,采用manifest会收到很好的效果,第二次及以后加载速度都非常快,而且你的应用如果不依赖网络的话,就算用户断网了,也可以正常使用,如html5游戏,你完全可以离线来带来更好的用户体验。然而对于一些传统的站点,也想运用一下manifest新特性,如果你的站点每天都有上线更新,每天都有上线,从manifest的机制上来说,这个是不怎么适合用manifest离线的,静态资源本来就可以自己缓存,引入manifest还会带来一些维护成本已经上面提到的一些问题。但是你的站点开发完成以后,更新频率比较低,做完一版以后很长时间内不做升级(这点类似客户端),那么,你的站点就很适合用manifest离线来优化用户体验,manifest虽然带来了一些好处,但是也有很多问题。

    另外,在使用过程中,某些情况下,会出现浏览器无法更新manifest的情况:目前chrome 23.0.1271.97已经找到稳定复现方法。其中:
    1.问题:chrome会无法更新静态文件(这些文件包括离线主页,静态文件。)

    2.原因:bug情况下,修改了manifest文件以后,通过抓包,发现某些静态文件不会发生校验。Manifest的机制是如果manifest文件发生过修改,是会校验资源列表中的静态文件的,如果文件发生过修改则会重新download这个文件,如果资源没有发生修改,服务器会返回304 。但是实际情况是,浏览器不会校验一些文件了。导致文件无法更新。此时,通过chrome://appcache-internals/方式找到浏览器的manifest缓存,然后删除,然后直接访问无法校验的静态文件。发现仍然可以访问,并且是未修改过的。后来发现是浏览器传统缓存缓存了这个文件。通过chrome://cache/可以查看到缓存的文件并没有发生修改,造成无法更新。

    3.解决方法:

    1.把manifest离线资源与主文件跨域存放,但这个也会导致一个问题,就是会导致dns解析变多。

    2.在将manifest的列表中的静态文件的文件头中加上no-store(加no-cache不行)。这个也会导致一个问题,就是如果一个库文件,如zepto.js在m.baidu.com/static 下,manifest用到了,然后其他产品线也用到了,那么其他没用manifest的产品线就享受不了传统缓存了。

    3.将manifest静态资源列表中的文件加上时间戳参数,每次修改静态文件,都主动修改下manifest中静态文件的时间戳。可能有人会认为修改一次时间戳,manifest就会本地多存一次文件,其实不是的,发现列表中如果没有该文件了,浏览器会很高级,会自动删除这个文件。不好的地方可能就是上线要多改一处地方。略微增加维护成本。

    另外,有同学还碰到过浏览器将manifest文件缓存的情况,后来将这个文件的过期时间设置成了1s。

    一路坑踩过来,还不知道有多少,而且一些bug出现后很难复现,总之慎用吧。

    正确应用manifest来优化你的站点吧。

  • 相关阅读:
    Java设计模式(Design Patterns)
    P2213 [USACO14MAR]懒惰的牛The Lazy Cow_Sliver
    P3120 [USACO15FEB]牛跳房子(金)Cow Hopscotch (Gold)
    P4818 [USACO15DEC]Bessie's Dream 贝西的梦
    P3667 [USACO17OPEN]Bovine Genomics
    P4379 [USACO18OPEN]Lemonade Line
    P4378 [USACO18OPEN]Out of Sorts S
    P4089 [USACO17DEC]The Bovine Shuffle
    P4269 [USACO18FEB]Snow Boots G
    P4086 [USACO17DEC]My Cow Ate My Homework
  • 原文地址:https://www.cnblogs.com/sunshq/p/4227905.html
Copyright © 2011-2022 走看看