创建连接的 Windows 应用商店应用
当前,人们身边与网络连接的设备的数量不断增加。最新的冰箱和洗衣机甚至都能与 Internet 和家庭网络连接。因此,最终用户期待自己的应用也能实现网络连接也就不足为奇。这些“连接的应用”将使用来自 Web 的最新内容(社交媒体、数字媒体、博客以及其他类型的内容)。开发连接的应用已经实现了常态化,但是处理诸如网络连接断开、按流量计费的网络成本,或性能问题等常见问题仍让开发人员觉得力不从心。Windows 8 让编写连接的应用变得空前简单。
在本篇博文中,我们将介绍一些有用的提示,这些提示有助于用户在使用您的 Windows 应用商店应用时获得快速而流畅、轻松、连接的体验:
- 为您的应用场景选择正确的 API
- 选择正确的网络功能
- 为按流量计费的网络调整应用行为
- 应对网络状态变化
- 缓存内容以提高流畅性
我们来详细看看各条提示。
选择正确的 API
如果您打算建造一座房屋,那么您需要适当的工具。您需要锤子来钉钉子,需要拉锯来切割板子,还需要螺丝刀来拧紧螺丝钉。同样的,您在开发连接的 Windows 应用商店应用时也需要使用适当的网络连接 API。Windows 8 提供了大量网络连接 API,这些 API 可让您的应用通过 Internet 或专用网络与其他计算机和设备通信。因此,您的首个步骤是确定您的应用需要怎样的网络连接功能。
最常见的网络连接应用场景是访问网站来检索或存储信息。使用网站来存储用户信息和游戏分数的游戏便是一个简单的例子。而更为复杂的示例可能是连接到一个基于 REST 的 Web 服务,并使用 Web 服务提供的库来访问或存储信息的应用。Windows 8 拥有多个与 Web 服务和网站连接的 API。借助这些 API,您的应用可访问支持 REST 或向 Web 服务器发送基本 HTTP 协议命令(如 GET 和 POST)的 Web 服务。对于 Web 访问,您所需要的 API 将取决于您开发应用所使用的语言:
- XMLHTTPRequest 和 WinJS.xhr - 使用 JavaScript 和 HTML 编写的应用。
- HttpClient - 使用 C# 或 Visual Basic .NET 和 XAML 编写的应用。
- XML HTTP Request 2 (IXHR2) - 使用 C++ 和 XAML 编写的应用。
以下代码显示了如何执行一个基本请求,并响应基于 REST 的 Web 服务。在这一情形中,Web 服务将在 Web 服务器上执行一个 ASP.NET 脚本。
JavaScript
function makeXHRCall() { WinJS.xhr({ uri: "https://www.microsoft.com/en-us/default.aspx” }).done( function onComplete(result) { print(result.responseText); }, function onError(err) { print("Error: " + err.responseText); }); }
C#
private async void MakeHttpCall() { HttpClient httpClient = new HttpClient(); try { string response = await httpClient.GetStringAsync("https://www.microsoft.com/en-us/default.aspx"); } catch (Exception) { // Handle exception. } }
欲了解有关 WinJS.xhr 的更多信息,请参阅连接到 Web 服务(使用 JavaScript 和 HTML 编写的 Windows 应用商店应用)。欲了解有关 HttpClient 的更多信息,请参阅快速入门:使用 HttpClient 连接(使用 C#/VB/C++ 和 XAML 编写的 Windows 应用商店应用)和 HttpClient 示例。
另一常见的网络连接应用场景是下载和上载文件,此时传输过程将持续一段时间。需要从 Web 服务上载或下载照片或相册的相机或照片库应用就是一个例子。这些内容的传输将持续较长时间,因此让用户等待传输完成将毫无意义。Windows.Networking.BackgroundTransfer API 具有文件下载和上载的功能,即使是在应用不再运行时,上载和下载仍可继续。应用将在其处于前端运行、且具有焦点内容时开始传输进程,即使其不再运行,Windows 8 随后也将在后台继续传输进程。
另一更为特殊的应用场景是访问联合内容。Windows.Web.Syndication API 可检索 RSS 中的源或 Atom 格式。此外,Windows.Web.AtomPub API 可让应用以多种 AtomPub 格式发布数据。
对于特定、高级别的网络连接协议无法通过 API 可用的应用场景,Windows 运行时还将支持 TCP 和 UDP 套接字(以及多播)。Windows.Networking.Sockets API 将提供一个 StreamSocket (TCP) 和 DatagramSocket (UDP),从而可让您实施其他较高层的协议。
注意: Windows 8 中引入了一个名为 WebSocket 的新型套接字。欲了解有关 WebSockets 及何时使用它们的更多信息,请参阅与 WebSockets 连接。
以下表格提供了更完整的受支持的网络连接功能列表,并提供了额外信息的链接。
API |
功能 |
示例 |
|
在 RSS 或跨多个版本的 Atom 格式中检索源。这些 API 可简化对诸如 OData 等较新格式实施支持的过程。Windows 运行时还将支持 Atom 发布协议,启用 Atom 集合的发布。 欲了解更多信息,请参阅访问和管理联合内容。
|
|
Windows.Networking.BackgroundTransfer
|
进行不间断、成本感知和可恢复的内容下载和上载,即使是在调用应用并未位于后台的时候,仍可继续。此 API 可支持使用 HTTP、HTTPS 和 FTP 协议传输内容。 欲了解更多信息,请参阅在后台传输数据。
|
|
|
与基于 REST 的 Web 服务和其他基于 HTTP 的协议交互。 欲了解更多信息,请参阅连接到 Web 服务。
|
|
|
检测邻近的两台设备,之后应用将使用套接字 API 触发二者之间的网络通信。 欲了解更多信息,请参阅支持邻近感应和点击。
|
|
|
与远程文件共享通信。 欲了解更多信息,请参阅访问数据和文件。
|
|
|
连接到使用上述 API 不支持的协议(如 SMTP、MAPI 或 telnet)的服务,或连接到其他同一本地网络的另一设备。还可用于需要类套接字语义(异步、双向)来通过 Web(包括 HTTP)连接到新服务的应用。 欲了解更多信息,请参阅与套接字连接。
|
|
Windows.Storage.ApplicationData
|
Windows 8 实际上将在不同用户设备之间传输特定的应用数据。应用数据漫游对于应用大有益处,用户可不止使用一台设备,如工作使用的 PC 和在家里使用的平板电脑,并在多台设备上安装应用。 欲了解更多信息,请参阅漫游应用数据的指导原则。
|
|
选择正确的网络功能
网络隔离是 Windows 8 使用的应用安全模型中的一部分。Windows 将主动发现网络边界,并强制执行网络访问限制,以进行网络隔离。当您适当地部署了它们之后,这些功能将有助于保护您的用户和应用免遭恶意攻击。
应用必须声明网络隔离功能,以定义网络访问的范围。如果没有声明这些功能,那么您的应用将无法访问网络资源。请参阅如何使用网络功能来了解有关 Windows 如何为应用强制执行网络隔离的更多信息。
请注意,您无法将网络连接作为 Windows 应用商店应用和同一设备中桌面应用之间的进程间通信机制。因此,您无法使用 Windows 应用商店应用中的 IP 环回地址。出于开发目的,存在有限的例外可允许用户在 Visual Studio 调试程序中操作时使用 IP 环回地址。欲了解更多信息,请参阅如何启用环回和排除网络隔离的故障。
网络访问请求包含两个类型:
- 客户端启动的出站请求:您的应用可作为客户端,并通过向远程计算机(通常是一台服务器)发送一个初始网络请求来启动网络访问。应用将向服务器发送一个或多个请求,而服务器将返回一个或多个响应。例如,所有从 Web 客户端应用向 Web 服务器的流量就将是这种类型。
- 主动提供的入站请求:您的应用将作为一台网络服务器,并侦听来自远程计算机的入站网络请求。远程计算机将通过向您的应用发送一个初始请求(其中您的应用将作为一台服务器),进而启动网络访问。远程计算机将向您的应用发送一个或多个请求,您的应用将向远程计算机返回一个或多个响应。例如,作为一台媒体服务器的应用就将是该类型。
请遵循最低特权的原则,并仅添加您的应用需要的功能。您的应用可能仅需客户端启动的出站请求,或者可能需要接收主动提供的入站请求。某些应用在通过网络进行身份验证时,可能还需要访问用户凭据和证书。
以下表格详细描述了网络隔离功能及连接的应用通常需要的其他相关功能。头三项功能是连接的应用将使用的主要网络隔离功能。事实上,您的应用必须启用这头三项功能中的至少一项。而表格中的其他条目是某些连接的应用经常需要的其他功能。
网络功能 |
描述 |
示例应用 |
Internet(客户端)
|
提供对 Internet 以及机场和咖啡厅等公共场所的网络的出站访问。需要访问 Internet 的大多数应用都应声明这一功能。
|
|
Internet(客户端和服务器)
|
提供对 Internet 以及机场和咖啡厅等公共场所的网络的入站和出站访问。对关键端口的入站访问将始终受阻。该功能是 Internet(客户端)功能的一个超集;因此没有必要声明二者。
|
|
专用网络(客户端和服务器)
|
在用户信任的私人位置提供入站和出站网络访问。这些通常是家庭或工作网络。对关键端口的入站访问将始终受阻。
|
|
临近感应
|
需要与设备进行近场邻近通信。允许应用访问网络来连接到附近设备,需要用户同意发送或接受邀请。
|
|
企业身份验证
|
提供连接到需要域凭据的企业 Intranet 资源的功能。
|
|
共享用户证书
|
提供访问软件和硬件证书(如智能卡证书)来验证用户身份的功能。当相关的 API 在运行时被调用时,用户必须采取措施(如插入卡片或选择证书)。
|
|
使用此检查列表来确保您的应用中已配置网络隔离:
- 确定您的应用所需的网络访问方向;客户端启动的出站请求,主动提供的入站请求,抑或二者。
- 确定您的应用将通信的网络资源的类型:家庭网络或工作网络中的受信任资源,Internet 中的资源,抑或二者。
- 在应用部件清单 (manifest) 中配置最低所需网络隔离功能。您可使用 Microsoft Visual Studio 2012 中的应用程序清单设计器来进行这一操作,或手动添加这些功能。
- 使用用于故障排除的网络隔离工具来部署和运行您的应用,进而测试您的应用。欲了解更多信息,请参阅如何启用环回和排除网络隔离的故障。
以下屏幕显示了如何使用 Microsoft Visual Studio 2012 中的应用程序清单设计器来启用网络功能。
在 Visual Studio 2012 中选择您应用的 package.appxmanifest 内的网络功能
为按流量计费的网络调整应用行为
想想您上次接近每月数据限额或出国旅行的情形。在这些情形中,我通常将非常小心的使用设备和应用,以避免不必要的网络费用。
Windows 8 通过让应用监控可用网络资源,并在按流量计费的网络中进行相应的操作,进而解决了用户的这一问题。为了增强用户对您的应用的信心,您可让应用在连接可能产生成本的时候告知用户,并调整应用行为以避免或减少费用。
Windows.Networking.Connectivity API 提供了网络连接的类型和成本的信息。这可让您的应用确定何时正常使用网络资源,何时保守使用网络资源,抑或何时先询问用户再使用网络资源。
ConnectionProfile 代表了一个网络连接。您的应用可使用 ConnectionProfile 的 ConnectionCost 来确定是否调整其行为。NetworkCostType 属性可显示网络连接的类型。总共包含四种可能的值:
- Unrestricted - 网络连接使用不受限制。此类网络连接无使用费和容量限制。
- Fixed - 在特定限值以下此类网络连接的使用将不受限制。
- Variable - 此类网络连接的使用将以每字节为基础计费。
- Unknown - 此类网络连接的成本信息不可知。
ConnectionCost 的数个其他布尔属性仍可提供更多信息。
- Roaming - 该连接面向家庭提供程序以外的网络。
- ApproachingDataLimit - 该连接接近数据计划指定的使用允许时间。
- OverDataLimit - 该连接已经超过了数据计划指定的使用允许时间。
让您的应用响应这些属性指示的条件。当连接为 Roaming 时,与网络使用相关的数据成本将非常高。当 NetworkCostType 为 Variable 时,网络将是一个按流量计费的网络,用户将根据其在网络中发送或接收的数据量付费。当 NetworkCostType 为 Fixed 时,如果用户已经超过或接近数据极限时,则将产生问题。
借助这些信息,应用可遵循这些指导原则来确定如何最佳使用网络资源。
行为 |
连接断开 |
应用指导原则 |
示例 |
正常
|
NetworkCostType 为 Unrestricted 或 Unknown,而 ConnectionCost 不是 Roaming
|
应用将不会实施限制。应用认为连接成本“不受限制”,而且不受使用费和容量的限制。
|
|
保守
|
NetworkCostType 为 Fixed 或 Variable,而 ConnectionCost 不是 Roaming 或 OverDataLimit
|
应用将实施限制来优化网络使用,进而处理按流量计费网络中的操作。
|
|
选择加入
|
ConnectionCost 是 Roaming 或 OverDataLimit
|
应用将处理网络访问成本远高于计划成本的特殊情况。
|
|
该代码示例检查了连接成本,并返回恰当应用行为的建议。
JavaScript
var CostGuidance = { Normal: 0, Conservative: 1, OptIn: 2 }; // GetCostGuidance returns an object with a Cost (with value of CostGuidance), // CostName (a string) and Reason, which says why the cost is what it is. function GetCostGuidance() { var connectionCost = Windows.Networking.Connectivity.NetworkInformation.getInternetConnectionProfile().getConnectionCost(); var networkCostConstants = Windows.Networking.Connectivity.NetworkCostType; var Retval = new Object(); if (connectionCost.roaming || connectionCost.overDataLimit) { Retval.Cost = CostGuidance.OptIn; Retval.CostName = "OptIn"; Retval.Reason = connectionCost.roaming ? "Connection is roaming; using the connection may result in additional charge." : "Connection has exceeded the usage cap limit."; } else if (connectionCost.networkCostType == networkCostConstants.fixed || connectionCost.networkCostType == networkCostConstants.variable) { Retval.Cost = CostGuidance.conservative; Retval.CostName = "Conservative"; Retval.Reason = connectionCost.networkCostType == NetworkCostType.fixed ? "Connection has limited allowed usage." : "Connection is charged based on usage. "; } else { Retval.Cost = CostGuidance.Normal; Retval.CostName = "Normal"; Retval.Reason = connectionCost.networkCostType == networkCostConstants.unknown ? "Connection is unknown." : "Connection cost is unrestricted."; } return Retval; }
C#
public enum NetworkCost { Normal, Conservative, OptIn }; public class CostGuidance { public CostGuidance() { var connectionCost = NetworkInformation.GetInternetConnectionProfile().GetConnectionCost(); Init(connectionCost); } public NetworkCost Cost { get; private set; } public String Reason { get; private set; } public void Init(ConnectionCost connectionCost) { if (connectionCost == null) return; if (connectionCost.Roaming || connectionCost.OverDataLimit) { Cost = NetworkCost.OptIn; Reason = connectionCost.Roaming ? "Connection is roaming; using the connection may result in additional charge." : "Connection has exceeded the usage cap limit."; } else if (connectionCost.NetworkCostType == NetworkCostType.Fixed || connectionCost.NetworkCostType == NetworkCostType.Variable) { Cost = NetworkCost.Conservative; Reason = connectionCost.NetworkCostType == NetworkCostType.Fixed ? "Connection has limited allowed usage." : "Connection is charged based on usage. "; } else { Cost = NetworkCost.Normal; Reason = connectionCost.NetworkCostType == NetworkCostType.Unknown ? "Connection is unknown." : "Connection cost is unrestricted."; } } }
请使用网络信息示例来了解有关如何为按流量计费的网络调整应用行为的更多信息。
用户也可运行“任务管理器”来查看每个应用消耗的网络数据量。此屏幕截图显示了一个示例。
“任务管理器”中的“应用历史记录”选项卡可让用户按应用查看 CPU 和网络消耗情况
应对网络状态变化
在任何移动设备应用场景中,用户都可随意连接网络。移动 3G 或 4G 宽带网络可覆盖用户的家中或车库,只要 Wi-Fi 仍然可用。类似地,Wi-Fi 也可在用户离开家中时进行覆盖。当然有时候网络也不可用。由于 Wi-Fi 和移动宽带网络覆盖面不断扩张,这样的网络变化可能经常出现。
NetworkStatusChanged 表明“可用成本”或“连接选项”可能发生变化。为了应对网络状态的变化,并在状态变化时为用户提供一个无缝的体验,请让您的连接应用遵循这些应用场景指导原则。
错误导致的连接断开
在大多数情况下,您只需重试网络操作即可重新建立连接。如果失败,则等待 NetworkStatusChanged 事件。我们建议为应用设置不断增加的重试间隔回退,从 50 毫秒的间隔开始,如果连接仍然失败,则成倍提高此回退间隔。
网络断开
请告知用户连接已断开,然后注册并等待 NetworkStatusChanged 事件。
新网络可用性
有些情况中设备可能与多个网络连接。例如,用户可能连接了移动宽带,在到家之前使用 Windows 8 消息应用与好友聊天,也可能连接到了不受限制的网络。Windows 8 中的默认策略是首选不受限制的网络,而非流量计费的网络,首选较快的网络,而非较慢的网络。然而,应用确立的现有连接并不会自动切换到新的网络。由于只有应用才能做出是否切换到新网络的最佳决策,因此必须应用必须参与网络选择确定过程。
例如,如果一个视频流接近结束,那么切换到新网络可能毫无意义。然而,如果当前网络正在删除数据包,或者网络速度过慢,抑或该流需要额外的时间才能完成,那么切换到新网络可能是最佳方案。
如果您确定可为您的应用使用情形切换网络,那么请在检测新网络时遵循这些指导原则:
- 检查现有网络和新网络的网络成本。如果切换到新网络有意义,那么请根据我刚刚提到的指导原则尝试建立新的网络连接,并重试网络操作。Windows 将在网络可用的情况下,自动选择不受限制的网络,而非按流量计费的网络,以及选择较快的网络,而非较慢的网络。
- 如果新网络连接成功建立,那么请在您的应用中使用新的网络连接,并在连接已经存在的情况下,在原来网络中取消原始网络操作。
网络成本的变化
当 NetworkCostType 为 Fixed,且使用量接近数据极限或超过数据极限时,网络成本将发生变化。如果 NetworkCostType 成为 Variable,或如果 Roaming 变为“true”时,网络成本也可能发生变化。在这些情况中,请根据此前提示中的指导原则调整应用行为。
这些代码示例将使用 NetworkStatusChanged 事件来在网络状态发生多次变化时为用户提供一个无缝的应用体验。两个示例均使用了一个名为 registeredNetworkStatusNotification 的全局布尔变量,且其值最初均设为“false”。
JavaScript
// Register for NetworkStatusChanged notifications, and display new // Internet ConnectionProfile info upon network status change. function registerForNetworkStatusChange() { try { // Register for network status change notifications. if (!registeredNetworkStatusNotification) { var networkInfo.addEventListener("networkstatuschanged", onNetworkStatusChange); registeredNetworkStatusNotification = true; } } catch (e) { print("An unexpected exception occurred: " + e.name + ": " + e.message); } } // Event handler for NetworkStatusChanged event function onNetworkStatusChange(sender) { try { // Get the ConnectionProfile that is currently used to connect // to the Internet. var internetProfile = networkInfo.getInternetConnectionProfile(); if (internetProfile === null) { print("Not connected to Internet\n\r"); } else { internetProfileInfo += getConnectionProfileInfo(internetProfile) + "\n\r"; print(internetProfileInfo); } internetProfileInfo = ""; } catch (e) { print("An unexpected exception occurred: " + e.name + ": " + e.message); } }
C#
// Register for NetworkStatusChanged notifications, and display new // Internet ConnectionProfile info upon network status change. void NetworkStatusChange() { // Register for network status change notifications. try { var networkStatusCallback = new NetworkStatusChangedEventHandler(OnNetworkStatusChange); if (!registeredNetworkStatusNotification) { NetworkInformation.NetworkStatusChanged += networkStatusCallback; registeredNetworkStatusNotification = true; } } catch (Exception ex) { rootPage.NotifyUser("Unexpected exception occurred: " + ex.ToString(), NotifyType.ErrorMessage); } } // Event handler for NetworkStatusChanged event async void OnNetworkStatusChange(object sender) { try { // Get the ConnectionProfile that is currently used to connect // to the Internet ConnectionProfile InternetConnectionProfile = NetworkInformation.GetInternetConnectionProfile(); if (InternetConnectionProfile == null) { await _cd.RunAsync(CoreDispatcherPriority.Normal, () => { rootPage.NotifyUser("Not connected to Internet\n", NotifyType.StatusMessage); }); } else { connectionProfileInfo = GetConnectionProfile(InternetConnectionProfile); await _cd.RunAsync(CoreDispatcherPriority.Normal, () => { rootPage.NotifyUser(connectionProfileInfo, NotifyType.StatusMessage); }); } internetProfileInfo = ""; } catch (Exception ex) { rootPage.NotifyUser("Unexpected exception occurred: " + ex.ToString(), NotifyType.ErrorMessage); } }
欲了解有关 NetworkStatusChanged 事件的更多信息,请参阅快速入门:管理连接事件和可用性变更以及网络信息示例。
缓存内容以提高流畅性
将内容缓存到磁盘可让您的应用快速、流畅地运行。例如,一个 RSS 源阅读器应用可立即显示从此前会话缓存到磁盘中的源。当最新源可用时,应用将更新其内容。缓存可确保用户在启动应用时立即读取内容,同时应用也可获得新内容。
Windows 8 在 Windows.Storage 命名空间中提供了一个 ApplicationData 类。该类提供了对应用数据存储的访问权限。此数据存储包含设备本地的文件和设置、可在多台设备间漫游的文件和设置,抑或是临时文件和设置。
文件是存储大型数据集、数据库或常见文件格式数据的理想介质。文件可存放于漫游、本地或临时文件夹中。以下内容解释了其含义:
- 漫游文件将在用户使用连接的帐户登录的多台计算机和设备间同步。系统并不会立即漫游文件,而是将权衡数个因素后再确定何时发送数据。请确保数据漫游的使用量低于配额(您可通过 RoamingStorageQuota 属性查看配额);如果使用量超过配额,则数据漫游将被挂起。系统可在应用向文件写入数据时漫游文件,因此当系统不再需要应用的文件对象时,请务必关闭它们。
- 本地文件将不会在多台计算机间同步。它们将保留于被原始写入的计算机上。
- 临时文件有可能在系统不使用时被删除。系统将在确定何时及是否删除临时文件时考虑诸如可用磁盘容量和文件年限等因素。
这些代码示例可将内容缓存到磁盘中。缓存服务器响应可让您的应用在终止或重新启动后立即向用户显示内容。为简单起见,这些示例并没有向您显示如何向应用数据存储写入设置,以及如何响应漫游数据。应用数据示例将介绍这些详细内容。
JavaScript
var roamingFolder = Windows.Storage.ApplicationData.current.roamingFolder; var filename = "serverResponse.txt"; function cacheResponse(strResponse) { roamingFolder.createFileAsync(filename, Windows.Storage.CreationCollisionOption.replaceExisting) .done(function (file) { return Windows.Storage.FileIO.writeTextAsync(file, strResponse); }); } function getCachedResponse() { roamingFolder.getFileAsync(filename) .then(function (file) { return Windows.Storage.FileIO.readTextAsync(file); }).done(function (response) { print(response); }, function () { // getFileAsync or readTextAsync failed. // No cached response. }); }
C#
MainPage rootPage = MainPage.Current; StorageFolder roamingFolder = null; const string filename = "serverResponse.txt"; async void cacheResponse(string strResponse) { StorageFile file = await roamingFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting); await FileIO.WriteTextAsync(file, strResponse); } async void getCachedResponse() { try { StorageFile file = await roamingFolder.GetFileAsync(filename); string response = await FileIO.ReadTextAsync(file); } catch (Exception) { // getFileAsync or readTextAsync failed. // No cached response. } }
欲了解有关应用数据存储的更多信息,请参阅漫游您的应用数据博文,并试用应用数据示例。
总结
为了给您的用户提供一个卓越、轻松、连接的应用使用体验,请在规划 Windows 应用商店应用时遵循本文中介绍的指导原则。这些提示能够简化您的开发过程,同时确保用户体验的流畅性,并提高用户对您的应用的满意度。
- Windows 二级项目经理 Suhail Khalid
投稿者:Steven Baker 和 Peter Smith
Comments
Anonymous
February 16, 2014
win8.1平板,从断网状态连接到wifi时,NetworkInformation_NetworkStatusChanged执行了两次。Anonymous
February 16, 2014
win8.1pc从断网状态连接上wifi后,NetworkInformation_NetworkStatusChanged执行两次