ランタイム共有ライブラリを調べる

  • 2011 年 5月 27 日
  • kosuke

Flashには「ランタイム共有ライブラリ(RSL)」という機能がある。
これはAS2のころからある機能でSWFの実行時にあるSWFがもつライブラリを他のSWFから利用するもの。
たとえば、A.swfでもB.swfでも利用するシンボルをShare.swfでRSLとして登録し、実行時に参照する。
するとA.swfとB.swfは自身のライブラリに同じシンボルのデータを持つ必要がなくなり、結果的にファイル容量が軽量化しダウンロードにかかる時間が短縮されたり、一意に修正をすることが出来たりするわけです。

便利そうな機能なのだけど、これが以外に使われない。RSLにすることで、(単一ファイルではありえない)読込みエラーなどが発生する機会が増えるなど構造的なデメリットもあるのだけど、一番の理由は読込みのタイミングがうまく制御できないからではないかと思っている。制御に関してはっきり挙動がわかる資料が無かった。最近は事情が変わってきて、RSLを進化させFlashPlayerにキャッシュするアドビ公式のランタイム共有ライブラリとでも言おうか。符号付きRSLであるSWZ形式のファイルが登場しRSL読込み用のクラスなども実装されてきた。

しかし、昔RSLを試したことあるけど…って方は鼻っからRSL使い物になる?と疑念もあってあまり関心がなかったりしないだろうか。(←早い話、僕。)そこで実際仕事で使いたかったって事情もあり調べてみた。

例として下のような、フチあり写真のようなシンボルがあり、このシンボルをRSLとして他のSWFから使うものとします。

ランタイム共有用に書き出しをチェックして、SWFを書き出します。SWFはShare.swfとしました。

RSLを利用する側のRSLTest1.flaではRSLの読込みを設定します。挿入→新規シンボルで空のシンボルを作り、ランタイム共有用に読込みをチェック。URLにShare.swfを指定します。(今回の場合、RSLTest1.flaを書き出したSWFとShare.swfは同じディレクトリに配置します。)

ここでは「いいえ」で。

タイムラインに適当に配置してみる。

パブリッシュ。

これでRSLに書き出したシンボルを読込み、表示するまでの確認ができました。
今はタイムラインに配置していますがクラスベースで開発する時は、通常インスタンスはスクリプトで制御して表示します。そこでタイムラインに配置したシンボルはすべて削除してドキュメントクラスからRSLを使う場合の検証をしました。

package rsl{
 
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import share.PhotoTemplate;
 
	public class RSLTest1 extends MovieClip{
 
		public function RSLTest1(){
			loaderInfo.addEventListener( Event.OPEN, onOpen );
			loaderInfo.addEventListener( ProgressEvent.PROGRESS, onProgress );
			loaderInfo.addEventListener( Event.INIT, onInit );
			loaderInfo.addEventListener( Event.COMPLETE, onComplete );
		}
 
		private function onOpen(e:Event):void{
			trace("RSLTest1 OPEN:", loaderInfo.bytesLoaded, loaderInfo.bytesTotal );
		}
 
		private function onProgress(e:ProgressEvent):void{
			trace("RSLTest1 PROGRESS:", e.bytesLoaded, e.bytesTotal );
		}
 
		private function onInit( e:Event ):void{
			trace( "RSLTest1 INIT:", loaderInfo.bytesLoaded, loaderInfo.bytesTotal );
			var temp:PhotoTemplate = new PhotoTemplate();
			addChild( temp );
		}
 
		private function onComplete( e:Event ):void{
			trace( "RSLTest1 COMPLETE:", loaderInfo.bytesLoaded, loaderInfo.bytesTotal );
		}
	}
}

ドキュメントクラスのSWF(RSLTest1.swf)のloaderInfoを監視しアクセス可能(Event.INIT)となったらPhotoTemplateのインスタンスを生成し表示リストに加える。表示されましたか?この検証では表示されませんでした。
traceは以下になっています。

Safariの構成ファイルなど読込んでいるファイルが見えるもので確認するとわかるのですがそもそもShare.swfが読込まれていません。

ランタイム共有用に読込みのシンボルはActionScript用に書き出しがチェックできない為、タイムラインに配置なしだとコンパイルに含まれないのだと思われます。そこでPhotoTemplateを含むシンボルを生成してActionScript用に書き出しをチェックしてみます。

今度はShare.swfは読込まれ、PhotoTemplateのインスタンスも表示されました。

しかし、今回はtraceは妙な出力をしています。

ProgressEvent.PROGRESSとEvent.COMPLETEが返ってこない。

ちなみにActionScript用に書き出しをやめて、先ほどのタイムラインに配置した状態でこのドキュメントクラスを使った場合も、PhotoTemplateのインスタンスは表示されますがProgressEvent.PROGRESSとEvent.COMPLETEが返ってきません。

ここまでの様子をまとめると、ランタイム共有ライブラリが読込まれる場合、そのドキュメントクラスでは、

・ProgressEvent.PROGRESSとEvent.COMPLETEは返ってこない。
・Event.INIT以降、ランタイム共有ライブラリにアクセス可能。
・loaderInfoのbytesLoadedとbytesTotalに、ランタイム共有ライブラリのSWFのファイルの容量は含まれない。

ものと思われる。
とすると、Flashで開発する場合にランタイム共有ライブラリを使うなら、RSLを読込むSWF単体にそれぞれについては、初期化時のEvent.INITを起点に開発することさえ気をつければ問題なさそうに思います。

しかしこのままだとRSLTest1.swfはEvent.INITがくるまで何のアクションもなく待ち続けることになる。そこでRSLTest1.swfとShare.swfの読込みを管理する為のプリローダー(Preloader1.swf)を考えてみる。

まず、RSLの読込みに使えるクラスがある。

RSLPreloader

RSLPreloaderは、SWZも含めてRSLのプリロードを管理するクラス。
Preloader1.swfがShare.swfのRSLをライブラリに読込んでいると意味がないのでPreloader1.swfではRSLを利用しないこと。

ちなみにActionScript用に書き出しがない状態でRSLを読込んでいる場合(ドキュメントクラスを使った最初の検証の場合)、RSLTest1.swfでRSLPreloaderクラスを使ってローディングしてもうまくいく。

package rsl{
 
 
	import fl.events.RSLErrorEvent;
	import fl.events.RSLEvent;
	import fl.rsl.RSLInfo;
	import fl.rsl.RSLPreloader;
 
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.net.URLRequest;
 
 
	public class Preloader1 extends Sprite{
 
		private var rslPreloader:RSLPreloader;
		private var loader:Loader;
 
		public function Preloader1(){
			super();
			rslPreloader 			= new RSLPreloader();
			var info:RSLInfo	= new RSLInfo();
			info.addEntry( 'Share.swf' );
			rslPreloader.addRSLInfo( info );
			rslPreloader.addEventListener( RSLErrorEvent.RSL_LOAD_FAILED, onRslLoadFailed );
			rslPreloader.addEventListener( RSLEvent.RSL_PROGRESS, onRslProgress );
			rslPreloader.addEventListener( RSLEvent.RSL_LOAD_COMPLETE, onRslLoadComplete );
			rslPreloader.start();
 
			loader	= new Loader();
			addChild( loader );
			loader.contentLoaderInfo.addEventListener( Event.OPEN, onOpen );
			loader.contentLoaderInfo.addEventListener( ProgressEvent.PROGRESS, onProgress );
			loader.contentLoaderInfo.addEventListener( Event.INIT, onInit );
			loader.contentLoaderInfo.addEventListener( Event.COMPLETE, onComplete );
			loader.load( new URLRequest( "RSLTest1.swf" ) );
		}
 
 
		private function onRslLoadFailed( e:RSLErrorEvent ):void{
			trace( "RSL_LOAD_FAILED:"  );
		}
 
		private function onRslProgress( e:RSLEvent ):void{
			trace( "RSL_PROGRESS:", e.bytesLoaded, e.bytesTotal );
		}
 
		private function onRslLoadComplete( e:RSLEvent ):void{
			trace( "RSL_LOAD_COMPLETE:", e.bytesLoaded, e.bytesTotal );
		}
 
 
		private function onOpen(e:Event):void{
			trace("Preloader1 OPEN:", loader.contentLoaderInfo.bytesLoaded, loader.contentLoaderInfo.bytesTotal );
		}
 
		private function onProgress(e:ProgressEvent):void{
			trace("Preloader1 PROGRESS:", e.bytesLoaded, e.bytesTotal );
		}
 
		private function onInit(e:Event):void{
			trace("Preloader1 INIT:", loader.contentLoaderInfo.bytesLoaded, loader.contentLoaderInfo.bytesTotal );
		}
 
		private function onComplete(e:Event):void{
			trace("Preloader1 COMPLETE:", loader.contentLoaderInfo.bytesLoaded, loader.contentLoaderInfo.bytesTotal );
		}
 
 
	}
}

trace出力は以下だ。

Share.swfのプログレスが取得できていることと、RSLTest1.swfのプログレスも取得できていることが確認できる。
さらにRSLTest1.swf単体だと動かなかった、Event.COMPLETEも無事動いている。

RSLPreloaderは、APIドキュメントではランタイム10.1以上ってことなんだけどどうなんだろう。サンプルはPlayer 9書き出しにして、Player 9(9.0.47.0)で見てみましたが動いていますね。。。SWZとか完全に動かないよってことなのかな。

他にFlex SDKで、
PreloaderDownloadProgressBar
を使う方法も試した。この方法でもうまく読込めます。

Preloader2.as

package rsl{
 
 
	import fl.rsl.RSLInfo;
 
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.net.URLRequest;
 
	import mx.preloaders.Preloader;
 
 
 
	/** RSLの読込みをFlexのPreloaderで行ってみる。 */
	public class Preloader2 extends Sprite{
 
		private var rslPreloader:Preloader
		private var info:RSLInfo
		private var loader:Loader;
 
		public function Preloader2(){
			super();
 
			rslPreloader	= new Preloader();
			addChild( rslPreloader );
			rslPreloader.initialize( true, CustomProgressBar, 0x000000, 1, null, "", 1, 1, [ "Share.swf" ] );
 
			loader	= new Loader();
			addChild( loader );
			loader.contentLoaderInfo.addEventListener( Event.OPEN, onOpen );
			loader.contentLoaderInfo.addEventListener( ProgressEvent.PROGRESS, onProgress );
			loader.contentLoaderInfo.addEventListener( Event.INIT, onInit );
			loader.contentLoaderInfo.addEventListener( Event.COMPLETE, onComplete );
			loader.load( new URLRequest( "RSLTest1.swf" ) );
		}
 
 
 
		private function onOpen(e:Event):void{
			trace("Preloader1 OPEN:", loader.contentLoaderInfo.bytesLoaded, loader.contentLoaderInfo.bytesTotal );
		}
 
		private function onProgress(e:ProgressEvent):void{
			trace("Preloader1 PROGRESS:", e.bytesLoaded, e.bytesTotal );
		}
 
		private function onInit(e:Event):void{
			trace("Preloader1 INIT:", loader.contentLoaderInfo.bytesLoaded, loader.contentLoaderInfo.bytesTotal );
		}
 
		private function onComplete(e:Event):void{
			trace("Preloader1 COMPLETE:", loader.contentLoaderInfo.bytesLoaded, loader.contentLoaderInfo.bytesTotal );
		}
 
 
	}
}

Preloaderから利用するProgressBarを用意。DownloadProgressBarを継承して各メソッドを上書きすればよい。

package rsl{
 
 
	import mx.events.RSLEvent;
	import mx.preloaders.DownloadProgressBar;
 
 
	public class CustomProgressBar extends DownloadProgressBar{
 
 
		public function CustomProgressBar(){
			super();
		}
 
 
		protected override function rslProgressHandler(event:RSLEvent):void{
			trace( "rslProgressHandler:", event.bytesLoaded, event.bytesTotal );
		}
 
 
		protected override function rslCompleteHandler(event:RSLEvent):void{
			trace( "rslCompleteHandler", event.bytesLoaded, event.bytesTotal );
		}
 
 
	}
}

trace出力はこんな感じ。

一応、このプリローダーからRSLとRSLを使うSWFを読込む方法なら、読込みのタイミングを処理できるんじゃないかと思う。
一応としているのは、SWZでないRSL、つまりユーザーが自由につくれるRSLはプレイヤーにはキャッシュされない。キャッシュされるのはブラウザのキャッシュであるということ。この当たりを読むとわかる。

Flash Playerキャッシュの使用によるFlexアプリケーション性能の改善
フレームワーク RSL の使用

ってことはやっぱり上のプリローダーを使った場合も、RSLPreloaderを先に処理しても、RSLTest1.swfを読込んだ後、RSLTest1.swfがRSLをリクエストするんじゃないかと。実際Safariの構成ファイルを見ると2度Share.swfが読込まれているので。2回目はキャッシュを参照するから瞬間的に読込まれるものと思われるけど、キャッシュを消していたら読込みに時間がかかるんじゃないかなと思っている。これはもうしょうがないんじゃないかと思う。サイト全体としてキャッシュ消された時の対策までを考えるなら、あるSWFを読込む時、そのSWFがRSLを利用しているものならRSLPreloaderで毎回プリロードかけるとか、プログレス表示なしでいいならEvent.INITを待つとか…。やっぱり今もRSLを使うことで手間になることはあると思う。

さてそんなRSLですが調べるきっかけとなった状況がありました。僕が直面したのは仕事で16ファイルのSWFを作る時。この16ファイルには共通するシンボルがあるのだけれど、モック段階から進めて作っていく為、アップデートを重ねる度にシンボルを16のflaファイル変更しなくちゃいけない。この時はオーサタイム共有ライブラリにして対応したので差し替えは半自動的に行っていたのですが、それでも16ファイルパブリッシュするとかってばからしいじゃない。試行錯誤の段階においては。
この時は結局RSLは使わなかったのですが、今度同じような時があったらRSLが使えるか調べておこうと思いました。
Flashで作るならアプリケーションで素早くデザインが見える・作れるというのが利点なので、最後にこの利点を踏まえてRSLの使い方を自分なりにまとめておきたい。

この記事では最初にPhotoTemplateというシンボルをShare.swfに作り、RSLTest1.swfでは空のシンボルを作ってRSL読込みの設定をしましたが、空のシンボルである必要はありません。なので、サイズは固定されているけど、中身の要素やデザインは変わっていくなんて時には途中段階のデザインでシンボルにし、RSL読込みを設定、最新のデザインはRSLから読込まれるようにしておくという方法が便利かと思いました。

先ほど作ったPhotoTemplateシンボルを編集する。今回は「はい」として一旦、RSL読込み設定を解除。

その時点の仮デザインを入れる。入れ子のシンボルなどに使う場合、仮のデザインでも有無でこの後の作業の進み具合が全然違ってくるものです。

RSL読込みがはずれていますので再設定。

そのシンボルをステージにおいてパブリッシュしてみると、きちんとShare.swfが書き出しているRSLで表示されます。

“ランタイム共有ライブラリを調べる” に コメントはありません

コメントをどうぞ