HTML5 存储

  1. 什么是 HTML5 存储?
  2. 为什么要使用 HTML5 存储?
  3. 关于 HTML5 存储您应该了解的细节
  4. GWT 中的 HTML5 存储支持
  5. 如何在您的 Web 应用中使用 HTML5 存储

从 GWT 2.3 开始,GWT SDK 提供了对 HTML5 客户端存储(也称为“Web 存储”)的支持。使用 GWT 库的存储功能使您的 Web 应用能够在运行在支持 HTML5 的浏览器中时利用这些存储功能。

什么是 HTML5 存储?

HTML5(Web)存储规范是一种标准化的方式,用于提供更大容量的客户端存储,并更合适地“划分”会话存储和本地持久存储。HTML5 规范还规定了由感兴趣的监听器生成和处理的存储事件。通过查看非 HTML5 世界中的客户端存储,可以最好地看到 HTML5 存储提供的这些功能的全部影响。

在没有 HTML5 的情况下,Web 应用的客户端存储仅限于 cookie 提供的微量存储(每个 cookie 4KB,每个域 20 个 cookie),除非使用专有存储方案,例如 Flash 本地共享对象或 Google Gears。如果使用 cookie,它们同时提供会话存储和本地持久存储,并且所有浏览器窗口和选项卡都可以访问它们。域 cookie 会随对该域的每次请求发送,这会消耗带宽。处理 cookie 的“机制”也比较繁琐。

相比之下,HTML5 存储提供了更大的初始本地存储(每个域 5MB),无限的会话存储(仅受系统资源限制),并成功地将本地存储和会话存储进行了划分,因此只有要持久化的数据才会持久化到本地存储中,而要保持瞬态的数据将保持瞬态。此外,会话存储只能从其起源的选项卡或窗口访问;它不会在所有浏览器窗口和选项卡之间共享。访问会话存储和本地存储非常简单,只需对键值字符串进行简单的读写操作。最后,本地存储和会话存储仅在客户端;它们不会随请求发送。

为什么要使用 HTML5 存储?

使用 HTML5 本地存储,可以将更大容量的数据(最初是每个应用程序每个浏览器 5MB)持久地缓存在客户端,这为服务器下载提供了一种替代方案。如果 Web 应用使用这种本地存储,它可以实现更好的性能并提供更好的用户体验。例如,您的 Web 应用可以使用本地存储缓存来自 RPC 调用的数据,以实现更快的启动时间和更具响应性的用户界面。其他用途包括在本地保存应用程序状态,以便在用户重新进入应用程序时更快地恢复,以及在网络中断时保存用户的工作,等等。

注意:5MB 的最大值仅适用于本地存储,不适用于会话存储,会话存储仅受系统内存限制。

以下是本地存储的一些好处和用途的简短列表

  • 减少网络流量
  • 显著加快显示时间
  • 缓存来自 RPC 调用的数据
  • 在启动时加载缓存数据(更快的启动速度)
  • 保存临时状态
  • 在重新进入应用时恢复状态
  • 防止网络断开连接导致工作丢失

注意:与 cookie 不同,存储中的项目不会随请求一起发送,这有助于减少网络流量。

关于 HTML5 存储您应该了解的细节

要使用 HTML5 存储功能,您需要了解本地存储和会话存储的生命周期(持久性)、它们的范围(哪些窗口和选项卡可以访问存储)以及哪些选项卡和窗口可以监听存储事件。

LocalStorage 和 SessionStorage

HTML5 Web 存储定义了两种类型的键值存储类型:sessionStorage 和 localStorage。主要的行为差异是值持续的时间以及它们的共享方式。下表显示了两种存储类型的区别。

存储类型 最大尺寸 持久性 对其他窗口/选项卡的可用性 支持的数据类型
LocalStorage 每个应用每个浏览器 5MB。根据 HTML5 规范,用户在需要时可以增加此限制;但是,只有少数浏览器支持此功能 保存在磁盘上,直到用户删除(删除缓存)或应用删除 在运行相同 Web 应用的同一个浏览器的所有窗口和选项卡之间共享 仅字符串,作为键值对
SessionStorage 仅受系统内存限制 仅在创建它的窗口或选项卡存在时才存在 只能在创建它的窗口或选项卡内访问 仅字符串,作为键值对

浏览器如何共享本地存储

每个 Web 应用一个 LocalStorage,最大尺寸为 5MB,可供给定浏览器使用,并由该浏览器的所有窗口和选项卡共享。例如,假设您在客户端的 Chrome 浏览器中运行 MyWebApp。如果您在多个选项卡和窗口中运行 MyWebApp,它们都共享相同的 LocalStorage 数据,但最大限制为 5MB。如果您随后在另一个浏览器(例如 Firefox)中打开该应用程序,则新浏览器将获得自己的 LocalStorage 来与它的所有选项卡和窗口共享。下图显示了这一点

GWT Local Storage

本地存储是字符串存储

HTML5 本地存储以字符串形式将数据保存为键值对。如果要保存的数据不是字符串数据,您需要在使用 LocalStorage 时负责将其转换为字符串和从字符串转换回来。对于与 GWT RequestFactory 一起使用的代理类,可以使用 RequestFactory#getSerializer() 进行字符串序列化。对于非代理对象,可以使用 JSON stringify 和 parse

注意:就像 cookie 一样,LocalStorage 和 sessionStorage 可以使用浏览器工具(如 Chrome 中的开发者工具、Safari 中的 Web 检查器等)进行检查。这些工具允许用户删除存储值,并查看正在访问的网站记录了哪些值。

LocalStorage 不是安全存储

HTML5 本地存储以未加密的字符串形式将数据保存在常规浏览器缓存中。它不是安全存储。它不应用于敏感数据,例如社会安全号码、信用卡号码、登录凭据等。

存储事件如何工作

当数据添加到 LocalStorage 或 SessionStorage 中、修改或从中删除时,将在当前浏览器选项卡或窗口中触发一个 StorageEvent。该 Storage 事件包含发生事件的存储对象、该存储适用的文档的 URL 以及已更改的键的旧值和新值。任何为该事件注册的监听器都可以处理它。

注意:尽管 HTML5 规范要求在同一个浏览器的所有选项卡或同一个浏览器的所有窗口中触发存储事件,但目前只有少数浏览器实现了这一点。

GWT 中的 HTML5 存储支持

GWT 对 HTML5 存储功能的支持包括以下内容

  • com.google.gwt.storage.client.Storage(必需导入)
  • LocalStorage(本地存储)
  • SessionStorage(会话存储)
  • StorageEvent(由会话或本地存储更改生成的事件)
  • StorageEvent.Handler(存储事件处理程序接口)
  • StorageMap(将存储对象公开为标准 Map)

这些的语法细节可以在 存储功能 javadoc 中找到。

如何在您的 Web 应用中使用 HTML5 存储?

您可以通过调用 Storage.getLocalStorageIfSupported() 或 Storage.getSessionStorageIfSupported() 来获取存储对象,具体取决于要访问的存储类型。

因为您的 Web 应用可能会从不支持 HTML5 的浏览器中访问,所以您应该始终在访问任何 HTML5 存储功能之前进行检查。

如果支持存储功能,您将获得存储对象,然后根据需要向其写入数据或从中读取数据。如果您想从存储中删除一个键值对,您可以这样做,或者您可以清除存储对象中的所有数据。

  1. 检查浏览器支持
  2. 为您的浏览器获取存储对象
  3. 从存储中读取数据
  4. 向存储中写入数据
  5. 从存储中删除数据
  6. 处理存储事件

检查浏览器支持

GWT 提供了一种简单的方法来确定浏览器是否支持 HTML5 存储 - 当您获取存储对象时会进行内置检查。您可以使用 Storage.getLocalStorageIfSupported() 或 Storage.getSessionStorageIfSupported(),具体取决于您要使用的存储类型。如果支持该功能,则返回存储对象;如果不支持,则返回 null。

import com.google.gwt.storage.client.Storage;
  private Storage stockStore = null;
  stockStore = Storage.getLocalStorageIfSupported();

获取存储对象

如果浏览器支持 HTML5 存储,则 Storage.getLocalStorageIfSupported 方法会在存储对象尚不存在的情况下创建该对象,并返回该对象。如果存储对象已存在,则它只是返回已存在的对象。如果浏览器不支持 HTML5 存储,则返回 null。Storage.getSessionStorageIfSupported() 方法的工作方式相同。

获取存储对象和检查浏览器对 HTML5 存储的支持是在同一时间完成的,因此执行此操作的代码片段应该看起来很熟悉

import com.google.gwt.storage.client.Storage;
  private Storage stockStore = null;
  stockStore = Storage.getLocalStorageIfSupported();

从存储中读取数据

数据以键值字符串对的形式存储,因此您需要使用键来获取数据。您要么必须知道键是什么,要么需要使用索引遍历存储以获取键。为键选择良好的命名约定会有所帮助,使用 StorageMap 也很有用。如果需要将数据从字符串转换为其他类型,则需要负责执行转换。

以下代码片段显示了遍历存储内容的过程,其中存储中的每个项目都会写入 FlexTable 中的单独行。为了简单起见,这假设所有存储仅用于该 FlexTable。

import com.google.gwt.storage.client.Storage;

private FlexTable stocksFlexTable = new FlexTable();
private Storage stockstore = null;

stockStore = Storage.getLocalStorageIfSupported();
if (stockStore != null){
  for (int i = 0; i < stockStore.getLength(); i++){
    String key = stockStore.key(i);
    stocksFlexTable.setText(i+1, 0, stockStore.getItem(key));
    stocksFlexTable.setWidget(i+1, 2, new Label());
  }
}

使用 StorageMap 快速检查特定键或值

如果您想快速检查特定键或特定值是否出现在存储中,可以使用 StorageMap 对象,方法是将存储提供给 StorageMap 构造函数,然后使用其 containsValue()containsKey() 方法。

在以下代码片段中,我们使用 StorageMap 来查看某个值是否已存在于存储中,如果尚未存储,则将数据写入存储。

stockStore = Storage.getLocalStorageIfSupported();
if (stockStore != null) {
  stockMap = new StorageMap(stockStore);
  if (stockMap.containsValue(symbol)!= true){
    int numStocks = stockStore.getLength();
    stockStore.setItem("Stock."+numStocks, symbol);
}

将数据写入存储

要写入数据,您需要提供一个键名和要保存的字符串值。您只能写入字符串数据,因此您需要从其他数据类型或对象进行任何转换。(如果该对象是在 GWT RequestFactory 中使用的代理,则可以使用 RequestFactory#getSerializer() 来执行字符串序列化。对于非代理对象,可以使用 JSON 序列化和解析。)

谨慎使用命名约定有助于处理存储数据。例如,在名为 MyWebApp 的 Web 应用程序中,与名为 Stock 的 UI 表中行相关联的键值数据可以具有以 MyWebApp.Stock 为前缀的键名。

在以下代码片段中,这是“添加”按钮单击处理程序的一部分,从文本框中读取文本值并保存,并将键名与前缀和存储中当前项目的数量连接起来。

import com.google.gwt.storage.client.Storage;

final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
stockStore = Storage.getLocalStorageIfSupported();
if (stockStore != null) {
  int numStocks = stockStore.getLength();
  stockStore.setItem("Stock."+numStocks, symbol);
}

从存储中删除数据

您可以从存储中删除单个键值对数据,也可以一次性删除所有数据。

删除特定的键值对

如果您想删除特定数据,并且知道键名,只需将键名提供给 removeItem 方法,如下所示:myStorage.removeItem(myKey);

如果您不知道键,或者需要处理键列表,可以使用 key 方法遍历存储,如下所示:myStorage.key(myIndexValue);

清除整个存储

要清除 Web 应用程序使用的存储,请调用 clear() 方法,如下所示:myStorage.clear();

以下示例代码片段提供了一种将此方法与 UI 集成的方法示例,在本例中,这是一个从存储中显示项目的 FlexTable。用户通过单击“清除全部”按钮来清除 UI 和存储。在按钮单击处理程序中,我们仅使用存储中项目的数量来遍历并从 UI 中删除行,完成后,我们将删除所有存储数据。(为了保持简单,我们只使用存储来填充 FlexTable。)

import com.google.gwt.storage.client.Storage;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Widget;

// Listen for mouse events on the Clear all button.
clearAllButton.addClickHandler(new ClickHandler() {
  public void onClick(ClickEvent event) {
  // note that in general, events can have sources that are not Widgets.
  Widget sender = (Widget) event.getSource();
  //If HTML5 storage is supported, clear all rows from the FlexTable UI,
  //then clear storage
  if (sender == clearAllButton) {
    stockStore = Storage.getLocalStorageIfSupported();
    if (stockStore !=null) {
      for (int ix =0; ix < stockStore.getLength(); ix++) {
        stocksFlexTable.removeRow(1);
      }

      stockStore.clear();}
    }
  } // if sender is the clear all button
});

处理存储事件

您可以使用存储对象注册存储事件处理程序,这些处理程序会在数据写入或从存储中删除时针对当前窗口或选项卡调用。尽管 HTML5 规范指出存储事件会在浏览器的所有窗口和选项卡中触发,但不能假设这一点,因为很少有浏览器实现这一点。请注意,如果存储被清除,则事件不会包含有关已删除键值对的任何信息。

存储事件处理程序会获取一个存储事件对象,该对象包含各种有用的信息,例如旧值和新值(在更新现有键值对的情况下)。可以从 StorageEvent 对象中获取以下内容

方法 描述
getKey 返回正在更改的键。
getNewValue 返回更改后的键值,如果未更改或它是 Storage.clear() 操作的结果,则返回 null。
getOldValue 返回更改前的键值,如果未更改或它是 Storage.clear() 操作的结果,则返回 null。
getStorageArea 返回发生事件的 SessionStorage 或 LocalStorage 对象。
getURL 发生更改的文档的地址。

以下代码片段显示了与存储注册的示例事件处理程序,其中来自传入事件的更改将显示在 UI 标签中。

import com.google.gwt.storage.client.Storage;
import com.google.gwt.storage.client.StorageEvent;
private Storage stockstore = null;
stockStore = Storage.getLocalStorageIfSupported();
if (stockStore != null) {
  stockStore.addStorageEventHandler(new StorageEvent.Handler() {
  public void onStorageChange(StorageEvent event) {
    lastStockLabel.setText("Last Update: "+event.getNewValue() +": " +event.getOldValue() +": " +event.getUrl());
  }
});