ActionScript作为一个编译型(解释+编译)语言,经常需要把一些变量存在外部文件中,如XML文档。但是经常去解析XML文件就不是一件令人愉快的事情,而且变化点不是String, Number等基本类型的话,解析完XML后还要做更多的事情,于是as-spring诞生了。
as-spring是我的一个基于Flash平台的类库,作为一个Ioc反射框架,它在平时工作中给我带来了很多便利。我以前有过几篇博客提到过它:
- 在ActionScript中使用简单的Spring框架来实现IOC
- 使用Spring来配置RemoteObject
- 发布as-spring 2.1
这次升级加入了部分新的特征,用来消除使用过程中遇到的一些不便之处。具体特征如下:
1. 简化基本类型的数据类型定义
以前定义一个Bean都是用<bean>标签来定义,如果我要定义一个String类型的Bean,需要这样来定义:
<bean class='String'>
<constructor-arg value='This is a String'/>
</bean>
现在可以用<element>标签来定义:
<element value='This is a String' type='String'/>
2. 增加了数组定义
以前定义数组没有好的方法,特别是当数组的元素不是基本类型时。现在可以用<list>标签来定义:
<list id='arr'>
<element value='true'/>
<bean>
</bean>
<list>
</list>
</list>
3. 增加了Hash Object的定义
这个功能其实在以前就可以很方便的实现,只是现在加入了一个更符合思维的定义方式:
<map>
<key name='apple' value='[iPhone,iPad]'>
<key name='google'>
<value>android</value>
</key>
<key name='ms'>
<list>
<element value='XP'/>
<element value='Vista'/>
<element value='Win7'/>
</list>
</key>
</map>
本来打算加入对flash.utils.Dictionary的定义的,最后还是放弃了,主要是因为暂时没有遇到迫切需要Dictionary的应用场景,如果以后遇到了,可以考虑加入这个功能。
新浪微博作为一个微博应用在国内还是比较流行的,至少我周围的人就有很多是她的用户。由于新浪微博开放了类似twitter的API, 所以有一部分推友使用了twitter至新浪微博的同步脚本,这里我想使用ActionScript来更新一个新浪微博。
通过API来更新新浪微博有一个前提条件,就是需要注册成为sina的开发者,并创建一个应用还获得一个app key。这些不在详述,具体请参考新浪微博官方站点。获得app key后就可以开始使用ActionScript来正常调用相关API了。
这里我没有使用号称相对安全的OAuth方式登陆,而是使用更直接的Basic Authorization来通过验证,下面是完整代码:
//Define parameters
var username:String='新浪微博用户名';
var password:String='新浪微博密码';
var source:String='新浪微博app key';
var API_URL:String='http://api.t.sina.com.cn'; //新浪微博API domain
var status:String='需要更新的状态';
//define HTTP request
var encodedCredentials:String=Base64.encode(username + ":" + password);
var authHeader:URLRequestHeader=new URLRequestHeader("Authorization",
"Basic " + encodedCredentials);
var url:String = API_URL+ '/statuses/update.xml';
var request:URLRequest = new URLRequest(url);
var variables:URLVariables = new URLVariables();
variables.source = source;
variables.status = status;
request.data = variables;
request.requestHeaders.push(authHeader);
request.method = 'POST';
//send HTTP request.
var loader:URLLoader=new URLLoader();
loader.addEventListener(Event.COMPLETE, onLoaderComplete);
loader.load(request);
//HTTP response
function onLoaderComplete(e){
trace (e.target.data);
}
这里只用到1个外部类Base64,用于HTTP请求中对用户名密码进行加密,该类可以从as3crypto类库中获得。
下载Base64外部库后,设置好正确的classpath,然后替换相关参数,执行代码来更新你的新浪微博吧。
have fun!
seesmic-as3-xmpp是XMPP在ActionScript脚本上的一个实现。或许更加出名的是xiff这个框架,但是目前它并不支持TLS加密,所以需要要TLS支持的时候,更好用的是seesmic-as3-xmpp。
人人网的IM工具校内通是基于XMPP协议进行通信的,根据网络上各路神仙的指示,使用基于XMPP框架的API可以很方便的更新一条状态。那么,在我使用桌面工具Twitter Air更新twitter的时候也可以同步更新到人人网,虽然人人网提供了REST API供应用程序调用,但是它们的验证机制明显需要花费更多的时间。
要想成功地连接到人人网,账号密码是必不可少的,人人网用户注册账号是Email地址,Email地址前的用户名无法保证唯一性,所以人人网用户都有一个额外的数字标识,就像QQ号一样,登陆之后可以从浏览器地址中得到该标识,例如http://www.renren.com/Home.do?id=9527这个用户的ID是9527。在进行XMPP连接前,要设置连接的JID, PASSWORD和SERVER三个值,RFC 3920中规定一个完整的JID应该是id@domain/resource这样一个形式,resource可以作为一个账户多处登陆的会话ID。按照这个规定,人人网用户9527的JID则是9527@talk.xiaonei.com。
下面在AIR中试着更新人人网的状态。打开Flash Builder新建一个AIR工程,将seesmic-as3-xmpp加入到编译路径,工程代码大致如下(MXML省略):
import com.hurlant.crypto.tls.TLSConfig;
import com.hurlant.crypto.tls.TLSEngine;
import com.hurlant.crypto.tls.TLSEvent;
import com.hurlant.crypto.tls.TLSSocket;
import com.seesmic.as3.xmpp.XMPP;
import com.seesmic.as3.xmpp.XMPPEvent;
var xmpp:XMPP;
private function init():void{
xmpp=new XMPP("USER_ID@talk.xiaonei.com","USER_PASSWORD","talk.xiaonei.com");
xmpp.addEventListener(XMPPEvent.SESSION, handleSession,false,0,true);
xmpp.setupTLS(TLSEvent,TLSConfig,TLSEngine,TLSSocket);
xmpp.connect();
}
private function handleSession(e:XMPPEvent):void {
var status:String="a presence sent by AIR using XMPP protocol";
xmpp.sendPresence(status);
}
设置好正确的ID和密码,编译运行,然后登陆人人网就会发现状态已经更新了。have fun!
今天我终于升级了twitter客户端软件Twitter Air。相对于上一版本,V0.3加入了search user, follow user, unfollow user, change profile image等功能。虽然还没有支持direct message,但是这些功能已经基本满足日常需求了。
Twitter Air基于Adobe AIR技术构建,客户端需要安装Adobe AIR2 beta1,而目前AIR的正式版本为1.5,所以使用该软件时需要先手动安装Adobe AIR2 beta1,而安装了Adobe AIR2 beta2的用户可能需要先卸载AIR再安装Adobe AIR2 beta1。在这方面,我也十分恼火,希望Adobe尽快放出AIR2的正式版。
首先,苦于没有twitter账号朋友可以从这里进行注册:http://twitese.darkices.com/。
由于某些原因,目前国内无法直接访问twitter.com的REST API,所以Twitter Air使用了API Proxy来获取和更新数据。目前使用的API proxy是本人空间的一个地址:????(已移除) 我并不保证这个地址永久可用,所以在软件不能使用的情况下可以搜索其它可用的API Proxy。
当然,如果你拥有一个国外的PHP空间,也可以搭建自己的API Proxy,具体方法请参考:http://code.google.com/p/tweetr/
最后要感谢一些开源框架的作者,没有他们的杰出贡献,Twitter Air也不能这么顺利的完成:
Robotlegs是一个纯ActionScript(指不依赖Flex框架中的代码或特性)写成的MVC微型架构。它简单易用,为应用程序的设计提供一个清晰的层次结构,并且它的DI(Dependency Inject)特性容易让人对这个迷人的框架产生“依赖”。
tweetr是一个twitter api的library, 它既包括一个AS3的API, 还包括一个PHP的API proxy,更新一条tweet,follow一个user都靠它们来实现了。
kingnarestyle是一套Flex skin, 这套黑色皮肤是十分受本人喜爱的,这里“严重”声明一下。
用于Flash平台的开源事件模型as3signals目前在开源社区逐步升温,我在之前的一篇Blog中也略有提到了它诞生的历史原因。如今,许多著名的Flash框架开始与as3signals联姻,这里是它们的一些结合体:
跟EventDispathcer相比,as3signals有更易用的接口,如add, remove, dispatch。并且它还有一些EventDispatcher没有的接口,如addOnce, removeAll。
下面看看如何使用as3signals来触发和监听事件:
public class Dispatcher{
public var signal:Signal;
public function Dispatcher(){
signal=new Signal();
}
}
var dispatcher:Dispatcher=new Dispatcher();
dispatcher.signal.add(signalHandler);
function signalHandler():void{
trace("signal dispatched");
}
dispatcher.signal.dispatch();
ISignal.dispatch()支持可变参数,这意味着通信时可以传递任意数量的对象。Signal的构造函数可以指定dispatch传输的数据类型,这利于在编译期间找出潜在bug。
public class Dispatcher{
public var signal:Signal;
public function Dispatcher(){
signal=new Signal(String,String);
}
}
var dispatcher:Dispatcher=new Dispatcher();
dispatcher.signal.add(signalHandler);
function signalHandler(p:String,q:String):void{
trace("signal dispatched");
}
dispatcher.signal.dispatch("cool","as3Signals");
DeluxeSignal支持事件监听器的优先级以及事件的冒泡。addOnce监听的事件只会执行一次,这将为我们提供很大的便利。
var deluxeSignal:DeluxeSignal = new DeluxeSignal(this);
deluxeSignal.addOnce(deluxeSignalHandler);
deluxeSignal.dispatch(new GenericEvent());
deluxeSignal.dispatch(new GenericEvent());
function deluxeSignalHandler(event:GenericEvent):void {
trace("deluxeSignal dispatched: ",event.target);
}
NativeSignal用于结合EventDispatcher,这样原本有EventDispatcher发出的事件转交给了Signal。
var nativeSignal:NativeSignal = new NativeSignal(stage, MouseEvent.CLICK, MouseEvent);
nativeSignal.add(nativeMouseHandler);
function nativeMouseHandler(event:MouseEvent):void {
trace("nativeMouseHandler: stage clicked");
}
除了易用的接口之外,它在性能上也表现不凡:http://alecmce.com/as3/events-and-signals-performance-tests。
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/
Flash IDE中fl包内的组件由StyleManager负责样式的统一管理,每个组件的诞生都会在StyleManager中进行登记。我们可以通过三个途径来修改、查看和清除(也是一种修改行为)组件的样式:
1. 通过组件实例修改
对组件实例调用setStyle会对单个组件修改样式,其他组件的样式不受影响。
instance<UIComponent>.getStyle(name:String):Object;
instance<UIComponent>.setStyle(name:String,value:String):void;
instance<UIComponent>.clearStyle(name:String):void;
这里创建两个Button和一个Label,并对其中一个Button修改样式。
import fl.controls.Button;
import fl.controls.Label;
import fl.managers.StyleManager;
var textFormat:TextFormat=new TextFormat();
textFormat.size=12;
button1=new Button;
button2=new Button;
button2.y=40;
label=new Label;
label.y=80;
addChild(button1);
addChild(button2);
addChild(label);
button1.setStyle('textFormat',textFormat);
2. 通过组件类型修改
通过这种方式可以修改某一类组件的样式,而其他类型组件的样式不受影响。
StyleManager.getComponentStyle(component:Object,name:String):Object;
StyleManager.setComponentStyle(component:Object,name:String,style:Object):void;
StyleManager.clearComponentStyle(component:Object,name:String):void;
修改所有Button的字号,而Label不受影响。
StyleManager.setComponentStyle(Button,'textFormat',textFormat);
3. 通过全局样式修改
通过这种方式可以所有组件的样式。fl组件默认的字体大小是10,如果显示中文是达不到好的效果的,用这种方式能最快的修改组件字体大小。
StyleManager.getStyle(name:String):Object;
StyleManager.setStyle(name:String,value:Object):void;
StyleManager.clearStyle(name:String):void;
修改Button和Label的字号。
StyleManager.setStyle('textFormat',textFormat);
组件在渲染时,会根据这个顺序依次查找样式,按优先级来算单个实例最高,然后是类型样式,接着是全局样式,如果这些样式都没有被设置过,将采用默认样式。默认样式被定义在组件的静态方法中,StyleManager会查找组件的静态方法getStyleDefinition(如果存在的话)来得到默认样式。因此,在自定义组件中,声明静态方法getStyleDefinition是一个好的习惯。