Обмен данными между Flex-приложениями.
У меня в блоге уже описано во многих топиках как Flex может служить клиентом сервисов (например Криптография во Flex,Создание асинхронного прокси для обращения к WCF средствами Adobe flex builder.), есть описание обмена данными через JavaScript - Как с помощью jQuery сделать флеш-ролик резиновым - но иногда возникает задача ПРЯМОГО общения между несколькими FLEX-AIR приложениями.
Живых примеров такого обмена в инете полно, описаний тоже - на этой страничке я тоже скажу пару своих слов на эту тему:
Во-первых, прямое общение между SWF-роликами через LocalConnection намного предпочтительнее JavaScript, даже когда два ролика просто висят в браузере. Я обнаружил замечательное сообщение, которое выдает Opera - о том, что общение через ExternalInterface.addCallback - ExternalInterface.call не поддерживается в Опере.
Во-вторых, самая главная фишка (довольно невнятно прожеванная в документации), что имя коннекта должно быть уникальным. В ситуации, когда на одной страничке находятся несколько Flex-приложений - это значит, что имя коннекта должно формироваться одним роликом и публиковатся где-то (например в URL или в скрытой переменной), а другим роликом это имя должно читатся (и по этому имени должен открыватся коннект с ожидаением поступления команд.
В-третьих, надо понимать технологию отладки взаимодействия приложений по LocalConnection.
В-четвертых, есть несколько обязательных событий - если не добавить слушатели этих событий (даже пустые) работать с LocalConnection не получится.
На скринчике ниже вы видите, как происходит обмен между двумя flex-приложениями. Верхнее приложение формирует параметры URL (в том числе имя коннекта), нижнее приложение читает эти параметры и показывает билеты по этим параметрам. Для наглядности я добавил Alert при передаче сообщения и на приеме.
В принципе я попробовал разделять сложные flex-приложения на несколько - и мне понравилось. Особенно если понимать как отлаживать сплитированное приложение.
На самом деле все просто - ведь все что должно сделать верхнее приложение - это сформировать параметры для передачи нижнему (в случае на рисунке все просто - их видно в URL - и понятно, правильно ли их формирует приложение. А вот для отладки нижнего приложения проще всего сделать вот такой небольшой враппер:
На самом деле это весьма распространенная техника и такие врапперы вы можете увидеть во многих моих прогах, например Трехмерное вдохновение PaperVision3D.
Ну и теперь я покажу несколько строчек из кода шлюза для обмена сообщениями по LocalConnection. Верхний ролик формирует URL в разных точках своего алгоритма, централлизованно обращаясь к функции:
1: protected function FormatURL(IsDirection:Boolean, IsReturn:Boolean, FromDate:String, ToDate:String):String{
2: return mx.utils.StringUtil.substitute("Direction={0},Return={1},FromDate={2},ToDate={3},CN={4}", IsDirection, IsReturn, FromDate, ToDate, CN);
3: }
При инициалиализации верхнего приложения порождается уникальное имя коннекта:
140: protected var Browser:IBrowserManager;
141:
142: protected function application1_applicationCompleteHandler(event:FlexEvent):void
143: {
159: ...
171: if (Browser.fragment==""){
172: //дернули web-сервис (проинициализировали календарик, создали уникальный номер коннекта)
173: CN = GetRandomChanellName();
174: IsSelectedDayFromURL=false;
175: GetReadyButtons();
176: }
177: else{
178: //инициализация от параметров URL
179: IsSelectedDayFromURL=true;
180: ParseURL(Browser.fragment);
181: }
182: }
183:
184: //имя канала обмена для рефреша
185: protected var CN:String;
186:
187: protected function GetRandomChanellName():String{
188: var RND:Number = Math.random();
189: var NF:spark.formatters.NumberFormatter = new spark.formatters.NumberFormatter();
190: NF.decimalSeparator="_";
191: NF.fractionalDigits=8;
192: return "INDIA"+NF.format(RND).substr(2);
193: }
Нажатие кнопки "искать билеты" у меня приводит к срабатыванию вот такой функции:
227: protected function GoSearch(){
228: if (Browser.url.toLowerCase().indexOf("ticket.aspx")<0){
229: flash.net.navigateToURL(new URLRequest("Ticket.aspx#" + Browser.fragment),"_self");
230: }
231: else {
232: try {
233: //открыли коннект к SWF с календариком
234: if (CN1==null) {
235: CN1 = new LocalConnection();
236: CN1.addEventListener(SecurityErrorEvent.SECURITY_ERROR , LocalConnection_SECURITY_ERROR);
237: CN1.addEventListener(AsyncErrorEvent.ASYNC_ERROR , LocalConnection_ASYNC_ERROR);
238: CN1.addEventListener(StatusEvent.STATUS,LocalConnection_STATUS);
239: }
240:
241: CN1.send("_"+CN,"RefreshTicket");
242: Alert.show("Send " + "_"+CN);
243:
244: }
245: catch (e:Error){
246: Alert.show(e.ToString());
247: trace(e.ToString());
248: //ничего не делаем - если связи с роликом потребителем нет
249: }
250: }
251:
252: }
253:
254: public var CN1:LocalConnection;
255: protected function LocalConnection_ASYNC_ERROR(e:AsyncErrorEvent):void{
256: Alert.show(e.toString());
257: trace (e.toString());
258: }
259: protected function LocalConnection_SECURITY_ERROR(e:SecurityErrorEvent):void{
260: Alert.show(e.toString());
261: trace(e.toString());
262: }
263: protected function LocalConnection_STATUS(e:StatusEvent):void{
264: //Alert.show(e.toString());
265: trace (e.toString());
266: }
Хандлеры ASYNC_ERROR и SECURITY_ERROR здесь особо не нужны, они никогда не срабатывают и остались в коде от отладки, а вот без ханлера LocalConnection_STATUS работать не получится - это обязательный хандлер.
Теперь посмотрим на приемную часть шлюза. Здесь все начинается с парсинга URL:
1: public var URL_Parm:URLprm = new URLprm();
2:
3: //разбор внешнего URL и позиционирование всех элементов по данным из URL
4: protected function ParseURL(URL:String):void{
5: var AllPRM:Array = URL.split(",");
6: try{
7: if (AllPRM.length>1){
8: for (var i:int=0;i<AllPRM.length;i++){
9: var OnePrm:Array = AllPRM[i].toString().split("=");
10: if (OnePrm.length==2){
11: switch (OnePrm[0])
12: {
13: case "Direction":
14: URL_Parm.IsDirection = OnePrm[1]=="true" ? false:true ;
15: break;
16: case "Return":
17: URL_Parm.IsReturn = OnePrm[1]=="true" ? true : false;
18: break;
19: case "FromDate":
20: URL_Parm.FromDate=OnePrm[1];
21: break;
22: case "ToDate":
23: URL_Parm.ToDate=OnePrm[1];
24: break;
25: case "ID":
26: URL_Parm.ID=OnePrm[1];
27: break;
28: case "CN":
29: URL_Parm.CN=OnePrm[1];
30: break;
31: }
32: }
33: }
34: }
35: }
36: catch(e:Object){
37: Alert.show(e.toString());
38: }
39: }
Как видите все параметры, считанные этой функцией из URL - у меня укладываются в типизированный класс URLprm:
1: package
2: {
3: public class URLprm
4: {
5: public var IsDirection:Boolean;
6: public var IsReturn:Boolean;
7: public var FromDate:String;
8: public var ToDate:String;
9: public var ID:String;
10: public var CN:String="INDIA";
11:
12: public function URLprm()
13: {
14: }
15: }
16: }
Чтобы дальнейший код был более понятен (я ведь родом из Бейсика, самого типизированного языка в мире, я привык к строгой типизации за много лет программирования на Бейсике), я покажу как и некоторые свои Enum - перечисления:
1: package
2: {
3: public final class TicketType
4: {
5: public static const Good:TicketType = new TicketType(Good);
6: public static const Less:TicketType = new TicketType(Less);
7: public static const More:TicketType = new TicketType(More);
8:
9: public function TicketType(value:TicketType)
10: {
11: }
12: }
13: }
Теперь, когда структура данных, с которой работает приемная часть шлюза - понятна, посмотрим на код инициализации шлюза:
140:
141: import valueObjects.AU;
142: import valueObjects.OneGUID;
143: import valueObjects.TicketRequest;
144:
145: import mx.managers.BrowserManager;
146: import mx.managers.IBrowserManager;
147:
148: protected var Browser:IBrowserManager;
149:
150: protected function application1_applicationCompleteHandler(event:FlexEvent):void
151: {
152: //Доступ к строке URL
153: Browser = BrowserManager.getInstance();
154: Browser.init();
155:
156: //инициализация от параметров URL
157: ParseURL(Browser.fragment);
158:
159: //проинициализировали и дернули web-сервис
160: WebServiceInit();
161: WebServiceStart();
162:
163: //открыли коннект к SWF с календариком
164: if (CN1==null) {
165: CN1 = new LocalConnection();
166: CN1.allowDomain("*")
167: }
168: try{
169: //Alert.show("Open "+ "_"+TST1.text);
170: //CN1.connect("_"+TST1.text);
171: CN1.connect("_"+URL_Parm.CN);
172: CN1.client=this;
173: CN1.addEventListener(SecurityErrorEvent.SECURITY_ERROR , LocalConnection_SECURITY_ERROR);
174: CN1.addEventListener(AsyncErrorEvent.ASYNC_ERROR , LocalConnection_ASYNC_ERROR);
175: CN1.addEventListener(StatusEvent.STATUS,LocalConnection_STATUS);
176: }
177: catch(e:Error){
178: Alert.show(e.toString());
179: trace (e.toString());
180: }
181: }
182:
183: public var CN1:LocalConnection;
184: protected function LocalConnection_ASYNC_ERROR(e:AsyncErrorEvent):void{
185: Alert.show(e.toString());
186: trace (e.toString());
187: }
188: protected function LocalConnection_SECURITY_ERROR(e:SecurityErrorEvent):void{
189: Alert.show(e.toString());
190: trace(e.toString());
191: }
192: protected function LocalConnection_STATUS(e:StatusEvent):void{
193: Alert.show(e.toString());
194: trace (e.toString());
195: }
В строке 169 вы видите закомментированный Alert, который сработал на самом первом рисунке в этой заметке, а в строке 170 закоментированный код враппера со второго скрина вначале этой заметки. Ниже обработчики событий, которые особо тут тоже не нужны и остались от процесса отладки.
А вот собственно публикуемая функция (которую вызывает другой SWF-ролик) выглядит вот так:
202: public function RefreshTicket():void{
203: //рефреш от SWF-рлика с календариком
204: //Alert.show("RefreshTicket: " + URL_Parm.CN + " " + Browser.fragment);
205:
206: //рефреш
207:
208: //Доступ к строке URL
209: Browser = BrowserManager.getInstance();
210: Browser.init();
211:
212: //инициализация от параметров URL
213: ParseURL(Browser.fragment);
214: //ParseURL(TST1.text);
215:
216: //проинициализировали и дернули web-сервис
217: WebServiceInit();
218: WebServiceStart();
219: }
Здесь в строке 204 вы видите как раз тот самый закомментированный Alert, который сработал на самом первом скрине в этой заметке.
В строке 161 вызывается асинхронный Web-сервис (Создание асинхронного прокси для обращения к WCF средствами Adobe flex builder), который долго-долго крутится, а потом в конечном итоге событие завершение обмена с сервером вызывает функцию - WebServiceEnd:
366: [Bindable]
367: protected var Tickets:ArrayCollection;
368:
369: //все обращения к сервисам завершились -> все вычитанные данные в Tickets
370: protected function WebServiceEnd():void{
371: if (TicketsGroup.dataProvider!==null){
372: TicketsGroup.dataProvider.removeAll();
373: TicketsGroup.validateNow();
374: TicketsGroup.dataProvider=Tickets;
375: TicketsGroup.validateNow();
376: }
377: else {
378: TicketsGroup.invalidateDisplayList();
379: TicketsGroup.dataProvider=Tickets;
380: }
381: }
Соответственно, эта функция заставляет начать рендеринг элемент DataGroup:
501: <s:DataGroup id="TicketsGroup" x="0" y="50" width="100%" height="100%" itemRenderer="OneTicketView" updateComplete="TicketsGroup_updateCompleteHandler(event)">
502: <s:layout>
503: <s:VerticalLayout gap="20"/>
504: </s:layout>
505: </s:DataGroup>
Который начинает отображать каждый билет из Tickets (куда описания билетов вставили асинхронные обращения к Web-сервисам). Соотвественно, один рендерер, отображающий билет выглядит вот так:
1: <?xml version="1.0" encoding="utf-8"?>
2: <s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
3: xmlns:s="library://ns.adobe.com/flex/spark"
4: xmlns:mx="library://ns.adobe.com/flex/mx" width="654" height="100"
5: creationComplete="bordercontainer1_contentCreationCompleteHandler(event)" >
6:
7: <fx:Metadata>
8: [Event(name="TicketSelected", type="flash.events.Event")]
9: </fx:Metadata>
10:
11: <fx:Script>
12: <![CDATA[
13: import mx.controls.Alert;
14: import mx.events.FlexEvent;
15:
16: [Embed(source="../Images/back.png")]
17: [Bindable]
18: public var BackImage:Class;
19:
20: [Embed(source="../Images/z4.gif")]
21: [Bindable]
22: public var NoImage:Class;
23:
24: [Embed(source="../Images/z5.gif")]
25: [Bindable]
26: public var YesImage:Class;
27:
28: [Embed(source="../Images/z1.jpg")]
29: [Bindable]
30: public var OkButtImage:Class;
31:
32: [Embed(source="../Images/z2.jpg")]
33: [Bindable]
34: public var GrayButtImage:Class;
35:
36: [Embed(source="../Images/z3.jpg")]
37: [Bindable]
38: public var NormButtImage:Class;
39:
40: [Embed(source="../Images/z4.jpg")]
41: [Bindable]
42: public var RedImage:Class;
43:
44: import valueObjects.OneTicket;
45:
46: private var _TicketType:TicketType;
47: private var _USDCurs:Number;
48: private var _DirectTicketInfo:valueObjects.OneTicket;
49: private var _ReturnTicketInfo:valueObjects.OneTicket;
50:
51: protected function FormatPrice():String{
52: var NF:NumberFormatter = new NumberFormatter();
53: var DirectCost:Number=0;
54: var ReturnCost:Number=0;
55: var RubCost:Number;
56: if (_DirectTicketInfo !==null){
57: DirectCost = _DirectTicketInfo.Price;
58: }
59: if (_ReturnTicketInfo!==null){
60: ReturnCost = _ReturnTicketInfo.Price;
61: }
62: RubCost = _USDCurs * (DirectCost + ReturnCost);
63: return NF.format(RubCost);
64: }
65:
66:
67: protected function bordercontainer1_contentCreationCompleteHandler(event:FlexEvent):void
68: {
69: _TicketType = data.TicketType;
70: _USDCurs = data.USDCurs;
71: _DirectTicketInfo = data.DirectTicketInfo;
72: _ReturnTicketInfo = data.ReturnTicketInfo;
73: if (_DirectTicketInfo !== null){
74: L1.text = FormatDayNames(_DirectTicketInfo.FromDate);
75: L2.text = _DirectTicketInfo.FromAirport + " " + _DirectTicketInfo.FromTime;
76: L3.text = _DirectTicketInfo.ToAirport + " " + _DirectTicketInfo.FlyTime;
77: L4.text = _DirectTicketInfo.AviaCompanyCode + " " + _DirectTicketInfo.FlyNumber;
78: L5.text = _DirectTicketInfo.HowMany;
79: }
80: ...
Как видите, я показал не только собственно код шлюза, но и показал как этот несложный шлюз вписывается в общую структуру приложения. Хотя надо сказать, что из всех возможных шлюзов - это самый простой.
Для примера можете почитать про тысячекратно более обьемные шлюзы:
- Шлюз к 1С по протоколу Битрикс
- Шлюзы к платежным системам интернет-денег
- WebActivator - клиент/сервер защиты от копирования для платных программ
- Remote SQL execute for PostgreSQL on GSM/GPRS channel with extreme compress and cryptography
- SQL-Client_for_remote_XML-WebService - клиент meteonova.ru
- Скачка, раззиповка, перекодирование, парсинг и укладка в базу ЖД-расписания из АСУ Экспресс-3
- Как сделать SOAP/WSDL-вебсервис на ASP.NET/MONO для вызова его из FLEX
- WCF_CLIENT - клиент Web-сервиса (вторая версия)
Ну собственно, если поискать, то в моем блоге описаны ДЕСЯТКИ СЛОЖНЫХ ШЛЮЗОВ - а теперь мой блог пополнился описанием простейшего шлюза на Flex для связи по LocalConnection.
|