翻译:构建 AdobeRIA 离线应用程序(未完成)

来自 Adobe Labs

(链接 AIR:Articles:Taking Apollo Applications Offline)

作者 John C. Bland II (http://www.johncblandii.com)

个人认为,RIA 的最大特点是有能力创建一个在线和离线都可以运行的应用程序。这个应用程序允许用户更改他们的帐号,内容等。当连接恢复到在线的时候,便异步更新数据。用户更喜欢这样的应用程序。

通过这篇文章,我将处理在离线和在线状态下如何去管你的应用系统。在开发 RIA 和 Flex 2.0.1 应用系统的重点将放在管理网络状态和处理基本数据。不论是在线的数据接受还是离线的管理我将都采用这个简易的数据数据类型(XML)。

请记住所有的带来都来自 AIR Alpha1 和我自己原创的东西而并没有相关的 “书” 涉及到这些,尽管我认为这并不是什么伟大的创作。你可以从这里学习并融入你自己的想法。

内容目录:

  1. 条件
  2. 检测网络状态
    1. 检测在线状态
  3. 创建一个基于状态的应用程序
    1. 更改应用程序的状态
    2. 管理数据
  4. 由此及彼(如何应用相关)
  5. 关于作者

 

基础条件

为了能掌握这篇文章更多,你需要如下的软件和文件:

示例文件:john_bland_sample_code.zip (1.83 MB)

先决条件:Flex 3,MXML, 和 ActionScript 3.0 的知识

 

探测网络状态

我将跳过 Flex Builder 和代码的深入。关于设置 Flex Builder 和创建 Flex 应用程序的更多的信息,可以访问 Flex Developer Center 的 IDEs section

AIR Alpha 1 发布版本提供一个很好的事件(Event.NETWORK_CHANGE),这个事件在网络状态发生变化的时候会通知应用程序。这个事件无法让你知道你应用程序是在线还是在离线状态,它只有在网络状态发生改变的时候才通知你:你可以进去离线,进入在线,或登入 VPN 系统等。

让我们从 NETWORK_CHANGE 事件的捕捉开始。

 [Code (AIROffline_Step1.mxml)] 
<?xml version="1.0" encoding="utf-8"?>   
<mx:WindowedApplication xmlns:mx=http://www.adobe.com/2006/mxml   
layout="absolute" creationComplete="init()">   
<mx:Script> <![CDATA[   
private function init():void{   
Shell.shell.addEventListener(Event.NETWORK_CHANGE, onNetworkChange);   
}  
private function onNetworkChange(event:Event):void{   
trace(event);   
} 
]]>   
</mx:Script>   
</mx:WindowedApplication>   
[/Code] 

这段代码是最基础的网络探测。当应用程序的 creationComplete 事件被触发的时候,调用 init () 方法,在这个方法里添加一个事件监听器,命名这个监听函数为 onNetworkChange。在的 trace() 语句这一行设置一个断点,由此来进行调试。当这个应用程序被开启,断开你的网络链接,你将看到你的 Flex Builder 将应用程序停止到这个断点(如图一)。

200710121007

图 1. 应用程序断点

 

探测在线状态

现在在网络状态改变的时候你可以让应用程序告诉你。现在你需要知道当前状态(在线或离线)。我们将尝试去下载我们在线的数据。如果问能,说明我们在线。否则我们假设我们离线。让我们添加一些代码来检测。希望今后 AIR 解决方案不需要这么多的代码。

[Code (AIROffline_Step2.mxml)]
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication    xmlns:mx="http://www.adobe.com/2006/mxml"
                  layout="absolute" creationComplete="init()">
   <mx:Script>
      <![CDATA[
         [Bindable]
         private var isOnline:Boolean = false;
         private var request:URLRequest = new URLRequest("http://blogs.katapultmedia.com/jb2/_dev/onlineoffline/data/rooms.xml");
         private var requestLoader:URLLoader = new URLLoader();
        
         private function init():void{
            requestLoader.addEventListener(Event.COMPLETE, requestCompleteHandler);
            requestLoader.addEventListener(IOErrorEvent.IO_ERROR, requestErrorHandler);
            Shell.shell.addEventListener(Event.NETWORK_CHANGE, onNetworkChange);
            requestLoader.load(request);
         }
        
         private function onNetworkChange(event:Event):void{
            isOnline = !isOnline
            trace("Connected to Internet? " + isOnline);
         }
        
         private function requestErrorHandler(event:IOErrorEvent):void{
            isOnline = false;
         }
        
         private function requestCompleteHandler(event:Event):void{
            isOnline = true;
         }
      ]]>
   </mx:Script>
</mx:WindowedApplication>
[/Code]

我们在代码里添加了一些东西。首先让我们简略的浏览下这个流程

  1. 应用程序初始化
  2. 添加事件监听,响应数据装载的成功和失败,还有为网络连接的改变
  3. 尝试装载 XML 文件(或其他任意你需要的文件 / 数据)

这里指出应用程序被设置并准备去决定连接的状态。从而我们的监听函数将改变 isOnline 变量的状态。注意 onNetworkChange() 方法仅仅改变 isOnline 对应的值。更好的方法处理这使用延迟的服务器请求直到确保连接状态。

关注当前 NETWORK_CHANGE 事件有多个地方。当你从在线切换到离线的时候这个事件被调用,如上所述,当你连接入 VPN 的时候同样被触发。记住服务器是使用双击在线状态来切换到离线,这样会使数据的装载失败,从而使应用程序显示为离线状态。查找网络更新后再完成这个操作。

为了这篇文章,我们将提供一个最佳情况(服务器运行正常并没有其他类型的连接)但是并不能用这样的方法来研究应用程序。将来的 RIA 解决方案,提供更好的网络支持,这才是可行的,一定要重写 / 更新你的代码来更准确的处理前面 “最佳情况”(如上所述)。

在调试模式下运行上面的代码,当你连接和断开你的网络连接的时候,在 Console 视图中看到如下的情况(如图 2)。

200710121008

图 2. 当你连接和断开连接时候的输出视图

下一步就是在连接状态下创建一个应用程序。

 

创建基于状态的应用程序

我们现在知道连接状态仅仅需要和应用程序对应而已。你可以把你应用程序的 currentState 属性绑定到 isOnline 变量以达到想要的效果。

 

改变应用程序状态(states)

我假设你已经很熟悉 Flex 2 因此我会在这个基础上扩展。我们将实现通过基于 isOnlline 的真假来选择应用程序的状态。

 [Code (snippet; AIROffline_Step3.mxml)]   
<?xml version="1.0" encoding="utf-8"?>   
<mx:WindowedApplication xmlns:mx=http://www.adobe.com/2006/mxml   
layout="absolute" creationComplete="init()"   
currentState="{isOnline ? 'Online' : 'Offline'}">   
[/Code]

把你所以的注意力放到 currentState 这一行。让我们基于 isOnline 变量改变 Online 或 Off line 状态。非常简单,是不是?现在让我们创建 states。

[Code (snippet; AIROffline_Step3.mxml)]   
<mx:states>   
<mx:State name="Online">   
<mx:SetProperty name="status" value="Online"/>   
</mx:State>   
<mx:State name="Offline">   
<mx:SetProperty name="status" value="Offline"/>   
</mx:State>   
</mx:states>   
[/Code]

states 中的每个状态仅仅在改变 WindowedApplication.status 的值的时候展现当前的状态。你想用图形的方式告诉用户,你或许希望我能展现一些真正漂亮的网络连接的图形表示,基于状态使用灰色和全彩色来表示。一个你所能得到的状态条的改变伴随我的图形的改变。

那么接下来我们怎么做?我很高兴你会这样问。让我们处理这些数据。

 

管理数据

我们有自己的连接状态能告诉我们当前的状态。现在我们需要处理数据。我们将再次调用 onNetworkChange() 函数和其他一些东西。让我们首先来看先我们的目标和所要得到的结果。

  • 如果离线,装载本地数据文件。
  • 如果在先,装载在线数据文件并保存到本地以便以后的离线数据装载。(如果数据装载失败,返回到离线数据文件)
  • 如果网络状态从离线改变到在线,装载在线数据并重复前面的步骤。
  • 结果:数据自动更新到应用程序(定义绑定)而无论哪种状态数据一直是可用的。

大部分代码已经提过了,因此这里我只显示一小部分,这样我们看到代码更改的部分更清楚。

 [Code (snippet; Script block; AIROffline_Step4.mxml)]   
import flash.filesystem.*;   
private var localFile:File = File.applicationStorageDirectory.resolve("AIROffline/rooms.xml");   
private var localFileStream:FileStream;   
...[other code]   
private function init():void{   
requestLoader.addEventListener(Event.COMPLETE, requestCompleteHandler);   
requestLoader.addEventListener(IOErrorEvent.IO_ERROR, requestErrorHandler);   
Shell.shell.addEventListener(Event.NETWORK_CHANGE, onNetworkChange);   
//create/open local file   
localFileStream = new FileStream();   
localFileStream.open(localFile, FileMode.UPDATE);   
localFileStream.close();   
loadData();   
}   
[/Code]

AIR for Adobe Flex Developers Pocket Guide 里详细介绍了文件管理因此我在这里不再详细的重复。这个 FileStream 对象在创建指向本地文件的时候,是在定义这个变量的时候决定的。FileMode.UPDATE 打开文件或创建没有存在的文件。如果文件不存在,这个文件将是空的。直到我们想做的所有操作为止这个文件一直是关闭的,之前所做的只是为了确保这个 XML 文件是存在的。

[Code (snippet; AIROffline_Step4.mxml)] //Cleanup Methods   
private function applicationClosingHandler(event:*):void{   
localFileStream.close();   
} //Data Methods   
private function loadData():void{ requestLoader.load(request); }   
private function readLocalFile():void{ localFileStream.open(localFile, FileMode.READ);   
roomsXML = XML(localFileStream.readUTFBytes(localFileStream.bytesAvailable));   
localFileStream.close(); }   
private function saveDataLocally():void{ localFileStream.open(localFile, FileMode.WRITE);   
localFileStream.writeUTFBytes('<?xml version="1.0" encoding="utf-8"?>
'  
+roomsXML.toXMLString());   
localFileStream.close(); } //Connection methods   
private function onNetworkChange(event:Event):void{   
isOnline = !isOnline if(isOnline){ loadData(); }   
}   
private function requestErrorHandler(event:IOErrorEvent):void{   
isOnline = false; //Get data from local file   
readLocalFile();   
}   
private function requestCompleteHandler(event:Event):void{   
isOnline = true;   
roomsXML = XML(requestLoader.data); //Write data locally saveDataLocally();   
}   
[/Code]

我添加一个 applicationClosingHandler() 函数只是为了保证在应用程序关闭后 FileStream 是关闭的。以上的大部分代码跟我们已经写的很类似。注意添加的 readLocalFile() 和 saveDataLocally() 函数。这些函数被用来管理本地的文件 / 数据。这些步骤如下:

If isOnline == true
# requestCompleteHandler() 的调用发生在初始数据装载之后.
# 在 roomsXML 变量中保存数据;应用程序根据绑定自动更新显示
# saveDataLocally() 被调用
## 保存 roomsXML 中的数据到 FileStream 中去
If isOffline == false
# requestErrorHandler() 被调用
# readLocalFile() 被调用
## 设置 roomsXML 的内容为本地 roomsXML 文件中的内容

只有 isOnline 是真的时候,当 onNetworkChange() 中是在线的时候我们必须再次调用 loadData () 更新本地数据。当数据装载我们重复以上这些步骤 "If isOnline == true" 这样将会保存我们本地的数据也能用在线装载的数据重置 roomsXML 数据并异步更新显示。

我新增了一些显示的对象,这样我们便能查看数据的变化。

[Code]   
<mx:VBox width="100%" height="100%">   
<mx:Text text="{roomsXML.Room.length()} Rooms Available"/>   
<mx:TextArea id="RoomsList" width="100%" height="100%" text="{roomsXML.toXMLString()}"   
selectable="false" editable="false"/>   
</mx:VBox>   
[/Code]

当前的 XML 数据结构,如下所示,在线的,在我这个时刻。图 3 让你看到运行应用程序后这个数据的结果。

[Code (data/rooms.xml)]   
<?xml version="1.0" encoding="utf-8"?>   
<Rooms>   
<Room name="Room 1" />   
<Room name="Room 2" />   
<Room name="Room 3" />   
<Room name="Room 4" />   
<Room name="Room 5" />   
<Room name="Room 6" />   
<Room name="Room 7" />   
<Room name="Room 8" />   
<Room name="Room 9" />   
<Room name="Room 10" />   
</Rooms>   
[/Code] 

200710121009

图 3. 运行应用程序呈现 XML 数据

现在,我将断开我的网络,关闭应用程序,你再重新打开它。(如图 4)

200710121010

图 4. 重新打开应用程序

好的,这里没有任何变化,是么?只有一个地方发生了变化那就是 status,但是我在关闭应用程序然后在更新在线的 XML 后断开我的网络连接。

这是一个新的 XML (仅仅复制 Room 节点).

 [Code (data/rooms.xml)]   
<?xml version="1.0" encoding="utf-8"?>   
<Rooms>   
<Room name="Room 1"/>   
<Room name="Room 2"/>   
<Room name="Room 3"/>   
<Room name="Room 4"/>   
<Room name="Room 5"/>   
<Room name="Room 6"/>   
<Room name="Room 7"/>   
<Room name="Room 8"/>   
<Room name="Room 9"/>   
<Room name="Room 10"/>   
<Room name="Room 1"/>   
<Room name="Room 2"/>   
<Room name="Room 3"/>   
<Room name="Room 4"/>   
<Room name="Room 5"/>   
<Room name="Room 6"/>   
<Room name="Room 7"/>   
<Room name="Room 8"/>   
<Room name="Room 9"/>   
<Room name="Room 10"/>   
<Room name="Room 1"/>   
<Room name="Room 2"/>   
<Room name="Room 3"/>   
<Room name="Room 4"/>   
<Room name="Room 5"/>   
<Room name="Room 6"/>   
<Room name="Room 7"/>   
<Room name="Room 8"/>   
<Room name="Room 9"/>   
<Room name="Room 10"/>   
</Rooms>   
[/Code]

现在我再次打开应用程序,依然是离线的,先前装载的数据之后装载本地保存的数据之后,因此装载本地数据他会看到图 4 的。这就是它的可爱之处。再没有关闭应用程序的时候我重新连接网络。这在线数据文件将被下载且保存到本地(如图 5)

200710121011

图 5. 在线文件装载并保存到本地

太好了!记住应用程序从来没有关闭。我一直保持打开仅仅重新连接下而已。如果

Sweet! Keep in mind the application never closed. I kept it open and just reconnected. If this doesn't wet your whistle of excitement, I don't know what will!

To finish the cycle, I'm going to disconnect from my network then open the application again. The result is Figure 5 with a state of Offline. Yummy!

OK: Everyone at the same time, "AIR IS SWEET!" Let's look at what we covered today.

 

Where to go from here

From the ground up we built a quick application that monitored network status and responded to the status accordingly. We also setup a data sync so the user would always have the latest online data locally without having to request it. Our data was simple XML but you can see how this process could be utilized to store specific user information offline application use while in a disconnected state. The possibilities of how your application functions offline is 100% up to you. AIR gives you the tools and leaves it to you to be creative.

Again, keep in mind this is with AIR Alpha 1. I used some, what will become, unnecessary techniques. Keep an eye out for future releases of AIR. For now, enjoy Alpha 1!

I hope this article was helpful. If you have any questions and/or concerns, feel free to email me (mailto:[email protected]) and I'll try to respond within 24 hours. My apologies in advance for the spam trap.

Download AIR (when available), game-plan your application, and get rocking! Let AIR guide you on a path to a highly usable online/offline application.

Final code:

[Code] <?xml version="1.0" encoding="utf-8"?>   
<mx:WindowedApplication xmlns:mx=http://www.adobe.com/2006/mxml   
layout="absolute" creationComplete="init()"   
currentState="{isOnline ? 'Online' : 'Offline'}"   
closing="applicationClosingHandler(event)">   
<mx:Script> <![CDATA[   
import flash.filesystem.*;   
private var localFile:File = File.appStorageDirectory.resolve("AIROffline/rooms.xml");   
private var localFileStream:FileStream;   
[Bindable]   
private var isOnline:Boolean = false;   
private var request:URLRequest =   
new URLRequest(http://blogs.katapultmedia.com/jb2/_dev/onlineoffline/data/rooms.xml);   
private var requestLoader:URLLoader = new URLLoader();   
[Bindable]   
private var roomsXML:XML = new XML();   
private function init():void{   
requestLoader.addEventListener(Event.COMPLETE, requestCompleteHandler);   
requestLoader.addEventListener(IOErrorEvent.IO_ERROR, requestErrorHandler);   
Shell.shell.addEventListener(Event.NETWORK_CHANGE,   
onNetworkChange); //create/open local file   
localFileStream = new FileStream();   
localFileStream.open(localFile, FileMode.UPDATE);   
localFileStream.close();   
loadData();   
} //Cleanup Methods   
private function applicationClosingHandler(event:*):void{   
localFileStream.close();   
} //Data Methods   
private function loadData():void{ requestLoader.load(request); }   
private function readLocalFile():void{   
localFileStream.open(localFile, FileMode.READ);   
roomsXML = XML(localFileStream.readUTFBytes(localFileStream.bytesAvailable));   
localFileStream.close();   
}   
private function saveDataLocally():void{   
localFileStream.open(localFile, FileMode.WRITE);   
localFileStream.writeUTFBytes('<?xml version="1.0" encoding="utf-8"?>
'  
+roomsXML.toXMLString());   
localFileStream.close();   
} //Connection methods   
private function onNetworkChange(event:Event):void{   
isOnline = !isOnline if(isOnline){ loadData(); }   
}  
private function requestErrorHandler(event:IOErrorEvent):void{   
isOnline = false; //Get data from local file readLocalFile();   
}   
private function requestCompleteHandler(event:Event):void{   
isOnline = true;   
roomsXML = XML(requestLoader.data); //Write data locally saveDataLocally();   
}   
]]>   
</mx:Script>   
<mx:states>   
<mx:State name="Online">   
<mx:SetProperty name="status" value="Online"/>   
</mx:State>   
<mx:State name="Offline">   
<mx:SetProperty name="status" value="Offline"/>   
</mx:State>   
</mx:states>   
<mx:VBox width="100%" height="100%">   
<mx:Text text="{roomsXML.Room.length()} Rooms Available"/>   
<mx:TextArea id="RoomsList" width="100%" height="100%"   
text="{roomsXML.toXMLString()}" selectable="false" editable="false"/>   
</mx:VBox>   
</mx:WindowedApplication>   
[/Code]

 

About the author

John C. Bland II is CEO and Chief Developer for Katapult Media, Inc. (http://www.katapultmedia.com), an Arizona based software and web development company. Along with running Katapult, he manages the Arizona Flash Platform User Group (http://www.gotoandstop.org). All titles aside, John is a geek.