flex与js通信(二)
之前写了一篇flex和js之间的通信,还记得最开始研究这两门语言如何交互,一晃我的任务也快做完了,公司的realspace产品之开了js的API,但是现在使用flex产品的人也比较多,要求开设flex的三维API,已经过去几个月了,产品也上线了,有兴趣的可以在https://github.com/SuperMap/Flex-Realspace下载,这是一个利用as与js交互放在了一个完整的产品里。
网上也有很多关于flex和js通信的文章,不过不知道大家有没有注意到好像都需要把js文件放在固定的文件下(flex调用js),并且需要在index.template.html文件里面动手脚,而且你把源代码考给团队的其他人,发现flash builder导进去后重新生成了index.template.html,又得自己修改一下,不是很麻烦吗?如果你是需要此技术做自己的产品的话我建议你一定要看一下我想出的办法,也许会帮了不少人哦!
一个大项目或者产品,在js代码里面估计都会有很多用原型建的类,在flex端估计也有很多功能。我们的假设场景是你正在使用flex制作项目,不过有一些功能只能通过js来实现,所以你将部分功能使用js封装成了一些js的文件,希望通过as与js之间的通信来使用as语言调用js里面的功能。js文件基本已经写好了,但是因为需要在不少地方懂手脚团队协作起来不方便,我这篇文章就是用来解决这个问题的。
一、设计好代码
首先我们需要明白的是:js文件在启动之后被加载到html页面里面,我们可以通过ExternalInterface.call("js的方法",参数);在flex端调用js端的方法,我们可以通过ExternalInterface.addCallback("对外公开的方法名",具体实现的方法);来解决在js端调用flex端的方法。
现在我们需要思考:js里面有很多功能,我们每次调用js功能都得去调用相关的方法吗?那样as和js之间的耦合不是太高了,不符合设计模式,并且很麻烦。同样js这边需要回调(常用在事件回调上)flex的方法时,方法很多,我们是不是也要在flex端动态注册那么多个方法名称呢?那样不怕名称冲突吗?所以这样的实现都是不可取的。
为了达到低耦合,我将as和js之间的通信设定为只有两座桥,as调用js功能永远只会通过调用js里面的固定方法,js回调flex的功能也只能调用在程序初始化flex端动态创建的唯一的一个方法。这样as和js之间的通信永远只通过这两个方法来交互,就不会出现之前的很多问题。但是大家肯定想问如何知道我这一次调用的功能和我下一次调用的有什么区别怎么办?那就是在参数上面做手脚了。说白了就是通过参数来判断你的行为。
在我们flex realspace产品面as调用js的功能都是通过var result:Object=ExternalInterface.call("SuperMap.Web.Util.ApplicationManager.initBridgeFlexToJs",object);这一局来的,SuperMap.Web.Util.ApplicationManager.initBridgeFlexToJs是在js端的一个文件里面的方法,唯一的as访问js的通道,特殊的是object,这是一个键值对,形如:
var realArgument:Array=[strServerUrl.toString()+"$String"]; var array:Object={ action:"FUNCTION", key:this.KEY, functionName:"checkPluginUpdate", isReturn:true, realArgument:realArgument };
上面的array就是我说的object,里面的action:"FUNCTION"代表我需要调用的是方法,调用的对象的key(唯一标示)是this.KEY,方法名称是checkPluginUpdate,有返回值,方法的参数是realArgument(一个特殊数组)。
其实这些有点类似json,用来传递数据的,而我自定义的一个格式是用来传递行为操作的,这个你们可以自己定义,反正核心就是通过参数来判断你的行为,方法永远调用同一个,这点明白了就是自己设计上的事情,不过在解析的时候有点麻烦哈!
二、打包js代码
如果你的代码符合我这样的逻辑,交互通过两座桥来通信,那么我们就开始下一步,其实这样设计后已经可以使得自己的产品交互起来很方便了,我当时花了一个月就是这么做的,都做的差不多了,上面交代说js代码不能开放出来,而且每次都需要修改index.template.html文件,太麻烦,希望flex端的开放人员完全看不到js的任何东西,要求需要把js文件打包成为swc文件,通过去调用swc文件里面的js来实现功能,我当时一听这简直就是不可能的嘛!不过后来研究了好久,发现这样可以实现,并且还有它的优势。
首先我们需要知道如何把js文件打包成为swc文件,新建一个库项目,将你的js文件都放进去,如下图:
我建的项目叫bridge,里面有我所有的js文件,还有不少as文件,细心的发现每一个js文件旁边都有一个...Stream.as文件,这里就是重点了,我们如何才能把js文件打包到swc里面呢,flex提供了一种流的方式,例如上面的ApplicationManager.js文件,我们创建一个类ApplicationManagerStream继承于flash.utils.ByteArray,代码如下:
package SuperMap.Js { import flash.utils.ByteArray; /** * 制定js文件以及打包方式 */ [Embed(source="ApplicationManager.js", mimeType="application/octet-stream")] public class ApplicationManagerStream extends ByteArray { public function ApplicationManagerStream() { super(); } } }
写好你对应的js文件,一定要和此类放在一个文件夹下,配上打包的类型application/octet-stream,每一个js文件都配置一个相应的as类,这样在
里面会自动生成好打包好的swc文件
现在问题又来了,里面是二进制的东西,我们如何使用啊,不是我们平时写的as类,写了什么方法引用这个包直接使用就行。
所以我们还需要一个as类。这里我创建IncludeStream.as,代码如下:
package SuperMap { import SuperMap.Js.ApplicationManagerStream; import SuperMap.Js.HashTableStream; import SuperMap.Js.SceneDivStream; import SuperMap.lib_Ajax.IServerJava6RStream; import SuperMap.lib_Ajax.JsStream; import SuperMap.lib_Ajax.MicrosoftAjaxStream; import SuperMap.lib_Realspace.RealspaceStream; /** * 此类主要用于以字符串形式获取二进制形式的js代码 */ public class IncludeStream { /** * 构造函数 */ public function IncludeStream() { } /** * 返回js文件里面代码的字符串形式 */ public function toString():String { var microsoftAjaxStream:MicrosoftAjaxStream=new MicrosoftAjaxStream(); var jsStream:JsStream=new JsStream(); var iServerJava6RStream:IServerJava6RStream=new IServerJava6RStream(); var realspaceStream:RealspaceStream=new RealspaceStream(); var hashTableStream:HashTableStream=new HashTableStream(); var applicationManagerStream:ApplicationManagerStream=new ApplicationManagerStream(); var sceneDivStream:SceneDivStream=new SceneDivStream(); return microsoftAjaxStream.toString()+jsStream.toString()+iServerJava6RStream.toString()+realspaceStream.toString()+hashTableStream.toString()+applicationManagerStream.toString()+sceneDivStream.toString(); } } }
有了这个类,我们可以通过实例化一个此对象,然后通过方法toString()获取到字符串形式的js的所有代码。这又是一个问题,我们还是不能使用,没法调用。不过先这样打包好,引用到你的flex项目里面再说。
三、调用js文件
首先我们要知道as调用js的方法只能调用js形式的方法,也就是嵌入到了html页面的js,而现在我们只有字符串形式的js源代码,所以需要想办法将它转化为标准的js代码,嵌入到html页面里面。
在你的flex项目启动的最开始的地方恰如如下代码:
//初始化一个字符串形式的方法parseStringToJs,用于将字符串转换为js语言 var str:String = "function parseStringToJs(str){var oHead = document.getElementsByTagName('HEAD').item(0);"; str+="var oScript = document.createElement(\"script\");"; str+="oScript.language = \"javascript\";"; str+="oScript.type = \"text/javascript\";"; str+="oScript.id = \"test\";"; str+="oScript.defer = true;"; str+="oScript.text = str;"; str+="oHead.appendChild(oScript);}"; //调用window.eval方法 ExternalInterface.call("eval",str); var includeStream:IncludeStream=new IncludeStream(); //获取js的字符串形式的源代码 var strey:String=includeStream.toString(); //使用正则表达式修改字符串 var char:RegExp = /\\/g; strey = strey.replace(char,"\\\\"); //生成js的API ExternalInterface.call("parseStringToJs",strey); //初始化js调用as的入口方法initBridgeJsToFlex,最终饭方法的实现转给方法ApplicationManager.initBridgeJsToFlex ExternalInterface.addCallback("initBridgeJsToFlex",ApplicationManager.initBridgeJsToFlex);
注释已经写的很清楚了,不过正则表达式这一步可以不要,我这里是因为我的代码里出现了反斜杠的东西,所以我这样是为了转义一下,这里的思路就是先使用ExternalInterface.call去调用window对象的方法eval在window下创建一个将字符串转化为js代码的方法parseStringToJs,然后再调用此方法将我们的字符串的源代码进行转化,这样html中就存在js的代码了,就不用再去修改index.template.html文件添加引用和添加入口了。并且在其他地方你可以通过ExternalInterface.call直接调用js端公开的桥,并且通过ApplicationManager.initBridgeJsToFlex来接收js的回调函数,这样我们只需要添加swc这个包就可以使用js的所用功能。
其实我也不想使用eval的,eval是很消耗性能的,我的源代码长度大概是8万多,还好,没见卡,我也只会这种方法,不要拿板砖拍我哦!有其他办法的朋友告诉我哦!期望对某些人有帮助吧!