Merapi是一个用于Java和AIR之间通信的桥接库。通过它可以极大的扩展AIR在本地的执行能力,当然前提是客户端不仅装有AIR运行时虚拟机,还需要安装有Java虚拟机。Merapi通过Socket来通信,使用Adobe的AMF作为数据传输格式。下面看看如何在Flash Builder中创建一个基于Adobe AIR的桌面软件。
在Java和ActionScript两端都存在一名为Bridge的类,它是一个Singleton,用来注册消息类型和发送消息。在这个例子中,AIR向Java端发送一个名为”flashEvent”的消息,消息内容是一个Dictionary实例;并且监听名为”javaEvent”的消息。
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" creationComplete="initApp()">
<mx:Script>
<![CDATA[
import demo.JavaMessageHandler;
import demo.SerializeMessageHandler;
import merapi.Bridge;
import merapi.messages.Message;
import merapi.error.MerapiErrorMessage;
private function initApp():void{
//register a error handler
new SerializeMessageHandler();
//send a message
var message:Message=new Message();
message.type="flashEvent";
var data:Dictionary=new Dictionary();
data["name"]="colorhook";
message.data=data;
message.send();
//register a message handler
Bridge.getInstance().registerMessageHandler("javaEvent",
new JavaMessageHandler());
}
]]>
</mx:Script>
</mx:WindowedApplication>
CoreErrorHandler继承自EventDispatcher,实现了IMessageHandler接口,它在构造函数中就集成了错误处理。所以不需要显示的在Bridge中注册,而是构造一个实例就行了。SerializeMessageHandler继承自CoreErrorHandler,用来处理全局错误。
package demo{
import merapi.handlers.mxml.CoreErrorHandler;
import mx.rpc.events.ResultEvent;
public class SerializeMessageHandler extends CoreErrorHandler{
public function SerializeMessageHandler(type:String=null){
super(type);
this.addEventListener(ResultEvent.RESULT,onMessageResult);
}
private function onMessageResult(event:ResultEvent):void{
trace("SerializeMessageHandler:",event);
}
}
}
JavaMessageHandler实现了接口IMessageHandler,并在handleMessage方法中输出从Java中返回的数据。
package demo{
import merapi.handlers.IMessageHandler;
import merapi.messages.IMessage;
public class JavaMessageHandler implements IMessageHandler{
public function handleMessage(message:IMessage):void{
trace(message.type);
trace(message.data);
}
}
}
编译运行会发现控制台输出SerializeMessageHandler中的trace信息,因为目前还没有开启Java端的ServerSocket。所以下面来启动一个Java程序。首先调用Bridge.open()来启动ServerSocket。然后监听名为”flashEvent”的消息。
package demo;
import org.apache.log4j.Logger;
import merapi.Bridge;
public class Application {
protected Logger logger=Logger.getLogger(Application.class);
public Application(){
logger.info("Application start");
//open Bridge
Bridge.open();
//register a message handler
Bridge.getInstance().registerMessageHandler("flashEvent",
new AIRMessageHandler());
}
public static void main(String[] args){
new Application();
}
}
在消息处理器AIRMessageHandler中,输出来自Flash的数据,AMF将ActionScript中的Dictionary映射为Java中的Map。接受到数据之后,我们再从Java中发出一个Message,该Message携带了一个List类型的数据,在Flash中,它会被映射成Array类型。
package demo;
import java.util.*;
import org.apache.log4j.Logger;
import merapi.handlers.IMessageHandler;
import merapi.messages.IMessage;
import merapi.messages.Message;
public class AIRMessageHandler implements IMessageHandler {
protected Logger logger=Logger.getLogger(AIRMessageHandler.class);
@Override
public void handleMessage(IMessage message) {
//handle message
logger.info(message.getType());
Map<String,String> map=(Map<String, String>)message.getData();
logger.info(map.get("name"));
//send a new message
Message newMessage=new Message();
newMessage.setType("javaEvent");
List<String> list=new ArrayList<String>();
list.add("Flash Player");
list.add("AIR");
newMessage.setData(list);
newMessage.send();
}
}
Merapi在Java端用到了Spring, log4j和Adobe AMF相关包, 所以运行时要保证项目包含了这些jar包。可以从Merapi的Google Code中得到这些包。
AIR2的发布,让我们在AIR和Java通信方面有了新的选择: flerry。
flerry使用了AIR2的新特性NativeProcess,它直接创建了一个进程了执行Java程序。从其作者的blog上可以了解更多信息: http://www.riaspace.net/2010/01/flerry-flex-java-bridge-for-adobe-air-2-0/
Flex跟ColdFusion的通信极为简单,而且它们都是基于标签的语法格式。在Flex中以RPC的形式调用ColdFusion程式不需要向AMFPHP那样指定一个gateway,更没有BlazeDS中繁琐的配置文件。
拿个简单的例子来说明到它底有多简单。
1. 编写ColdFusion组件。
首先建立一个ColdFusion组件来供前台调用。该组件有一个方法sayHello,它接受一个String类型的参数并返回一个String类型的结果。保存文件在服务器根目录的flex目录中,命名为HelloColdFusion.cfc。
<cfcomponent>
<cffunction name="sayHello" access="remote" returntype="string">
<cfargument name="info" type="string" required="yes">
<cfreturn "[Hello ColdFusion], #info#">
</cffunction>
</cfcomponent>
2. 构建Flex客户端
在Flex中声明一个RemoteObject对象把source属性
Flex代码片段
<mx:RemoteObject id="service" destination="ColdFusion"
source="flex.HelloColdFusion"
result="Alert.show(String(event.result))"/>
<mx:Button label="send" click="service2.sayHello('hoho~')"/>
3. 运行程序
点击按钮则弹出服务器的返回消息。
在Flex中使用RemoteObject可以很容易地对Java对象进行RPC调用,但是其配置较为复杂。使用AMFPHP对PHP进行RPC调用只需要指定RemoteObject的destination和endpoint属性即可,而在BlazeDS环境下,需要创建ChannelSet和Channel对象。
通常的做法是使用一个xml文件来配置一个Java destination,Flex在编译的时候会根据配置信息来创建一个完整的RemoteObject对象。但是如果需要更改该RemoteObject对象的destination或者ChannelSet等属性,则需要重新编译,这样的话很是麻烦。
Spring的IOC容器可以很好解决这个问题,它把对象的创建方式声明在外部,然后由容器来动态生成。
使用我的Spring框架来实现这个想法。
1.创建远程的Java对象,编译成class文件放在文件夹WEB-INF\classes\demo下面。
package demo;
public class HelloBlazeDS{
public HelloBlazeDS(){
}
public String sayHello(String info){
return "[BlazeDS return] : "+info;
}
}
2.配置BalzeDS。
打开文件WEB-INF\flex\remoting-config.xml,向其中添加一个destination定义。
<destination id="HelloBlazeDS">
<properties>
<source>demo.HelloBlazeDS</source>
</properties>
</destination>
3.创建Spring的配置文件spring_config.xml。
<?xml version="1.0" encoding="utf-8"?>
<spring-config>
<beans>
<bean id="remoteBean" class="mx.rpc.remoting.mxml.RemoteObject">
<property name="destination" value="HelloBlazeDS"/>
<property name="channelSet" ref="channelSet"/>
</bean>
<bean id="channelSet" class="mx.messaging.ChannelSet">
<method name="addChannel">
<method-arg ref="channel"/>
</method>
</bean>
<bean id="channel" class="mx.messaging.channels.AMFChannel">
<property name="id" value="my-amf"/>
<property name="uri" value="http://localhost:8080/blazeds/messagebroker/amf"/>
</bean>
</beans>
</spring-config>
该配置文件声明了一个Bean名字叫remoteBean,它是一个RemoteObject对象,这个Bean还引用了channelSet和channel这两个Bean。
4.在Flex中使用Spring,下面是代码片段。
//导入加载配置文件的类ContextLoader
import com.colorhook.spring.context.ContextLoader;
private var contextLoader:ContextLoader;
private var remoteService:RemoteObject;
//初始化时加载配置文件
private function init():void{
contextLoader=new ContextLoader();
contextLoader.addEventListener("complete",onContextLoaderComplete);
}
//加载完成之后创建RemoteObject,并调用远程方法sayHello
private function onContextLoaderComplete(event:Event):void{
contextLoader.removeEventListener("complete",onContextLoaderComplete);
remoteService=contextLoader.contextInfo.getBean("remoteBean");
remoteService.sayHello.addEventListener(ResultEvent.RESULT,onSayHelloResult);
remoteService.sayHello("Spring and BlazeDS");
}
//输出返回结果
private function onSayHelloResult(event:ResultEvent):void{
Alert.show(String(event.result));
}
Protocols, channels, destinations和endpoints
Data Services可以使用HTTP,HTTPS协议来进行通信,而我们更推崇的是使用开源的AMF协议,AMF协议是基于HTTP协议的,它没有使用特定的端口,所以不需要对防火墙进行特定的设置。更重要的是,AMF使用二进制数据通信,极大的提高了通信效率。
Channel代表一种通信特征。铁轨是给火车跑的,公路给汽车跑。公路也分是普通的、高速的。Channel也是这样,有的Channel用于HTTPService,有用于RemoteService,也可以设置它的参数来改变Channel的行为。
这是一个Channel的定义:
<destination>
<channel-definition id="my-http-longpoll" class="mx.messaging.channels.HTTPChannel">
<endpoint url="http://servername:8700/contextroot/messagebroker/myhttplongpoll"
class="flex.messaging.endpoings.HTTPEndpoint"/>
<properties>
<polling-enabled>true</polling-enabled>
<polling-interval-seconds>0</polling-interval-seconds>
<wait-interval-millis>60000</wait-interval-millis>
</properties>
</channel-definition>
</destination>
该Channel是一个HTTPChannel,Channel有一个Endpoint,该Channel的Endpoint是一个HTTPEndpoint,Endpoint声明了一个URI,一个端口和一个Endpoint类型。除了Endpoint,该Channel还有一些参数来配置它的具体行为,这些参数是可选的。
destination是在Flex客户端需要定义的标记,如果你使用的是HTTPService或者WebService,这个destination可以忽略,但是如果你使用RemoteObject来交互,就必须要定义它的destination。
<mx:RemoteObject id="remoteService" destination="helloDestination">
</mx:RemoteObject>
上面的Flex代码声明了一个RemoteObject,他的destination名为helloDestination。这个destination到底在什么地方呢?在服务器端的配置文件中。在WEB-INF文件夹下有一个flex文件夹,在flex文件夹中存在4个xml配置文件:services-config.xml,proxy-config.xml,remoting-config.xml,messaging-config.xml。其中services-config.xml被web.xml引用,其它三个被services-config.xml引用。在实际应用中,我们把HTTPService和WebService的destination定义在proxy-config.xml中,把RemoteObject的destination定义在remoting-config.xml中,把Producer和Consumer的destination定义在messaging-config.xm中。
这是一个RemoteObject的destination描述:
<destination id="helloDestination" channels="my-amf">
<properties>
<source>demo.HelloBlazeDS</source>
</properties>
</destination>
这个destination使用id为my-amf的Channel,对应的远程Java类为demo.HelloBlazeDS。
BlazeDS是Adobe的一款开源免费的Data Services。Data Services是一种遵守AMF或者其它协议(如RTMP)来进行Remoting和传输消息(Messaging)的产品(Product)。
这里是几个Data Services:
Data Services的特点如图

安装BlazeDS
Adobe官方网站提供BlazeDS的下载,这是一个turnkey版本,集成了tomcat服务器,下载之后立马就可以享受到它的特性。
客户端与服务器使用AMFPHP通信时不仅可以直接传递数字,字符,数组等基本数据类型外,还可以传递更复杂的数据类型。这意味着你可以传递
自定义类的实例。
1.在客户端有个名为ValueObject的AS3类。
该类有id,value和description三个属性。[RemoteClass(alias="")]元标签用来注册该类,服务器就可以根据注册名来查找对应的服务器类。该元标签必须要有,但是别名alias可以任意,不过建议写成完整的限定名。
package demo
{
[Bindable]
[RemoteClass(alias="demo.ValueObject")]
public class ValueObject
{
public var id:String;
public var value:String;
[Transient]
public var description:String;
public function ValueObject()
{
}
}
}
2.在服务器端对应有一个名为ValueObject的PHP类。
该类也有有id, value和description三个属性。该类中有个属性$_explicitType用来和客户端的类对应起来。如果你使用的版本是PHP5,那么这个属性其实是多余的。
<?php
class ValueObject{
var $_explicitType="demo.ValueObject";
public $id;
public $value;
public $description;
function ValueObject(){
}
function init($r){
$keys=array_keys($r);
foreach($keys as $k){
$this->$k=$r[$k];
}
}
}
?>
3.在AMFPHP的services目录下建立一个服务类ClassMapService。
该类有一个方法updateMyObject,用来接受客户端的自定义类型,并且返回一个服务器端的自定义类型。遗憾的是,客户端的自定义类型没有自动转换成服务器的自定义类型,我们需要手动实现它,PHP类ValueObject有个init方法就是为了达到这个目的。
<?php
require_once "demo/ValueObject.php";
class ClassMapService{
function updateMyObject($vo){
$result=new ValueObject();
$result->init($vo);
$result->id="PHP:".$result->id;
$result->value="PHP:".$result->value;
$result->description="PHP:".$result->description;
return $result;
}
}
?>
4.创建一个客户端应用程序来跟ClassMapService交互。
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
verticalCenter="0"
creationComplete="init()">
<mx:Style>
Label{
fontSize:12px;
color:#000000;
}
Panel{
padding-left:10;
padding-right:10;
padding-bottom:5;
padding-top:5;
}
Label{
textAlign:right;
}
</mx:Style>
<mx:Script>
<![CDATA[
import demo.ValueObject;
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.controls.Alert;
[Bindable]
private var myValueObject:ValueObject=new ValueObject();
private function init():void{
remoteService.updateMyObject.addEventListener(ResultEvent.RESULT,
onUpdateResult);
}
private function remoteFaultHandler(event:FaultEvent):void{
Alert.show(event.message.toString());
}
private function buttonClickHandler():void{
var vo:ValueObject=new ValueObject;
vo.id=id_send.text;
vo.value=value_send.text;
vo.description=description_send.text;
remoteService.updateMyObject.send(vo);
}
private function onUpdateResult(e:ResultEvent):void{
myValueObject=e.result as ValueObject;
}
]]>
</mx:Script>
<mx:RemoteObject id="remoteService"
destination="php_destination"
endpoint="http://localhost:8001/amfphp/gateway.php"
source="ClassMapService"
fault="remoteFaultHandler(event)"/>
<mx:Panel title="ValueObject to send:" width="300">
<mx:HBox>
<mx:Label text="id:" width="80"/>
<mx:TextInput id="id_send"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="value:" width="80"/>
<mx:TextInput id="value_send"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="description:" width="80" />
<mx:TextInput id="description_send"/>
</mx:HBox>
<mx:HBox>
<mx:Spacer width="180"/>
<mx:Button label="send" click="buttonClickHandler()"
width="60"/>
</mx:HBox>
</mx:Panel>
<mx:Panel title="ValueObject form PHP:" width="300">
<mx:HBox>
<mx:Label text="id:" width="80"/>
<mx:TextInput id="id_get" text="{myValueObject.id}"
editable="false" width="170"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="value:" width="80"/>
<mx:TextInput id="value_get" text="{myValueObject.value}"
editable="false" width="170"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="description:" width="80"/>
<mx:TextInput id="description_get"
text="{myValueObject.description}"
width="170" editable="false"/>
</mx:HBox>
</mx:Panel>
</mx:Application>
5.运行程序。
可以发现返回的数据中id和value都包含了输入的数据,而description属性比较不同,原因是客户端的ValueObject类中还有个一个元标签[Transient]。该标签的作用就是向后台传送数据时忽略这个属性的值。如果你的需求不需要交互某个属性,就可以将它设置为[Transient]来减少数据传输。

Flex中有RemoteObject类来实现Remoting,这个类无法在Flash IDE中使用。要在Flash CS3中实现Remoting,我们可以使用Flash原生API NetConnection,这里我借助bytearray.org上面提供的包装类来完成。你可以在这个地址中下载相关的类文件和范例:http://www.bytearray.org/?p=122
在这里,我将再次使用上一例子中的HelloRemoting.php这个文件,剩下的就只有建立Flash项目了。
import org.bytearray.remoting.Service;
import org.bytearray.remoting.PendingCall;
import org.bytearray.remoting.events.FaultEvent;
import org.bytearray.remoting.events.ResultEvent;
var service:Service=new Service("HelloRemoting",
"http://localhost:8001/amfphp/gateway.php");
var pendingCall:PendingCall=service.sayHello("remoting");
pendingCall.addEventListener(ResultEvent.RESULT,onResult);
pendingCall.addEventListener(FaultEvent.FAULT,onFault);
function onResult(e:ResultEvent):void{
trace(e.result);
}
function onFault(e:FaultEvent):void{
trace(e.fault);
}
运行结果:
hello,remoting