2008年9月21日日曜日

URLLoaderのイベントが発火されない件で悩む

AdobeAIRでURLLoaderを使っているのですが、追加しているはずのListenerにイベントが飛んでこなくて悩んでいました。
一応、原因が分かったのでご報告。

URLLoaderにEventListenerを追加するには、addEventListenerを使います。
このAPIを見ると
public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
イベントリスナーが不要になった場合は、removeEventListener() を呼び出して、イベントリスナーを削除します。削除しない場合、メモリの問題が発生する可能性があります。ガベージコレクターは参照を有するオブジェクトを削除しないため、登録されているイベントリスナーに関係したオブジェクトはメモリから自動的に除去されません。

と書いてあります。そうか...メモリリーク怖いしなぁーと思い、第三引数のWeakReferenceをtrueに指定して使っていました。

WeakReference...Javaでもおなじみの弱参照ってやつですね。

ということで、このWeakReferenceをつかったAIRアプリとしてこんな感じものもを作ってみました。

WindowLoadTest.mxml
<?xml version="1.0" encoding="UTF-8"?>
<mx:WindowedApplication title="WindowLoadTest" windowComplete="onWindowCompleted()" alpha="0.7" layout="absolute" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script source="WindowLoadTest.as"/>
</mx:WindowedApplication>


WindowLoadTest.as
import flash.net.*;
import flash.events.*;

protected function onWindowCompleted():void {
start();
}

public function start():void {
var loader:URLLoader = new URLLoader();
var request:URLRequest = new URLRequest();

request.method = "GET";
request.url = "http://www.google.com/";

loader.addEventListener(Event.COMPLETE, onCompleted, false, 0 , true);
loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler, false, 0 , true);

trace("loader load:" + request.method + ";" +request.url);
loader.load(request);
}

private function onCompleted(event:Event):void {
trace("onCompleted:" + event);
}

private function httpStatusHandler(event:HTTPStatusEvent):void {
trace("httpStatus: " + event.status);
}


これだと、無事に動きます。Traceで出力されたログは以下。
loader load:GET;http://www.google.com/
httpStatus: 200
onCompleted:[Event type="complete" bubbles=false cancelable=false eventPhase=2]


次に start()メソッドを別のクラスに移動して動かしてみます。
startのロジックをLoadTest.asに移動します。
LoadTest.as
package  {
import flash.net.*;
import flash.events.*;

public class LoadTest {

public function start():void {
var loader:URLLoader = new URLLoader();
var request:URLRequest = new URLRequest();

request.method = "GET";
request.url = "http://www.google.com/";

loader.addEventListener(Event.COMPLETE, onCompleted, false, 0 , true);
loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler, false, 0 , true);

trace("loader load:" + request.method + ";" +request.url);
loader.load(request);
}

private function onCompleted(event:Event):void {
trace("onCompleted:" + event);
}

private function httpStatusHandler(event:HTTPStatusEvent):void {
trace("httpStatus: " + event.status);
}

}
}

WindowLoadTest.asは以下に変更
protected function onWindowCompleted():void {
new LoadTest().start();
}


これを実行すると以下のTraceログになります。
loader load:GET;http://www.google.com/

追加したはずのEvent.COMPLETEのイベントが飛んでこないみたいでログに出てきません(涙)


ここで、LoadTest.asでWeakReferenceで追加しているコード
loader.addEventListener(Event.COMPLETE, onCompleted, false, 0 , true);
を強参照に戻す、つまり
loader.addEventListener(Event.COMPLETE, onCompleted)
にすると、Event.COMPLETEのイベントが飛んでくるようになります。

...ということで、どうやらURLLoaderに弱参照のみのListenerを追加するとイベントが完了する前にGCが行なわれて,GC対象になってしまう...ような感じです。

それならばっ!ということで、LoadTestのクラスを、MXMLのクラス変数で保持するようにしたら?という所を試してみました。
コード的には以下のように修正です。

WindowLoadTest.as
private var loadTest:LoadTest;

protected function onWindowCompleted():void {
loadTest = new LoadTest();
loadTest.start();
}


そうすると、結果は
loader load:GET;http://www.google.com/
httpStatus: 200
onCompleted:[Event type="complete" bubbles=false cancelable=false eventPhase=2]

となり、無事にCOMPLETEDイベントも返ってきました!

どういう状態のものがGC対象になるのか、ならないのか...ちゃんと理解しながらコード書かないとハマるなぁ...
今回の件は解決できたからいいとしても、はっきりと理解できていないので、不安です。。
• • •