多次启动Adobe AIR应用程序

13

Adobe AIR运行时会防止在同一时间启动多个AIR应用程序实例。通过任意更改发布者ID来规避此限制是否安全?有人知道Adobe是否计划在Air 2.0中允许多个并发实例吗?


请给出好的答案以赢得奖励。 - abc
6个回答

22
我们成功地实现了一种绕过该限制的黑客方式,以纯AIR方式进行操作,无需更改发布者ID(这需要多个证书,我想)。
如您所知,AIR使用唯一的应用程序标识符来实现其互斥锁。该标识符是使用应用程序ID和发布者标识符(从签署应用程序的证书中提取)计算出来的。
在AIR应用程序的安装目录中,有一个 META-INF 文件夹(或者在 Linux中使用 /share/ )。此 META-INF 文件夹包含一个 AIR 文件夹,其中包含一个 "application.xml" 文件。此文件包含一个 <id /> 标记,定义了应用程序标识符,用于计算互斥标识符。如果您的应用程序可以写入安装文件夹,则可以使用 File API 在运行时编辑它,随机更改 <id /> 标记,允许多个相同应用程序的进程同时运行。
这会产生一些烦人的副作用,比如每次创建一个新的文件夹在 File.applicationStorageDirectory 文件夹中。但是使用 LocalConnection,您可以通过记录哪些标识符可供重用来最小化这种情况。此外,SharedObject 存储在此文件夹中,因此无法使用(或必须在创建新实例时每次复制并通过 LocalConnection 进行同步)。
据我所知,Adobe 不打算删除这种本地限制。它是为多平台目的而实现的,特别是在 MacOS 上,其中 Dock 使这更加复杂(使用 Dock 启动相同的应用程序两次不太容易)。
官方方法是捕获 InvokeEvent.INVOKE 事件,并执行诸如打开新窗口之类的操作。AIR 2.0 中没有计划更改此行为。

1

我刚刚编写了一个快速实现Tyn方案的类。只需要调用MultipleInstanceAirApp.changeMetaInfId(stage);即可。

你不一定需要stage部分,但我在测试时使用它来改变窗口位置。 无论如何,祝你使用愉快!

import flash.display.Stage;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.utils.ByteArray;

/**
 * @author Lachhh
 */
public class MultipleInstanceAirApp {
    static private var loadFile : File;
    static private var thePath:String = "./META-INF/AIR/application.xml";
    static private var myGameId:String = "YOUR_GAME_ID";
    static private var stage:Stage ;
    static private var metaInfString:String ;

    static public var instanceNumber:int = 0;

    static public function changeMetaInfId(pStage:Stage):void {
        stage = pStage;
        var path:String = File.applicationDirectory.resolvePath(thePath).nativePath; 
        loadFile = new File(path);

        loadFile.addEventListener(Event.COMPLETE, onLoadMetaInf);
        loadFile.addEventListener(IOErrorEvent.IO_ERROR, onIoError);
        loadFile.load();
    }

    private static function onLoadMetaInf(event : Event) : void {
        loadFile.removeEventListener(Event.COMPLETE, onLoadMetaInf);
        metaInfString = loadFile.data.toString();
        replaceMetaInfIdIfFound();
        saveStringToMetaInf(metaInfString);
    }

    static public function saveStringToMetaInf(s:String):void {
        var b:ByteArray = new ByteArray();
        b.writeUTFBytes(s);
        saveFile(b);
    }

    static public function saveFile(data:ByteArray):void {
        var thePath:String = File.applicationDirectory.resolvePath(thePath).nativePath;     
        var saveFile:File = new File(thePath);
        var fileStream:FileStream = new FileStream();
        fileStream.openAsync(saveFile, FileMode.WRITE);
        fileStream.writeBytes(data);
        fileStream.addEventListener(Event.CLOSE, onClose);
        fileStream.close();
    }

    static private function replaceMetaInfIdIfFound():void {
        if(checkToReplaceId(1, 2)) return ;
        if(checkToReplaceId(2, 3)) return ;
        if(checkToReplaceId(3, 4)) return ;
        checkToReplaceId(4, 1);

    }

    static private function checkToReplaceId(i:int, newI:int):Boolean {
        var id:String = getGameIdWithBrackets(i);
        var newId:String = getGameIdWithBrackets(newI);
        if(metaInfString.indexOf(id) != -1) {
            metaInfString = myReplace(metaInfString, id, newId);
            instanceNumber = newI;
            return true;
        }
        return false;
    }

    private static function onClose(event : Event) : void {
        trace("all done!");
        placeScreenAccordingToInstanceNumber();
    }

    static private function placeScreenAccordingToInstanceNumber():void {;
        switch(instanceNumber) {
            case 1 : 
                stage.nativeWindow.x = 115;
                stage.nativeWindow.y = 37;
                break;
            case 2 : 
                stage.nativeWindow.x = 115 + 660;
                stage.nativeWindow.y = 37;
                break;
            case 3 : 
                stage.nativeWindow.x = 115;
                stage.nativeWindow.y = 37 + 380;
                break;
            case 4 : 
                stage.nativeWindow.x = 115 + 660;
                stage.nativeWindow.y = 37 + 380;
                break;
        }
    }

    private static function onIoError(event : IOErrorEvent) : void {
        trace("io Error");
    }

    static private function getGameIdOriginalWithBrackets():String {
        return "<id>" + myGameId + "</id>";
    }

    static private function getGameIdWithBrackets(i:int):String {
        if(i == 1) return getGameIdOriginalWithBrackets();
        return "<id>" + myGameId + i + "</id>";
    }

    static public function myReplace(msg:String, toFind:String, toBeReplacedWith:String):String {
        return msg.split(toFind).join(toBeReplacedWith) ;
    }
}

1

如果您将应用程序的逻辑封装为可以在一个窗口中运行并允许用户创建该窗口的多个实例的类,这是否会有所帮助?那会有所帮助吗?

您需要多个应用程序的主要原因是什么?


谢谢您的回答,但我特别寻找独立的进程。这有许多好处,例如在多处理器计算机上进行并行执行。 - abc
我明白了,谢谢解释。好问题。在Java中,您可以拥有单独的进程...但这可能会让事情变得太冗长。通过Merapi(http://merapiproject.net/)来接口AIR和Java是否有好处? - George Profenza
再次,有趣的帖子,但它与主题无关。Merapi是一个IPC框架而不是链接框架(如JNI),因此不可能链接到多线程Java库;必须有一个单独启动的Java进程。此外,使用Java助手只会提供并行处理,而不是多个进程的其他优点。让我们回到最初的问题:我正在寻找一种启动多个Flex AIR应用程序实例的方法,特别是在AIR运行时中使用多个发布者ID运行相同的swf是否合法。 - abc
只是出于好奇,Air应用程序是否仅限于一个线程? - Lorenzo Boccaccia
Lorenzo:是的,似乎只限于一个。http://www.flexjunk.com/2009/01/15/multi-threading-in-flexair/ - aaaidan

1

这将会破坏自动更新,请注意。


1
应用程序复制器将在这个过程中帮助您。使用它,您可以运行多个相同的AIR应用程序实例。

https://github.com/chrisdeely/AirAppDuplicator

只需将您的应用程序目录复制一份,并更改名称和应用程序ID。


0
package hobis.airpc 
{
    import flash.events.Event;
    import flash.filesystem.File;   
    import flash.filesystem.FileMode;
    import flash.filesystem.FileStream;
    import flash.utils.ByteArray;
    import jhb0b.utils.MArrayUtil;

    public final class MAppXmlUpdateCounter
    {
        private static var _AppXmlFile:File;

        public static function Update():void
        {
            _AppXmlFile = new File(File.applicationDirectory.nativePath);           
            _AppXmlFile = _AppXmlFile.resolvePath('META-INF\\AIR\\application.xml');
            _AppXmlFile.addEventListener(Event.COMPLETE, ppOpened);
            _AppXmlFile.load();         
        }

        private static function ppOpened(evt:Event):void
        {
            const trx1:RegExp = /<id>[\s\S]*?<\/id>/;
            const trx2:RegExp = /<([^>]+)>/g;

            var tXmlStr:String = _AppXmlFile.data.toString();
            var tMatArr:Array = tXmlStr.match(trx1);
            if (!MArrayUtil.is_empty(tMatArr))
            {
                var tIdTagStr:String = tMatArr[0];
                var tIdValStr:String = tIdTagStr.replace(trx2, '');

                var tOriVal:String;
                var tNumVal:uint;
                var tStrArr:Array = tIdValStr.split('-');
                if (tStrArr != null)
                {
                    if (tStrArr.length == 2)
                    {
                        tOriVal = tStrArr[0];
                        tNumVal = int(tStrArr[1]);                              
                    }
                    else
                    if (tStrArr.length == 1)
                    {
                        tOriVal = tStrArr[0];
                        tNumVal = 0;
                    }
                    tNumVal++;

                    var tIdNewStr:String = '<id>' + tOriVal + '-' + tNumVal + '<\/id>';                 
                    var tNewXmlStr:String = tXmlStr.replace(tIdTagStr, tIdNewStr);                  
                    ppSaveFile(tNewXmlStr);
                }
            }
            _AppXmlFile = null;
        }

        private static function ppSaveFile(val:String):void
        {
            var tfs:FileStream;
            try
            {
                tfs = new FileStream();
                tfs.openAsync(_AppXmlFile, FileMode.WRITE);
                var tba:ByteArray = new ByteArray();
                tba.writeUTFBytes(val);         
                tfs.writeBytes(tba);                
                tba.clear();
            }
            catch (e:Error) { }
            try
            {
                tfs.close();
            }
            catch (e:Error) { }
        }
    }   
}

欢迎来到Stack Overflow!虽然这段代码片段很受欢迎,也许能提供一些帮助,但如果它包含了解决问题的方法的说明,那将会大有改进。没有这个,你的答案就会少很多教育价值——记住,你正在回答未来读者的问题,而不仅仅是现在提问的人!请编辑你的答案以添加说明,并指出哪些限制和假设适用。 - Toby Speight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接