Время автономной работы Adobe предотвращает одновременное запуск более одного экземпляра воздушного приложения. Безопасно ли обойти это ограничение путем произвольного изменения идентификатора издателя? Кто-нибудь знает, планирует ли Adobe разрешить несколько параллельных экземпляров в Air 2.0?
Запуск приложения Adobe AIR несколько раз
Ответ 1
Мы успешно реализовали хак, чтобы обойти это ограничение в чистом режиме AIR, не изменяя идентификатор издателя (для которого, как мне кажется, требуется несколько сертификатов).
Как вы знаете, AIR реализует свой Mutex с помощью уникального идентификатора приложения. Этот идентификатор вычисляется с использованием идентификатора приложения и идентификатора издателя (извлеченного из сертификата, который подписал приложение).
В каталоге установки приложения AIR есть папка META-INF (или в /share/using Linux). Эта папка META-INF содержит папку AIR, содержащую файл "application.xml". Этот файл содержит тег <id />
, который определяет идентификатор приложения, который используется при вычислении идентификатора мьютекса. Если ваше приложение может писать в установочной папке, вы можете использовать API File
, чтобы редактировать его во время выполнения, произвольно меняя тэг <id />
, позволяя одновременно запускать несколько процессов одного и того же приложения.
У этого есть некоторые раздражающие побочные эффекты, такие как создание новой папки в папке File.applicationStorageDirectory
каждый раз. Но используя LocalConnection
, вы можете свести к минимуму это, повторно используя один и тот же идентификатор несколько раз, регистрируя, какие из них могут быть повторно использованы. Кроме того, SharedObject
хранятся в этой папке, поэтому их нельзя использовать (или их нужно копировать каждый раз, когда создается новый экземпляр, и синхронизируются, хотя LocalConnection
).
Насколько я знаю, Adobe не планирует удалять это собственное ограничение. Он был реализован для многоплатформенных целей, особенно на MacOS, где док делает это более сложным (не так просто запустить одно и то же приложение дважды с док-станцией).
Официальный способ сделать это - поймать событие InvokeEvent.INVOKE
и сделать что-то вроде открытия нового окна. И никаких изменений, запланированных для AIR 2.0 в этом поведении, не было.
Ответ 2
Помогло бы вам инкапсулировать логику вашего приложения как класс, который мог бы запускаться в окне, и позволить пользователю создавать несколько экземпляров этого окна в одном приложении? это поможет?
В чем основная причина, по которой вам потребуется несколько приложений?
Ответ 3
Дупликатор Air Application поможет вам в этой части. Используя это, вы можете запускать несколько экземпляров одного и того же приложения AIR.
https://github.com/chrisdeely/AirAppDuplicator
Простое копирование вашего каталога приложений с новым именем и новым идентификатором приложения.
Ответ 4
Это прервет автоматическое обновление, будет предупреждено.
Ответ 5
Просто сделал быстрый класс для реализации решения Tyn. Просто позвоните. MultipleInstanceAirApp.changeMetaInfId(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) ;
}
}
Ответ 6
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) { }
}
}
}