前阵子搞 zk 与 flash 交互,搞得头破血流,zk 本身带有 flash 标签,但只能当播放器用,没法进行比较复杂的 flash 控制,如调用 flash 的内部方法。flash 也没法直接通过前端与 zk 组件交互,于是自己搞了一个中间件,用于 zk 与 flash 的交互,使得 zk 可以直接调用 flash 的内部方法,flash 可以直接向 zk 发送数据,同时加入了一点同步控制,因为 flash 虚拟机是异步单线程的,且 zk 的每个 flash 调用都是独立的,在实际执行过程中无法保证调用顺序。
需要 zk 5.0 以上版本,flash 支持 flex sdk3 和 sdk4 编译程序。
demo.zul(zk 演示页面)
<?page title="demoZK" contentType="text/html;charset=UTF-8"?> <zk> <window title="demoZK" border="normal" width="100%" height="100%" apply="mysh.Demo"> <div id="flashDiv" /> </window> </zk>
Demo.java(zk 端演示组件)
package mysh;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.util.GenericComposer;
import org.zkoss.zul.Div;
import org.zkoss.zul.Messagebox;
import mysh.FlashContainer;
/**
* Demo.
*
* @version Revision 1.0.0
*/
public class Demo extends GenericComposer {
/*
* @see org.zkoss.zk.ui.ext.AfterCompose#afterCompose()
*/
@Override
public void doAfterCompose(Component comp) {
Div flashDiv = (Div) comp.getFellow("flashDiv");
flashDiv.addEventListener(FlashContainer.ON_CONTAINER_INFO,
new EventListener() {
public void onEvent(Event event) throws Exception {
Messagebox.show("receive from flash : " + event.getData(),
"zk msg", Messagebox.OK, Messagebox.INFORMATION);
}
});
FlashContainer flashContainer = FlashContainer.genFlashContainer(
flashDiv, "demoFlash.swf");
flashContainer.registObserver(flashDiv);
flashContainer.callFlash("func1", "param");
}
}
demoFlash. mxml(flash 端演示)
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" applicationComplete="init()" xmlns:mx="library://ns.adobe.com/flex/mx" width="594" height="416"> <fx:Script> <![CDATA[ import mysh.FlashContainer; import mx.controls.Alert; private var flashContainer:FlashContainer; private function init():void { this.flashContainer=new FlashContainer(this); this.flashContainer.registFunc("func1", this.function1); } private function function1(param:String):void { Alert.show("msg from zk : " + param); } protected function send2ZK_clickHandler(event:MouseEvent):void { this.flashContainer.sendEvent2ZK(this.input.text); } ]]> </fx:Script> <s:Button id="send2ZK" x="46" y="101" label="发送到 zk" click="send2ZK_clickHandler(event)"/> <s:TextInput id="input" x="45" y="56"/> </s:Application>
flashContainer.as(flash 端组件。D.eval 下载地址)
package mysh
{ import flash.external.ExternalInterface; import mx.controls.Alert; import mx.core.Application; import r1.deval.D; /** * flash 容器 * * @version 20110117 */ public class FlashContainer { /** * 容器持有者 */ private var holder:Object; /** * 发送事件调用的JS方法 */ private var eventSendFunc:String; /** * 所有注册方法 */ private var funcs:Object=new Object(); /** * 执行顺序 */ private var order:Number=0; public function FlashContainer(holder:Object) { this.holder=holder; // sdk3 : this.eventSendFunc=Application.application.parameters.eventFunc; // sdk4 : // this.eventSendFunc=FlexGlobals.topLevelApplication.parameters.eventFunc; ExternalInterface.addCallback("jsCall", jsCall); } /** * 外部js调用的方法 */ private function jsCall(jsonStr:String):void { // Alert.show(jsonStr); var jsonObj:Object=D.eval(jsonStr); var funcArray:Array=jsonObj.funcs as Array; var order:Number=jsonObj.order as Number; // 检查执行顺序,若 小于0,则执行;若 大于0,则只有 大于 this.order 才执行 if (order > 0) { if (order > this.order) { this.order=order; } else { return; } } // Alert.show(order+","+this.order); for each (var pack:Object in funcArray) { var func:String=pack.func; var args:Array=pack.args; var treatedArgs:Array=new Array(); for (var i:int=0; i < args.length; i++) { treatedArgs[i]=(args[i] as String).replace(/"/g, "\"").replace(/'/g, "'"); } if (this.funcs[func] == null) { Alert.show("FlashContainer 找不到方法:" + func, "内部错误"); return; } (this.funcs[func] as Function).apply(this.holder, treatedArgs); } } /** * 注册方法名 * @param func 方法名 * @param method 方法实体 */ public function registFunc(func:String, method:Function):void { if (func == null || func == "" || method == null) { throw new Error("方法名或方法不能为空"); } this.funcs[func]=method; } /** * 发送事件到 zk * @param value 值 */ public function sendEvent2ZK(value:String):void { // Alert.show(this.eventSendFunc); ExternalInterface.call(this.eventSendFunc, value); } } }
flashContainer.zul(zk 页面)
<?page title="" contentType="text/html;charset=UTF-8"?> <zk> <window title="" border="normal" use="FlashContainer"> </window> </zk>
FlashContainer.java(zk端组件。swfobject.js 下载地址。随着功能的增加,我觉得叫 FlashManager 比较合适)
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicLong;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.ext.AfterCompose;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Div;
import org.zkoss.zul.Script;
import org.zkoss.zul.Window;
/**
* flash容器.
* 非线程安全,需要外部同步.
*
* @version 20110117
*/
public final class FlashContainer extends Window implements AfterCompose {
/**
* serialVersionUID.
*/
private static final long serialVersionUID = 2564493195082621543L;
/**
* zul 页面地址.
*/
private static final String ZUL_PATH = "flashContainer.zul";
/**
* 方法调用定义.
*
* @version Revision 1.0.0
*/
public static final class FlashFuncInvoke {
/**
* 方法名.
*/
private String func;
/**
* 方法参数.
*/
private String[] args;
/**
* 构造器.
*
* @param tFunc
* 方法名
* @param tArgs
* 方法参数
*/
public FlashFuncInvoke(String tFunc, String... tArgs) {
if (tFunc == null || tFunc.length() == 0 || tArgs == null) {
throw new RuntimeException("非法参数");
}
this.func = tFunc;
this.args = tArgs;
}
/**
* 返回一个“单元素列表”.
*
* @param tFunc
* 方法名
* @param tArgs
* 方法参数
* @return 单元素列表
*/
public static List<FlashFuncInvoke> genSingleEleList(String tFunc,
String... tArgs) {
List<FlashFuncInvoke> funcList = new ArrayList<FlashContainer.FlashFuncInvoke>(
1);
funcList.add(new FlashFuncInvoke(tFunc, tArgs));
return Collections.unmodifiableList(funcList);
}
}
/**
* 用于浏览器端向服务端发送事件.
*/
private static final String ON_CIENT_INFO = "onClientInfo";
/**
* flash 容器事件的名称,用于取得此容器发送的事件(从客户端取得的事件).
*/
public static final String ON_CONTAINER_INFO = "onContainerInfo";
/**
* 执行顺序号限制(2^52-1).
*/
private static final long ORDER_LIMIT = 0xFFFFFFFFFFFFFL;
/**
* 执行顺序号生成器.
*/
private final AtomicLong orderGen = new AtomicLong(1L);
/**
* 事件监听组件.
*/
private Div eventListener = new Div();
/**
* flash 容器.
*/
private Div flash = new Div();
/**
* 浏览器端脚本容器.
*/
private Script script = new Script();
/**
* 浏览器 js 方法命名空间.
*/
private String ns;
/**
* flash动作观察者.
*/
private Queue<Component> observers = new LinkedList<Component>();
/**
* 生成 FlashContainer(用于 flash 9 版本).
*
* @param parent
* flash容器的父组件
* @param swfSrc
* 页面 js 可访问的 swf 路径
* @return FlashContainer
*/
public static FlashContainer genFlashContainer(Component parent,
String swfSrc) {
Map<String, String> args = new HashMap<String, String>();
args.put("flashSrc", swfSrc);
args.put("flashVer", "9");
return (FlashContainer) Executions.createComponents(
FlashContainer.ZUL_PATH, parent, args);
}
/**
* 生成 FlashContainer(用于 flash 10 版本).
*
* @param parent
* flash容器的父组件
* @param swfSrc
* 页面 js 可访问的 swf 路径
* @return FlashContainer
*/
public static FlashContainer genFlashContainerVer10(Component parent,
String swfSrc) {
Map<String, String> args = new HashMap<String, String>();
args.put("flashSrc", swfSrc);
args.put("flashVer", "10");
return (FlashContainer) Executions.createComponents(
FlashContainer.ZUL_PATH, parent, args);
}
/**
* 生成 FlashContainer(用于 flash 10 版本).
*
* @param parent
* flash容器的父组件
* @param swfSrc
* 页面 js 可访问的 swf 路径
* @param flashVars
* 传递给 flash 的参数
* @return FlashContainer
*/
public static FlashContainer genFlashContainerVer10(Component parent,
String swfSrc, Map<String, String> flashVars) {
Map<String, Object> args = new HashMap<String, Object>();
args.put("flashSrc", swfSrc);
args.put("flashVer", "10");
args.put("flashVars", flashVars);
return (FlashContainer) Executions.createComponents(
FlashContainer.ZUL_PATH, parent, args);
}
/**
* 构造器.
*/
public FlashContainer() {
this.ns = this.getUuid();
this.appendChild(this.eventListener);
this.appendChild(this.flash);
this.appendChild(this.script);
this.eventListener.addEventListener(FlashContainer.ON_CIENT_INFO,
new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
// flash 控件不支持,打开下载页面
if (event.getData().equals("flashNotSupported")) {
alert("flashNotSupported");
}
for (Component comp : FlashContainer.this.observers) {
Event e = new Event(FlashContainer.ON_CONTAINER_INFO,
comp, event.getData());
Events.sendEvent(e);
}
}
});
// 发送事件脚本
String scriptStr = "function " + this.ns
+ "_flashSend(v){var e = new zk.Event();e.$init(zk.Widget.$('"
+ this.eventListener.getUuid() + "'), '"
+ FlashContainer.ON_CIENT_INFO + "', v);zAu.send(e);}";
// 调用 flash 方法
scriptStr += "function " + this.ns + "_callFlash(jsonStr){"
+ "var flash=null;try{flash = document.getElementById(\""
+ this.flash.getUuid()
+ "\").jsCall(jsonStr);}catch(err){setTimeout(\"" + this.ns
+ "_callFlash('\"+jsonStr+\"')\",150);}}";
this.script.setContent(scriptStr);
// swfobject
Script swfobject = new Script();
swfobject.setSrc("/common/js/swfobject.js");
this.appendChild(swfobject);
}
/*
* @see org.zkoss.zk.ui.ext.AfterCompose#afterCompose()
*/
@SuppressWarnings("unchecked")
@Override
public void afterCompose() {
// javaScript 可识别的 swf 地址
String flashSrc = (String) Executions.getCurrent().getArg()
.get("flashSrc");
// flash 播放需要的播放器版本号
String flashVer = (String) Executions.getCurrent().getArg()
.get("flashVer");
// 传递给 flash 的参数
Map<String, String> flashVars = (Map<String, String>) Executions
.getCurrent().getArg().get("flashVars");
String detectFlashVer = "9,0,124";
String swfobjectFlashVer = "9.0.124";
if (flashVer != null && flashVer.equals("10")) {
detectFlashVer = "10,0,0";
swfobjectFlashVer = "10.0.0";
}
// 检查 flash 控件版本,若不支持给定的 flash 版本,弹出页面
Clients.evalJavaScript("if(!DetectFlashVer(" + detectFlashVer + ")){"
+ this.ns + "_flashSend('flashNotSupported');}");
// 初始化flash,传入 js发送事件方法名
Clients.evalJavaScript("function " + this.ns
+ "_initSWF(){try{swfobject.embedSWF('" + flashSrc + "', '"
+ this.flash.getUuid() + "', '100%', '100%', '" + swfobjectFlashVer
+ "', '', {eventFunc:'" + this.ns + "_flashSend'"
+ this.genFlashVars(flashVars)
+ "}, {wmode:'opaque'}, {});}catch(e){setTimeout('" + this.ns
+ "_initSWF();',100);}}" + this.ns + "_initSWF();");
}
/**
* 生成 flashvars 参数字串.<br/>
* 若参数为 null,则生成的参数为 ''<br/>
* 参数名为空的参数将被忽略;含有引号的参数为非法参数,将抛运行时异常<br/>
* 格式:,a:'a',b:'b'
*
* @param flashVars
* 参数 map
* @return 参数字串
*/
private String genFlashVars(Map<String, String> flashVars) {
if (flashVars == null) {
return "";
}
StringBuilder vars = new StringBuilder("");
String tempKey, tempValue;
for (Map.Entry<String, String> entry : flashVars.entrySet()) {
tempKey = entry.getKey();
if (tempKey == null || tempKey.length() == 0) {
continue;
}
tempValue = entry.getValue();
if (tempValue == null || tempValue.contains("\'")
|| tempValue.contains("\"")) {
throw new RuntimeException("非法参数:" + tempValue);
}
vars.append(",");
vars.append(tempKey);
vars.append(":'");
vars.append(tempValue);
vars.append("'");
}
return vars.toString();
}
/**
* 只执行最新的调用(以调用顺序为准),忽略旧的调用(执行顺序值小于客户端当前执行顺序值的调用). <br/>
* 此方法用于解决对 flash 端的多个调用不能保证顺序的问题. <br/>
* 若不希望任何调用被忽略,请用 callFlashFuncList 方法. <br/>
* 调用flash方法.
*
* @param func
* flash 方法名
* @param args
* 可变参数(参数中不允许含有 &quot; 或 &apos;,有则抛异常)
*/
public void callFlashLatest(String func, String... args) {
this.innerCallFlashFuncList(this.orderGen.incrementAndGet(),
FlashFuncInvoke.genSingleEleList(func, args));
}
/**
* 调用flash方法. 这个方法的多个调用在 flash 客户端不能保证调用顺序. <br/>
* 要确保调用顺序,尝试 callFlashLatest 或 callFlashFuncList 方法. <br/>
*
* @param func
* flash 方法名
* @param args
* 可变参数(参数中不允许含有 &quot; 或 &apos;,有则抛异常)
*/
public void callFlash(String func, String... args) {
this.innerCallFlashFuncList(-1L,
FlashFuncInvoke.genSingleEleList(func, args));
}
/**
* 只执行最新的调用(以调用顺序为准),忽略旧的调用(执行顺序值小于客户端当前执行顺序值的调用). <br/>
* 此方法用于解决对 flash 端的多个调用不能保证顺序的问题. <br/>
* 按顺序执行给定的 flash 方法. <br/>
* 方法参数中不允许含有 &quot; 或 &apos;,有则抛异常
*
* @param list
* 方法定义列表
*/
public void callFlashFuncListLatest(List<FlashFuncInvoke> list) {
this.innerCallFlashFuncList(this.orderGen.incrementAndGet(), list);
}
/**
* 按顺序执行给定的 flash 方法. 这个方法的多个调用在 flash 客户端不能保证调用顺序. <br/>
* 要确保调用顺序,尝试 callFlashFuncListLatest 方法. <br/>
* 方法参数中不允许含有 &quot; 或 &apos;,有则抛异常
*
* @param list
* 方法定义列表
*/
public void callFlashFuncList(List<FlashFuncInvoke> list) {
this.innerCallFlashFuncList(-1L, list);
}
/**
* 向 flash 客户端发送执行命令(方法调用).
*
* @param order
* 给定一个执行顺序值(一个从小到大的正值,不能大于 2^52-1=4503599627370495L. <br/>
* 若为 -1L,则 flash 端执行此调用;若为一个正值,则 flash
* 端将忽略旧的调用(执行顺序值小于客户端当前执行顺序值的调用). <br/>
* 给定执行顺序值用于解决这样一个问题:flash 客户端的多个调用的执行不能同步,但给定执行顺序将导致旧的调用被忽略
* @param list
* 方法定义列表
*/
private void innerCallFlashFuncList(long order, List<FlashFuncInvoke> list) {
if (order < -1L || order > FlashContainer.ORDER_LIMIT) {
throw new RuntimeException("执行顺序值超出 [-1, 2^52-1]");
}
if (list == null || list.size() < 1) {
return;
}
StringBuilder str = new StringBuilder("var j={order:");
str.append(order);
str.append(",funcs:[");
for (FlashFuncInvoke funcInvoke : list) {
str.append(this.genJsonString(funcInvoke.func, funcInvoke.args));
str.append(",");
}
if (str.charAt(str.length() - 1) == ',') {
str.deleteCharAt(str.length() - 1);
}
str.append("]}");
Clients.evalJavaScript(this.ns + "_callFlash(\'" + str.toString()
+ "\')");
}
/**
* 根据给定的方法名、方法参数生成 json 对象字符串.
*
* @param func
* 方法名
* @param args
* 方法参数(参数中不允许含有 &quot; 或 &apos;,有则抛异常)
* @return json 对象字符串
*/
private String genJsonString(String func, String[] args) {
String argsStr = "";
if (args != null && args.length != 0) {
for (String arg : args) {
if (arg == null) {
throw new RuntimeException("不允许 null 值作为参数");
}
if (arg.contains(""") || arg.contains("'")) {
throw new RuntimeException("参数中不允许含有 " 或 '");
}
argsStr += "\""
+ arg.replace("\"", """).replace("'", "'")
+ "\",";
}
argsStr = argsStr.substring(0, argsStr.length() - 1);
}
return "{func:\"" + func + "\", args:[" + argsStr + "]}";
}
/**
* 注册一个 flash 事件观察者.
* 该观察者需要注册 ON_CONTAINER_INFO 的事件监听
*
* @param observer
* flash 动作观察者
*/
public void registObserver(Component observer) {
this.observers.add(observer);
}
}