PDA

Просмотр полной версии : Необходимость удаления обработчиков событий


MerlinTwi
08.09.2006, 15:51
Такой пример, создаем мувик, ему добавляем обработку события ENTER_FRAME, по которому просто делаем trace и добавляем возможность по клику удалить этот мувик. Примерно так:
package {
import flash.display.Sprite;
import flash.events.*;

dynamic public class Test extends Sprite {

function Test():void {
addEventListener(Event.ENTER_FRAME, doEnterFrame);
addEventListener(MouseEvent.CLICK, doClick);
}

function doEnterFrame(e:Event):void {
trace(e);
}

function doClick(e:Event):void {
parent.removeChild(this);
}
}
}

Так вот, после клика и исчезновения мувика, продолжает вызываться doEnterFrame! Хотя событие было привязано к мувиклипу, которого уже якобы нет. На самом деле, этот мувик garbage collector не удаляет, т.к. на него осталось зарегистрировано событие.
Собственно если внимательно прочитать Help по addEventListener, там об этом четко говорится.
You should remove an event listener when it is no longer needed by calling EventDispatcher.removeEventListener(). Failure to remove unnecessary event listeners may have a negative impact on memory usage. Any objects with registered event listeners are not removed from memory because the garbage collector does not remove objects that still have references.
По-русски, обязательно удалять все обработчики событий (removeEventListener), когда они более не нужны, иначе объект продолжает болтаться в памяти!
Подобное положение дел, лично меня совсем не радует. Т.е. в каждом классе, который обрабатывает любые события, нужно добавлять две функции open() для инициализации обработчков событий и close() для их удаления, которую нужно вызывать перед удалением этого класса. Т.е. просто removeChild уже не достаточно. Или же в каждом классе самому следить за событием removed, по которому удалять все обработчики за собой. Примерно так:
package {
import flash.display.Sprite;
import flash.events.*;

dynamic public class Test extends Sprite {

function Test():void {
addEventListener(Event.REMOVED, onRemoved);
addEventListener(Event.ENTER_FRAME, doEnterFrame);
addEventListener(MouseEvent.CLICK, doClick);
}

function onRemoved(e:Event):void {
trace("onRemoved");
removeEventListener(Event.REMOVED, onRemoved);
removeEventListener(Event.ENTER_FRAME, doEnterFrame);
removeEventListener(MouseEvent.CLICK, doClick);
}

function doEnterFrame(e:Event):void {
trace(e);
}

function doClick(e:Event):void {
parent.removeChild(this);
}
}
}

Что не совсем верно, т.к. удаление из списка отображаемых объектов еще не означает, что этот объект собираются полностью удалить. Да и сколько получается лишней писанины! Хотелось бы более простого решения, чтобы назначенные обработчики событий не препятствовали удалению объекта.
У функции addEventListener последним параметром идет
useWeakReference:Boolean (default = false) — Determines whether the reference to the listener is strong or weak. A strong reference (the default) prevents your listener from being garbage collected. A weak reference does not. The default value of this parameter is false.
Как я понял, именно то что надо, т.е. если вызвать
addEventListener(Event.ENTER_FRAME, doEnterFrame, false,0,true);
то объект должен удалиться даже без явного удаления этого обработчика события. Однако, это не работает :eek:
Собственно вот, подскажите где я что неправильно понял...

Nirth
08.09.2006, 15:58
removeChild не удаляет экземпляр класса DisplayObject или его наследника, а просто убирает его из контейнера. клип все еще существует, и события все еще работают.

miramax
08.09.2006, 16:02
Да с этим я тоже не разобрался... А ещё если в объекте использовать setInterval. То после удаления объекта, "интервал" продолжает вызывать функцию. И ругается что не может её найти, потому что объета не сущетсвует.
Приходится каждый раз писать обработчик события removed, что бы убить все интервалы и листенеры.

MerlinTwi
08.09.2006, 16:06
Верно. И если после removeChild на клип не осталось других ссылок, то клип удаляется полностью (теоретически, как это проверить не знаю).
Но если у клипа были добавлены какие-либо события, то он не удалится, пока их не освободит.

MerlinTwi
08.09.2006, 18:03
Ваще пипец...
Кнопка (клип типа Button) при изменении состояний Up-Over-Down (мышку над ней провели) генерит событие Event.REMOVED, которое могут ловят все ее родители :)

Nirth
08.09.2006, 18:19
аксиома ас1-2 :не используйте кнопки юзайте мувиклипы, в ас3 претерпела совсем немного изменений : не юзайте кнопки юзайте спрайты

FlexBuilder
09.09.2006, 01:16
По поводу garbage collector-а... Как он коллектит гарбадж - только ему самому известно. Но то, что он его коллектит - это факт.
По моим наблюдениям убивание ссылок на объект, не приводит к мгновеному физическому уничтожению объекта из памяти - то же касается обработчиков с WeakReference, но через некоторое время объект все-таки удаляется. В частности после удаления последней ссылки обработчик onEnterFrame может еще сработать до 2000 раз, после чего останавливается. Из чего можно сделать вывод, что garbage collector чистит память либо с некоторым периодом, либо с некоторой задержкой.
По поводу setInerval - не рекомендуется использовать по соображениям стиля программирования. Эта процедура, скажем так, не вполне объектно-ориентированная, со всеми вытекающими последствиями.

MerlinTwi
11.09.2006, 15:06
...то же касается обработчиков с WeakReference, но через некоторое время объект все-таки удаляется. В частности после удаления последней ссылки обработчик onEnterFrame может еще сработать до 2000 раз, после чего останавливается...
Можно пример? Вот такой скрипт
package {
import flash.display.Sprite;
import flash.events.*;
import flash.utils.*;

dynamic public class Test extends Sprite {
private var i:Number = 0;

function Test():void {
addEventListener(Event.ENTER_FRAME, doEnterFrame, false,0,true);
addEventListener(MouseEvent.CLICK, doClick, false,0,true);
}

function doEnterFrame(e:Event):void {
trace(getTimer());
}

function doClick(e:Event):void {
parent.removeChild(this);
}
}
}

После клика и исчезновения проработал 2 часа и останавливаться не собирался.

artcraft
11.09.2006, 22:15
A *very* important thing to understand about the Garbage Collector in FP9 is that it's operations are deferred. Your objects will not be removed immediately when all active references are deleted, instead they will be removed at some indeterminate time in the future (from a developer standpoint). The GC uses a set of heuristics that look at RAM allocation and the size of the memory stack (among other things) to determine when to run. As a developer, you must accept that fact that you will have no way of knowing when (or even if) your inactive objects will get deallocated. You must also be aware that inactive objects will continue to execute indefinitely (until the GC deallocates it), so code will keep running (ex. enterFrames), sounds will keep playing, loads will keep happening, events will keep firing, etc.

It's very important to remember that you have no control over when your objects will be deallocated, so you must make them as inert as possible when you are finished with them.


вот

artcraft
11.09.2006, 22:50
Unsupported Way to Force GC
There is a trick that will let you force the Flash player to carry out a full GC pass. This trick can be really handy for exploring Garbage Collection, and testing your applications during development, but it should never be deployed in production code because it can wreak havoc with processor load. It is also officially unsupported, so you cannot rely on it to work in updated versions of the player.

To force an immediate GC mark/sweep, all you have to do is call connect() on two LocalConnections with the same name. This will throw an error, so you'll have to wrap it in a try/catch block.
try {
new LocalConnection().connect('foo');
new LocalConnection().connect('foo');
} catch (e:*) {}
// the GC will perform a full mark/sweep on the second call.
Again, this should only be used as a development aid. It should never be used in production code!

MerlinTwi
11.09.2006, 23:09
artcraft, спасибо... и очень хорошо бы с русским переводом, т.к. там важно буквально каждое слово.

Похоже GC срабатывает, когда происходит перераспределение памяти. Например, если к моему последнему тесту, благополучно проработавшему два часа так и не удалив мувик из памяти, добавить в руте такой код:
var ar:Array = new Array();
var tm:Timer = new Timer(1000);
tm.addEventListener("timer", timerHandler);
tm.start();

function timerHandler(event:TimerEvent):void {
trace("timerHandler: " + getTimer());
for (var i:uint=0; i<1000; i++) {
ar.push( new Sprite() );
}
}
Фактически просто начинаем активно "кушать" память. То реальное удаление мувика Test из памяти происходит примерно через три секунды (перестает срабатывать EnterFrame).
Значит useWeakReference из функции addEventListener работает корректно, и его можно использовать для событий типа click, mouse..., focus... Которые зависят от самого мувика.
Для событий срабатывающих самостоятельно, типа enterFrame, activate... придется прописывать отдельную функцию на удаление всех созданных Listeners и запускать ее вручную или по Event.REMOVED, смотря по ситуации.

artcraft
11.09.2006, 23:15
есть полезная фигня: System.totalMemory
The amount of memory (in bytes) currently in use by Flash Player.

MerlinTwi
11.09.2006, 23:24
Есть, только по ней смотреть не получается. Если флеш отхавал кусок памяти, то даже после удаления всего хлама он не торопится память освобождать, и получается, что System.totalMemory показывает не сколько памяти требуется в конкретно данный момент, а сколько всего пришлось выделить в пике.

FlexBuilder
12.09.2006, 00:09
по просьбе Merlin-а перевод сообщения artcraft (мерси ему за цитату)

http://www.gskinner.com/blog/archives/2006/06/as3_resource_ma.html

Очень важная вещь, которую нужно понимать при работе со сборщиком мусора в 9-м флешплеере, заключается в том, что эта операция является отложенной. Ваши объекты не будут уничтожены немедленно после того, как все активные ссылки будут удалены, напротив они будут уничтожены лишь через некоторое неопределенное (с точки зрения разработчика) время. Сборщик мусора использует набор эвристических алгоритмов, которые контролируют распределение ОЗУ и размер стека (а также другие факторы) для принятия решения о чистке. Как разработчик вы должны смириться с фактом, что вам не остается никакой возможности для того, чтобы узнать когда (и даже убедиться что) ваши неактивные объекты будут уничтожены. Вам также необходимо знать, что неактивные объекты продолжают выполняться неограничено, (до тех пор пока сборщик мусора не удалит их), а значит код будет продолжать выполняться (например enterFrames), звуки - играть, загрузки - выполняться, события - срабатывать и т.д.

Очень важно помнить, что у вас нет никаких методов для управления моментом, когда ваши объекты будут удалены из памяти, в связи с чем, вы должны сделать их максимально пассивными и не взаимодействующими с другими объектами в тот момент когда вы заканчиваете работу с этими объектами.

artcraft
12.09.2006, 18:39
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.system.System;
import flash.utils.Timer;

public class Test extends Sprite {
private var a:Sprite;
private var timer:Timer;

function Test():void {
timer = new Timer(10);
timer.addEventListener("timer", timerHandler);
timer.start();
}

private function timerHandler(e:TimerEvent):void {
a = new Sprite();
a.name = '#' + timer.currentCount;
a.addEventListener(Event.ENTER_FRAME, efListner, false, 0, true);

addChild(a);
removeChild(a);
a=null;
}

private function efListner(e:Event):void {
trace(e.currentTarget.name, (System.totalMemory/100000).toFixed(1));
}
}
}
моя версия доказательства работы GC - больше 2000 экземпляров одновременно в памяти не зависает
но вот заставить GC сработать когда я этого захочу (двойным LocalConnection()) мне не удалось :~/

artcraft
12.09.2006, 19:41
ура я сделал это :~)
теперь для тестовых целей можно провацировать GC сработать тогда, когда это вам угодно

у меня срабатывает в течении 2-х секунд

package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;

public class Test extends Sprite {

function Test():void {
var a:Sprite = new Sprite();
a.addEventListener(Event.ENTER_FRAME, efListner, false, 0, true);
addChild(a);removeChild(a);a=null;

function efListner(e:Event):void {trace('Please delete me...');}

forceGCRun();
}


private function forceGCRun():void{
var a:Sprite;
var count:uint;
var oldCount:uint;
var timer:Timer;

a = new Sprite();
a.addEventListener(Event.ENTER_FRAME, efListner, false, 0, true);
addChild(a);removeChild(a);a=null;
function efListner(e:Event):void {count++;}

timer = new Timer(1000);
timer.addEventListener("timer", timerHandler, false, 0, true);
timer.start();

function timerHandler(e:TimerEvent):void {
if(oldCount == count){
trace(':)', timer.currentCount);
timer.stop(); timer=null;
}
oldCount = count;
for (var n:uint=0; n<10000; n++){
a = new Sprite(); addChild(a);removeChild(a);a=null;
}
}
}


}
}

artcraft
15.09.2006, 18:47
Ваще пипец...
Кнопка (клип типа Button) при изменении состояний Up-Over-Down (мышку над ней провели) генерит событие Event.REMOVED, которое могут ловят все ее родители :)
это конечно не приятно, но легко обходится

private function onRemove(e:Event):void{
if( e.target == this){
trace ('removed', e.target);
}
}

Yuliy
24.10.2007, 07:58
Забить память разными ненужными объектами, для того чтобы удалить "нужное" событие - не есть правильно!

vooparker
24.10.2007, 10:26
http://www.flasher.ru/forum/showthread.php?t=100234

Что касается того что необходимо отписаться от событий, как правило для этих целей реализуют метод destoy или die, в котором производится чистка ресурсов и отписка от событий в том числе. то есть перед тем как удалить экземпляр вызываем destoy, конечно это усложняет немного жизнь но позволяет избежать многих утечек памяти

LokiDi L0ck
24.10.2007, 12:33
2vooparker, +1
Ручной контроль за удалением обработчиков соответствует принципу всех серьезных языков.
Автору рекомендую почитать "OReilly Essential ActionScript 3.0", там обрисованы все важные ситуации.

__i
24.10.2007, 14:10
Вобщем-то нужно не ленится и запомнить простое правило "Насрал - убери"

Если ремувишь, что либо отпишись от событий, задиспозь битамапы, помочи все ссылки, и будет тебе счатье. Канешно это не решат проблемы GC но решает проблемы с логикой работы вышей программыи и ожидаемым результатом. Ентерфрейм тому пример ).

baron27
24.10.2007, 15:44
Если элементы и листенеры к ним создаются динамически, то можно вызывать при каждом addEventListener какую-нибудь функцию, которая будет добавлять в некий объект наш элемент, событие и слушатель. А потом нужно будет просто пробежаться по этому объекту для очистки всего.