starting an Adobe AIR application multiple times
Asked Answered
M

6

13

Adobe air runtime prevents more than one instance of an air application to be started at the same time. Is it safe to circumvent this restriction by arbitrarily changing the publisher ID? Does anyone know if Adobe plans to allow multiple concurrent instances in Air 2.0?

Milline answered 7/2, 2010 at 15:48 Comment(1)
Good answers please to earn the bounty.Milline
C
22

We successful implemented a hack to circumvent this limitation, in a pure AIR way, without having to change the publisher id (which needs multiple certificates, I think).

As you know, AIR is implementing its Mutex by using a unique application identifier. This identifier is calculated using the application id, and the publisher identifier (extracted from the certificate that signed the application).

In the installation directory of an AIR application, there is a META-INF folder (or in /share/ using Linux). This META-INF folder contains an AIR folder, which contains an "application.xml" file. This file contains a <id /> tag that defines the application identifier, which is used in the calculation of the mutex identifier. If your application can write in the installation folder, you can use the File API to edit it at runtime, randomly changing the <id /> tag, allowing multiple processes of the same application to be run at the same time.

This is having some annoying side effects, like creating a new folder in the File.applicationStorageDirectory folder every time. But using a LocalConnection, you can minimize this by reusing the same identifier multiple times by logging which ones are free to be reused. Also, SharedObject are stored in this folder, so cannot be used (or have to be copied every time a new instance is created, and synchronized though LocalConnection).

As far as I know, Adobe isn't planning to remove this native limitation. It was implemented for multi-platforming purposes, specifically on MacOS, where the dock is making that more complicated (it's not very easy to start the same application twice with the dock).

The official way to do that is to catch the InvokeEvent.INVOKE event, and do stuff like opening a new window. And there's no change planned for AIR 2.0 in this behaviour.

Celandine answered 17/2, 2010 at 8:33 Comment(0)
H
1

Would it help if you encapsulate the logic of your application as a class that could run in a window and allow the user to create multiple instances of that window, within one app ? would that help ?

What is the main reason you would need multiple applications ?

Hoover answered 14/2, 2010 at 19:35 Comment(5)
Thanks for the answer, but I am specifically looking for separate processes. There are a number of benefits, for example parallel execution on multi-processor computers.Milline
I see, thanks for the explanation. Good question. You can have separate processes in Java...this might be way to long winded, would interfacing AIR and Java through Merapi(merapiproject.net) do any good ?Hoover
Again, interesting post, but it is off topic. Merapi is an IPC framework rather than a link framework (such as JNI), so it isn't possible to link to a multi-threaded java library; there would have to be a separately started java process. Furthermore, using a java helper would only provide parallel processing, not other advantages of multiple processes. Let's return to the original question: I am looking for a way to start several instances of a Flex AIR app, and specifically whether running the same swf with multiple publisher IDs is legal in the AIR runtime.Milline
just out of curiosity: are air apps limited to one thread only?Pilot
Lorenzo: Yes, limited to one, it seems. flexjunk.com/2009/01/15/multi-threading-in-flexairHallerson
C
1

This will break auto update, be warned.

Coumas answered 20/12, 2012 at 8:39 Comment(0)
C
1

Air Application duplicator will help you in this part. Using this you can run multiple instance of same AIR application.

https://github.com/chrisdeely/AirAppDuplicator

Its just simply copying your app directory with new name and new application id.

Cadman answered 25/2, 2014 at 4:2 Comment(0)
G
1

Just made a quick class to implement Tyn's solution. Simply call. MultipleInstanceAirApp.changeMetaInfId(stage);

You don't really need the stage part, but I use it to change the position of the window when I test. Anyway, enjoy!

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) ;
    }
}
Garrido answered 18/6, 2016 at 5:52 Comment(0)
C
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) { }
        }
    }   
}
Cele answered 3/5, 2017 at 14:35 Comment(1)
Welcome to Stack Overflow! Whilst this code snippet is welcome, and may provide some help, it would be greatly improved if it included an explanation of how it addresses the question. Without that, your answer has much less educational value - remember that you are answering the question for readers in the future, not just the person asking now! Please edit your answer to add explanation, and give an indication of what limitations and assumptions apply.Gibe

© 2022 - 2024 — McMap. All rights reserved.