2009年3月2日星期一

使用 ClassLoader 載入外部 swf 中的 class

官方 HELP 中有段範例:

http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/system/ApplicationDomain.html#includeExamplesSummary

是談到當 main.swf 載入外部的 asset.swf 後,要使用 asset.swf 中的 Class 來動態產生物件時,要注意 ApplicationDomain 的設定。

在這個範例中最有意義的是 ClassLoader,透過這個好用的類別,你可以任意載入 *.swf,然後使用 function getClass(className:String):Class 取得在 asset.swf 中的類別,並產生物件。

我將這個 ClassLoader 類別作了一點點修改,當第一次載入完成後便會將 ByteArray 存在 cache 中,若是有不小心載入到同一個 *.swf 的話,就不會再從 server 作 request,而是從 cache 中找出之前載入過的 ByteArray 即可。


package
{
import flash.display.Loader;
import flash.errors.IllegalOperationError;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.utils.ByteArray;

public class ClassLoader extends EventDispatcher
{
public static var CLASS_LOADED:String = "classLoaded";
public static var LOAD_ERROR:String = "loadError";
private var loader:Loader;
private var swfLib:String;
private var request:URLRequest;
private var loadedClass:Class;

static private var cache:Object = new Object();
private var lib:String;

public function ClassLoader(lib:String)
{
this.lib = lib;

loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,completeHandler);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR,ioErrorHandler);
loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR,securityErrorHandler);
}

public function load():void {
if(cache[lib]!=null){
loadFromCache();
}else{
var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener(Event.COMPLETE, urlLoaderComplete);
urlLoader.load(new URLRequest(lib));
}
}

private function urlLoaderComplete(e:Event):void{
cache[lib] = (e.currentTarget as URLLoader).data;
loadFromCache();
}

private function loadFromCache(){
var ba:ByteArray = cache[lib] as ByteArray;

var context:LoaderContext = new LoaderContext();
context.applicationDomain=ApplicationDomain.currentDomain;

loader.loadBytes(ba, context);
}

public function getClass(className:String):Class {
try {
return loader.contentLoaderInfo.applicationDomain.getDefinition(className) as Class;
} catch (e:Error) {
throw new IllegalOperationError(className + " definition not found in " + swfLib);
}
return null;
}

private function completeHandler(e:Event):void {
dispatchEvent(new Event(ClassLoader.CLASS_LOADED));
}

private function ioErrorHandler(e:Event):void {
dispatchEvent(new Event(ClassLoader.LOAD_ERROR));
}

private function securityErrorHandler(e:Event):void {
dispatchEvent(new Event(ClassLoader.LOAD_ERROR));
}

}
}




要測試的話,可以先建立一個 asset.swf,裡面準備一個 MovieClip,放在 Library 中,設定其類別為 "MC1",並且是 "匯出在第 1 個影格",而場景或畫面上不用放置任何東西,基本上這個 asset.swf 的解析度或其他設定要設多少也都沒關係,因為我們真正會用到的就是那 第一影格會匯出 的類別們。

第二步,建一個 main.swf,在 frame 1 寫:


var clsLoader:ClassLoader = new ClassLoader("asset.swf");
clsLoader.addEventListener(ClassLoader.CLASS_LOADED, onLoaded);
clsLoader.load();

function onLoaded(e:Event):void{
var runtimeClassRef:Class = clsLoader.getClass("MC1");
var mc1:MovieClip = new runtimeClassRef() as MovieClip;
addChild(mc1);
}


測試後,應該可以發現 main 有將 asset 中的 MC1 類別產生出實體,並放在場景上。

2 意見:

little hungry 提到...

我執行了這個程式出現以下錯誤訊息:
1119: Access of possibly undefined property data through a reference with static type.
Clascache[lib]=e.currentTarget as URLLoader.data;
請問是甚麼問題呢?感謝。

Ben Chang 提到...

e.currentTarget as 後面應該要接一個 類別 (Class)、介面 (Interface),但是你接的卻是一個 object instance,所以會有問題