Очередь асинхронных операций
Привет. Сегодня речь пойдёт о том, как облегчить создание программ, использующих асинхронные операции. Я имею ввиду загрузку мультимедийного контента, асинхронные запросы к базе данных и т.д.
Цель
Техническое задание
Реализовать возможность загрузки и хранения изображений в одном классе. Этот класс должен быть реализован на основе шаблона проектирования Singleton. Реализовать возможность последовательной загрузки нескольких изображений. Реализовать возможность выполнения методов как после загрузки определённого ресурса, так и после всех заданных загрузок.
Решение
Поговорим немного об асинхронных операциях. Flash не поддерживает многопоточность, но зато у него реализована возможность асинхронного выполнения кода. Что это значит? Если нам нужно загрузить какой-либо ресурс, например изображение или текстовый файл, то после запуска метода типа load() программа не останавливается, чтобы дождаться момента, когда ресурс будет полностью загружен. Тоже самое происходит с запросами к серверу, если мы разрабатываем браузерное приложение. После отправления запроса к серверу, программа продолжает работать и выполнять дальнейшие инструкции. О том, что сервер прислал ответ на запрос программа узнаёт после возникновения определённого события. С одной стороны это большой плюс, т.к. для пользователя подобные операции будут протекать незаметно. Но с другой стороны это может вызвать трудности при разработке.
В этом уроке я расскажу как сделать простейший менеджер ресурсов, который будет загружать изображения и хранить их на всём жизненном цикле программы. Для последовательной загрузки будем использовать очередь загрузок. Т.е. загрузка очередного файла происходит после загрузки предыдущего. Все загруженные данные будут храниться в объекте типа Dictionary. Данный класс по структуре напоминает класс HashMap из языка Java, т.е. в качестве ключей словаря могут использоваться не только строки, но и любые другие объекты.
Запускаем Flash Builder и создаём проект Action Script. Назовём его QueueTest. Сразу начнём с создания класса SourceManager. Определим, что нам нужно. Во первых, для реализации шаблона Singleton нужна переменная instance. Во-вторых, нужен диспетчер событий для информирования о завершении очереди загрузок и статичная константа для типа события. Также нужен массив имён загружаемых файлов, хранилище данных, хранилище хэндлеров и несколько ссылок для выполнения очередной загрузки. Вот как выглядит наш класс в первоначальном виде:
package
{
import flash.display.*;
import flash.events.*;
import flash.events.EventDispatcher;
import flash.utils.Dictionary;
public class SourceManager
{
/** Тип события, возникающего после загрузки всех файлов в очереди */
public static const LOADING_COMPLETE:String = 'loading_complete';
protected static var _instance:SourceManager; //тут храним объект данного класса
protected var _dispatcher:EventDispatcher; //диспетчер для создания события
protected var _queue:Vector.<String>; //вектор имён файлов
protected var _handlers:Dictionary; //словарь функций для вызова после загрузки определённого файла
protected var _busy:Boolean; //флаг выполнения очереди
protected var _currentFileName:String; //имя очередного файла
protected var _currentLoader:Loader; //загрузчик очередного файла
protected var _dataStore:Dictionary; //хранилище данных
public function SourceManager(singleton:Singleton)
{
_dispatcher = new EventDispatcher();
_queue = new Vector.;
_handlers = new Dictionary();
_busy = false;
_dataStore = new Dictionary();
}
public static function get instance():SourceManager
{
if (!_instance)
_instance = new SourceManager(new Singleton());
return _instance;
}
}
}
class Singleton
{
//Реализация шаблона Singleton
}
С помощью внутреннего класса Singleton мы исключаем возможность создания объекта SourceManager в любом месте приложения.
На самом деле в нашем примере можно использовать обычный массив вместо класса Vector, а класс Dictionary заменить на Object. Но задача урока рассмотреть именно данные классы, поэтому я буду использовать их.
Теперь реализуем очередь загрузки файлов. При вызове метода загрузки мы не создаём загрузчика, а всего лишь добавляем имя файла в вектор имён, из которого последовательно будем эти имена доставать. Для загрузки изображений используется класс Loader. Это необходимо учесть, т.к. загрузку текстовых файлов, файлов XML необходимо реализовывать с помощью класса URLLoader. Ниже я приведу полный листинг метода загрузки и поясню некоторые моменты в комментариях:
public static function loadSource(fileName:String, handler:Function = null):void
{
//через статичный метод класса вызываем метод экземпляра
_instance.loadFile(fileName, handler);
}
protected function loadFile(fileName:String, handler:Function):void
{
//добавляем имя файла в очередь
_queue.push(fileName);
//если указан метод для выполнения после загрузки данного файла, запоминаем его
if (handler != null)
_handlers[fileName] = handler;
//если ничего не загружается, то запускаем очередь
if (!_busy)
{
loadNextFile();
_busy = true;
}
}
protected function loadNextFile():void
{
//запоминаем текущее имя файла и создаём текущий загрузчик
_currentFileName = _queue.shift();
_currentLoader = new Loader();
//вешаем слушателей загрузчику
addHandlers();
//загружаем
_currentLoader.load(new URLRequest(_currentFileName));
}
protected function addHandlers():void
{
_currentLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
_currentLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
}
protected function completeHandler(event:Event):void
{
//удаляем слушателей загрузчика
removeHandlers();
//сохраняем загруженные данные
_dataStore[_currentFileName] = _currentLoader.content;
//если у текущего файла был хэндлер, то выполняем его
var handler:Function = _handlers[_currentFileName] as Function;
if (handler != null)
{
handler(_currentLoader.content);
//...и удаляем после выполнения
_handlers[_currentFileName] = null;
delete _handlers[_currentFileName];
}
//проверяем очередь загрузок
checkQueue();
}
protected function ioErrorHandler(event:IOErrorEvent):void
{
removeHandlers();
trace(event.text);
checkQueue();
}
protected function removeHandlers():void
{
_currentLoader.removeEventListener(Event.COMPLETE, completeHandler);
_currentLoader.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
}
protected function checkQueue():void
{
//если в очереди есть файлы, то загружаем очередной
if (_queue.length > 0)
loadNextFile();
//либо сбрасываем флаг и создаём событие
else
{
_busy = false;
_dispatcher.dispatchEvent(new Event(LOADING_COMPLETE));
}
}
Теперь чтобы получить загруженный файл по имени, нужно обратиться к хранилищу данных по ключу, в качестве которого это имя используется. Главный класс проекта будет выглядеть следующим образом
public static function getSource(fileName:String):DisplayObject
{
//через статичный метод класса вызываем метод экземпляра
return _instance.getFile(fileName);
}
protected function getFile(fileName:String):DisplayObject
{
//получаем данные из хранилища
var data:DisplayObject = _dataStore[fileName];
//если данные существуют, то возвращаем
if (data)
return data;
//если нет, то возвращаем null
trace('Файл "' + fileName + '" не загружен');
return null;
}
Теперь осталось протестировать наш класс. Для этого в папке с проектом создадим папку assets и положим туда 4 изображения. Их мы будем загружать. Для 2-го изображения создадим хэндлер, который выполниться сразу после загрузки.
package
{
import flash.display.*;
import flash.events.Event;
public class QueueTest extends Sprite
{
public function QueueTest()
{
//стандартно для проекта Action Script
if (stage)
init();
else
addEventListener(Event.ADDED_TO_STAGE, init);
}
protected function init(event:Event = null):void
{
//изменяем свойства приложения
removeEventListener(Event.ADDED_TO_STAGE, init);
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
//сразу вешаем слушатель на загрузку всех файлов
SourceManager.instance.dispatcher.addEventListener(SourceManager.LOADING_COMPLETE, allFilesLoaded);
//...и загружаем
SourceManager.loadSource('../assets/pict01.jpg');
SourceManager.loadSource('../assets/pict02.jpg', file2Loaded); //указываем хэндлер
SourceManager.loadSource('../assets/pict03.jpg');
SourceManager.loadSource('../assets/pict04.jpg');
}
protected function file2Loaded(data:DisplayObject):void
{
trace('Файл 2 загружен');
stage.addChild(data);
}
protected function allFilesLoaded(event:Event):void
{
SourceManager.instance.dispatcher.removeEventListener(SourceManager.LOADING_COMPLETE, allFilesLoaded);
trace('Все файлы загружены')
testSource();
}
protected function testSource():void
{
var pict:DisplayObject;
//показываем загруженный файл
pict = SourceManager.getSource('../assets/pict04.jpg');
if (pict)
{
pict.x = 200;
stage.addChild(pict);
}
//пытаемся получить не загруженный файл
pict = SourceManager.getSource('../assets/pict05.jpg');
if (pict)
{
pict.x = 400;
stage.addChild(pict);
}
}
}
}
Готово. Запускаем проект и видим 2 изображения и 3 сообщения в отладчике. С ними, я думаю, вы разберётесь сами.
Данный проект — всего лишь пример использования очереди. Эту технику можно использовать и при получении данных с сервера, либо при создании диалогов в играх и .т.д. Фантазия подскажет. Удачных проектов…
Результат
Тэги: action script 3.0, программирование, средние
Ссылка для вашего сайта.
Комментарии (2)
Роман
| #
Интересная статья. У меня только возник вопрос по реализации Singleton.
Когда то вычитал вот такой способ:
public function SourceManager(caller:Function = null) { if (caller != SourceManager.instance) { throw new Error ("SourceManager is a singleton class, use instance() instead."); } if (SourceManager.instance != null) { throw new Error( "Only one SourceManager instance should be instantiated" ); } /** Все остальное */ } public static function get instance():SourceManager { if (!_instance) { _instance = new SourceManager(arguments.calee); } return _instance; }Вот у меня и возник вопрос, а не будет ли вышеизложенный вариант более корректной реализацией для данного шаблона? Если нет, то почему?
Ответить
Андрей
| #
Здравствуйте, Роман. Хороший вопрос. На самом деле я взял за основу пример реализации Singleton из книги «Action Script 3.0 Шаблоны проектирования». Но, действительно, сейчас обнаружил небольшую недоработку. О ней чуть ниже.
Что касается Вашего вопроса. Лично мне вариант с внутренним классом нравится больше, т.к. при использовании конструктора класса
SourceManager()возникнет ошибка на этапе компиляции. Конструктор требует параметрsingleton:Singleton. А это внутренний класс, экземпляр которого можно создать только в классеSourceManagerВ Вашем случае ошибка возникнет только на этапе выполнения. Это может создать дополнительные неудобства. Лучше увидеть ошибку сразу, чем когда приложение откомпилировано и загружено на сервер.Теперь о доработке моего примера. Очевидно, ошибку компиляции можно обойти, передав в качестве параметра
null. Для того, чтобы нельзя было таким образом создать экземпляр классаSourceManager, нужно добавить проверку наnullв конструктор. К сожалению, эта ошибка возникнет только на этапе выполнения.Ответить