PWA 程序开发实践



对本文有任何问题,可加我的个人微信询问:kymjs666

题外话:写给所有移动端开发的同学:PWA(Progressive Web Apps) 一定是将来的移动开发趋势,且学且珍惜。
手机端可在我公众号【技术实验室】的历史推送文章查看。

已经公开章节:
第一篇 · Service Worker:让网页无网络也能访问
第二篇 · PWA 程序开发实践
GitHub 演示项目 · PWAblog

介绍

这里再介绍一下 Progressive Web Apps 是结合了 web 和 原生应用中某些功能的一种体验(本质上还是 web 应用)。但是作为一个 web 应用,它可以 断网使用推送消息发送通知从桌面启动,当然还包括 Web 应用的优势:免安装快速开发依赖浏览器跨平台(支持包括Edge在内的各种主流PC/手机浏览器)

fetch 拦截请求

之前讲述了 PWA 最重要的组件:Service Worker,没有看过的可以先看看:https://kymjs.com/code/2017/02/04/01/。今天继续来看他的一些高级属性。
今天要说的高级属性已经写在了我的Service Worker里,可查看:开源实验室防盗链fetch事件。


```javascript
self.addEventListener('fetch', function(e) {
  var allDataUrl = filesToCache;
  var requestIsDataApi = false;

  for (dataurl in allDataUrl){
    if (e.request.url.indexOf(dataurl) > -1 ) {
      requestIsDataApi = true;
      e.respondWith(
        caches.open(dataCacheName).then(function(cache) {
          return fetch(e.request).then(function(response){
            cache.put(e.request.url, response.clone());
            return response;
          });
        })
      );
      break;
    }
  }

  if (!requestIsDataApi){
    e.respondWith(
        caches.match(e.request).then(function(response) {
          return response || fetch(e.request);
        })
      );
  }
});

这里,我将所有请求的 URL 做了个判断,如果是缓存URL,直接从本地缓存中返回;反之如果是不属于缓存 URL,就拦截请求,使用fetch()发请求,将结果保存到缓存并返回。
可以拦截请求这一点让fetch事件可以玩出花来,下一篇我们继续讲 PWA 时再看它的黑科技。

应用程序外壳(App Shell)

如果你按照上一篇博客的讲述,自己动手实现了博客断网访问。在欣喜的同时一定也会发现,博客没法更新了。
当博客内容更改的时候,如果之前用户已经访问过这篇博客,他再次访问依旧是之前缓存过的内容,而不是新内容。
这就涉及到 PWA 的一个名词:应用程序外壳(App Shell)
一个 web 应用分为 应用程序外壳应用数据, 应用外壳的结构分为应用的核心基础组件和承载数据的 UI。所有的 UI 和基础组件都使用一个 service worker 缓存在本地,因此在后续的加载中 Progressive Web App 仅需要加载需要的数据,而不是加载所有的内容。
这就类似 Android 应用,下载安装的是外壳,只需要下载一次,接口API请求的数据是实时变化的。
因此,之前我们是把整个博客当成了APP Shell,除非版本变更,否则当然不会再发生变化。

设计 App Shell

还是以 开源实验室 来做例子,首先看看效果图。

gradle, android, kotlin, PWA

一个移动应用,应该是包括 ActionBar(StatusBar)ContentBottomBar(FooterBar)三部分构成。ActionBar 和 BottomBar 基本是固定的不会有大变化,这部分用 html 很容易实现,最主要的是 Content 部分,这部分内容是动态改变的,不包括在 App Shell 中,因此这里我们先不管他。

<!DOCTYPE html>
<html>
<head>
  <title>开源实验室-kymjs张涛</title>
  <link rel="stylesheet" type="text/css" href="styles/inline.css">  
</head>
<body>

  <header class="header">
    <h1 class="header_title">开源实验室</h1>
    <a href="https://kymjs.com/about"><button id="butAbout" class="headerButton" aria-label="about"></button></a>
  </header>

  <main class="main">
  </main>
 
  <footer class="footer">
   <button id="column1" class="button1">专栏</button>
   <button id="column2" class="button2 active">博客</button> 
   <button id="column3" class="button3">作品</button> 
  </footer>

  <script src="scripts/app.js" async></script>
</body>
</html>

APP Shell 已经完成了,接下来我们需要在 service-worker 中声明他是一个不需要多次下载的页面。

设计 Content

由于 Content 是变化的,所以我们只写一个模板,让 js 去根据数据改变这个模板内容。
先写好将会放在 Content 区域的 item 模板(相当于 Android 中 ListView 的 item 先创建一个模板)。

<div class="post-list-body post-area"> 
  <div class="list-item" hidden>
    <a href="http://kymjs.com/" id="blogLink">
      <div class="post-list-item">
        <font color="#aaaaaa" id="blogTime"></font>
    	 <h2><font color="#AE57A4" id="tag"></font>
    	 <font color="#333333" id="title"></font></h2>
        <p><font color="#666666" id="description"> description template </font></p>
      </div>
    </a>
  </div>
</div>

接着使用 AJAX 请求博客的内容数据,可以通过调用开源实验室的 OpenAPI 获取数据。

app.requestBlogList = function(){
	 //跨域请求会失败,本地调试时先使用伪数据
	 // var url = "/download.json";
    var url = "http://openapi.kymjs.com/oslab";
    
    xmlhttp.onreadystatechange=function(){
    if (xmlhttp.readyState == XMLHttpRequest.DONE && xmlhttp.status==200){
        var response = JSON.parse(xmlhttp.response);
        var itemList = response.item;
        app.addContent(itemList);        
      }
    };
    xmlhttp.open("GET", url);
    xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;"); 
    xmlhttp.send();
  }

拿到数据了以后,调用addContent(),去根据模板动态生成内容。代码就不贴了,具体可查看 github 仓库 PWAblog

配置 Service Worker

至此,整个应用就已经全部开发完成了,接下来我们只需要配置 service worker 让应用程序外壳生效就 OK 了。
回顾上一篇文章,首先在 js 中判断浏览器是否支持,如果支持就注册。
监听 install 事件,缓存 url。
监听 activate事件,移除过期缓存,保证应用更新版本后可以升级。
最后,通过fetch事件拦截网络请求做访问缓存逻辑处理。
这样一个简单的 PWA 程序就完成了。