ラベル Flash の投稿を表示しています。 すべての投稿を表示
ラベル Flash の投稿を表示しています。 すべての投稿を表示

2010年3月31日水曜日

Flash の埋め込みパラメータ wmode で発生したトラブルいろいろ

Flashまわりの仕事をしていて wmode パラメータで躓いたのでメモ。


■事の発端
開発機のMac上では正常に動作していたコンテンツが、Windows機のFirefoxでテストを開始したとたん以下のような症状に見舞われてしまいました。
  • TextFieldにフォーカスを合わせて日本語をタイプするとTextFieldとは全然違うとんちんかんな位置に入力中の文字列が表示される
  • しかも入力した日本語が文字化けする
ちょっと調べてみて、どうやら wmode パラメータが悪さをしているのではないかということが分かりました。
http://www.326studio.net/blog/2009/10/firefox.html
http://fukata.org/2009/11/29/1905/

ということで早速 wmode パラメータを消してみたところ上手く動作しました。


■そのときのSkype上での会話(一部改変)
登場する皆様ご紹介
 akisute: そもそもテキストボックス内に入力が表示されないのが普通な気がしてきた
easy: あいうえお=0B0D0F0H
akisute: http://www.326studio.net/blog/2009/10/firefox.html
akisute: こいつだな
wozozo: なんでその指定してたんだっけ
AE35: > htmlにswfファイルを呼び出すときにwmodeの指定でtransparent,opaqueの指定をするとこの現象が起きます。
akisute: http://fukata.org/2009/11/29/1905/
akisute: ということでをぞぞんよろしくお願いします
wozozo:
{
id : 'externalswf',
quality : 'high',
wmode : 'transparent',
allowScriptAccess : 'always',
width: 100,
height: 100,
flashvars: 'abesi hidebu tawaba'
});
wozozo: いまこうなってます。
wozozo: wmodeごと削除?
feiz: お、そこは以前のプロジェクトでぶつかった問題ですね
feiz: wmode=transparentを付けると
feiz: flash上にhtmlのダイアログを表示したりしたときにflashが上に出てしまう問題が回避できます
feiz: ただし日本語が化けます。
wozozo: e
wozozo: じゃあこれ消していいの
feiz: 両方いっぺんに回避する方法はあったっけ・・・
wozozo: でもいま
feiz: ああ、無いんだった
wozozo: flashの上にhtmlを表示するときには、
wozozo: embedされてるdivごと hide してる
feiz: あ、それが正しいはず
feiz: 以前のプロジェクトでもそうした。
wozozo: naru
wozozo: じゃあ消す。
feiz: ちなみに二つの問題は両方とも特定ブラウザ限がついてたはず。
feiz: どうでもいいけど。
akisute: それはすごい面白いネタなので
akisute: メモってBlogる


■さらに調査

調べれば調べるほどこの wmode には問題だのバグだのが山盛りだと言うことが分かって驚愕。
http://blog.graffiti-web.org/archives/2006/04/wmode.html
http://chabudai.org/blog/?p=34
http://d.hatena.ne.jp/jedisystemer/20090227/1235754536
http://3ping.org/2004/05/15/1441
Flash Player 10では修正されているらしいのですが、上記のバグはFlash Player 10で発生してますし、うーん。これは触らぬ神にたたり無しですね。


■結論
wmode は指定しないようにしましょう、と。Flash Player 9以下ではIEでフレームレートが落ちる問題がありますが、そもそも9を使っている人も少なくなりましたし問題ないと思います。

2010年3月20日土曜日

IE6, IE7 上で flash.net.URLLoader を使って Comet (Long Poling) によるプッシュ通知を行う際の注意点

Flashは一度書いたらどのブラウザでも同じように動く、そう信じていた時が私にもありました。その私の考えを裏切るかのように、 またIEか と言いたくなる現象が発見されました。

調査の際に参考にしたページはこちら。
http://saruzaurus.blogspot.com/2008/07/comet_10.html


■問題
IE6, IE7 で、 flash.net.URLLoader / flash.net.Loader を使用すると、同時に2本しかコネクションが張れません。通常の使い道でしたらこの制限で何ら問題はないのですが、問題になるのは CometTornado のように所謂ロングポーリングと呼ばれるHTTP通信の仕方をするアプリケーションを書いたときです。ロングポーリングではいったんHTTPリクエストをサーバーに投げた後、サーバーで何かイベントが発生するまでそのレスポンスをひたすら待ちます。従って常時HTTPコネクションが1本消費されている状態になるため、事実上1本しかHTTPコネクションを使うことができません。

またIE6, IE7 では、 flash.net.URLLoader.close() の実装にも問題があるようです。
http://www.kirupa.com/forum/showthread.php?t=335691
http://stackoverflow.com/questions/455656/urlloader-gets-stuck-when-polling
きちんと使用後に URLLoaderclose しているのですが、それでも何故かメモリ使用量が減らなかったり、コネクションが閉じられなかったりして貴重なコネクション数が消費されっぱなしになったりするなど、どうにもこうにも不安定です。



これらが原因となって、常時ロングポーリングでイベントを待ち、イベントを受け取ったら処理を行ってまたすぐロングポーリングを張りに行くようなアプリケーションを書くと、その他の通信を行った際にロングポーリングが止まってしまったりその他の通信がコネクション数限界で実行されなかったりします。


■原因
Flashは内部的にはブラウザの機能を用いて実装されています。したがってブラウザ側のHTTP接続数が2本であれば自動的にFlash側のHTTP接続限界も2本になります。当たり前と言えば当たり前なのですが、盲点でした。

Flash側の接続数とブラウザ側の接続数が共有されるかどうかは未知数・未検証ですが、おそらく共有されるのではないかと思っています。従って(まずこんな構成にすることはないと思いますが)FlashでCometによる通知待ちを行い、Ajaxでも同様の通知待ちを行うと、それだけで接続限界になってしまいます。


■対策
今のところ最良の方法がまだ分かっていません・・・ごめんなさいorz 一応いくつか思いついている作戦は、
  1. Windowsのレジストリを操作してIE6/IE7の同時接続数を増やす
  2. 同時接続数制限は同一ドメインに対してのみ発生するらしいので、サブドメインを切って別々のドメインに分ける
  3. 他の通信が発生しているときはロングポーリングを切断する
などなど。上手い対策が発見されましたらまた書きます。

2010/03/31 23:59 追記:サブドメインを切って別々のドメインに分けたら上手く動作しましたので、この方法を一応お勧めしておきます。

2010年3月18日木曜日

Flash の fl.controls / fl.containers の visible プロパティはADDED_TO_STAGEで変更する

最近イマイチなFlashネタしかお届けできておらず残念です>< が、ちょっとしたネタを発見しましたので書いてみます。


■問題
fl.controls.Buttonとか, fl.containers.ScrollPaneのvisibleプロパティをコンストラクタの中でfalseにしても、直後にtrueに戻されてしまうようです。試しに、以下のような構成のMovieClipをFlashで作成して、
  • TestMovieClip
    • aButton
    • aScrollPane
以下のようなコードを書いてみます。
package {
import flash.display.*;
import fl.controls.*;
import fl.containers.*;
public class TestMovieClip extends MovieClip () {
public function TestMovieClip() {
aButton.visible = false;
aScrollPane.visible = false;
}
}
}
このコードを実行すると、なぜかボタンとスクロールペーンが 消えません 。なんでやねん。デバッガを使って調べてみると、どうやらコンストラクタが終わった後、どこかのタイミング?でvisible=trueに戻っているようです。


■対策
以下のようにADDED_TO_STAGEイベントを使って回避します。
package {
import flash.display.*;
import flash.events.*;
import fl.controls.*;
import fl.containers.*;
public class TestMovieClip extends MovieClip () {
public function TestMovieClip() {
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
private function onAddedToStage(event:Event):void {
aButton.visible = false;
aScrollPane.visible = false;
}
}
}
公式のドキュメントを見てもこのあたりの解説が無くて困りました。

2010年2月15日月曜日

Flash の SWC は使ってはいけない

タイトルからしてなかなかひどいですが、調査結果もなかなかひどいです。

■結論:fl.controls(最初からFlashに付属しているコンポーネント)を含むSWCを作ると正しく読み込まれない
まずはこちらに検証用のプロジェクトを用意しましたので、ご覧になってみてください。
package {
import flash.display.MovieClip;

public dynamic class Abesi extends MovieClip
{
public function Abesi() {
trace("abesi");
trace(this.button, this.textArea);
}
}
}
package {
import flash.display.MovieClip;

public dynamic class Hidebu extends MovieClip
{
public function Hidebu() {
trace("hidebu");
trace(this.button, this.textArea);
}
}
}
とまぁ、なんの変哲もないタダのFlashコードをswcとして出力し、TestMainの中でnewして出力しているだけなのですが、なんとswcに書き出すタイミングに応じて確実にエラーになって落ちるというひどい問題があるようなのです。


■検証結果
以下のスプレッドシートにまとめてみました。
http://spreadsheets.google.com/ccc?key=0AoXhhCSOuqOtdE5rNy1wc2N2Z2JuV1NPUFBjdlRNeHc



たぶん私の書き出し方に何か問題があるのだろうとは思っているのですが、何処を見てもそれに関する情報が見あたらなかったので解決できず、結局swcの使用はあきらめることにしました><

2010年2月8日月曜日

ActionScript 3 の flash.net.Loader が読み込んだ MovieClip の挙動がローカルファイルとリモートファイルで変わるみたいです

flash.net.Loaderを用いて、リモートサーバーからswfファイルをロードし、画面に表示するようなアプリを書いていたのですが、そのときふとしたことから以下のような問題に気づきました。


■前提条件
仮に以下のような条件があるとします。
  • main.swfとresource.swfがあって、main.swfはflash.net.Loaderクラスを用いてresource.swfをロードしてくるものとする。
    var loader:Loader = new Loader();
    loader.load(new URLRequest("http://akisute.com/static/swf/sample.swf"); //リモートサーバーからロード
    loader.load(new URLRequest("../swf/sample.swf"); //ローカルファイルからロード
  • ロードしてきたswfは、以下のコードでloader.loaderInfo.contentから取り出して、MovieClipとして処理する。Loaderは破棄する。
    function onComplete(event:Event):void {
    var loaderInfo:LoaderInfo = event.target as LoaderInfo;
    var content:MovieClip = loaderInfo.content as MovieClip;
    var mc:MovieClip = content.getChildAt(0) as MovieClip;
    }
  • ロードしてきたswf(以下resourceと呼称)には合計200フレームのタイムラインがあり、1から100フレーム目までにleftというラベルがついて左向きのデータが、101から200フレーム目までにrightというラベルがついて右向きのデータが格納されている。1から100フレーム目までに右向きのデータは存在しない。その逆もしかり。


■問題
  • 対象のファイルresource.swfを、ファイルシステムからロードしたときは、なんの問題もなくラベルleftとrightを入れ替えることができる。
  • 対象のファイルresource.swfを、httpでリモートサーバーからロードしたときは、leftとrightを入れ替えた瞬間に、resourceのアニメーションがバグる。stop()やgotoAndPlay()などの命令を無視した動きをする。
  • ローカルファイルとリモートサーバー、どちらのresource.swfも完全に同一のファイル。ファイルサイズもタイムスタンプもsha1ハッシュも完全に一致。
・・・なにこれ。最初に見かけたときはAdobe焼き討ちじゃコラと思ったものです。


■調査:取ってきたswfをdescribeTypeしてみる
愚痴っても仕方がないので調査します。まずは取ってきたswfに対して、flash.util.describeType()を実行し、オブジェクトダンプしたXMLを見てみます。するとローカルファイルから取ってきた場合とリモートサーバーから取ってきた場合で、以下のような違い(diff)がある事が分かりました。
1c1
< ## LOCAL ##
---
> ## REMOTE ##
5,6c5
< <type name="sample_fla::left_33" base="flash.display::MovieClip" isDynamic="true" isFinal=&quot;false" isStatic="false">
< <extendsClass type="flash.display::MovieClip"/>
---
> <type name="flash.display::MovieClip" base="flash.display::Sprite" isDynamic="true" isFinal="false" isStatic="false">
15,18d13
< <variable name="leftArm" type="flash.display::MovieClip"/>
< <variable name="leftLeg" type="flash.display::MovieClip"/>
< <variable name="leftHead" type="flash.display::MovieClip"/>
< <variable name="leftBody" type="flash.display::MovieClip"/>
diffなのでちょっとわかりにくいかもしれませんが、よく見ると以下のような差異があることが分かります。
  • LOCALにはきちんとしたクラス名がついており、MovieClipのサブクラスになっている
  • LOCALにはプロパティ情報がきちんと残されている
  • REMOTEはタダのMovieClipクラスになっており、プロパティも一切無い
つまり、全く同一のファイルをロードしているにもかかわらず、flash.net.Loaderは、ローカルファイルからロードするかリモートサーバーからロードするかによって生成するMovieClipオブジェクトを変化させているということがわかりました。

どうしてこのようなことをになっているのか少し考えてみたのですが、おそらくセキュリティ要件の問題ではないかと言う結論に至りました。というのも見ての通り、ローカルファイルから読み込んだswfをdescribeTypeすると、クラス名としてファイル名とシンボル名がついてしまっていて、これを悪用することができるのではないかと危惧したのではと。

■対応:しかしさらに泥沼
クラス情報が欠落してMovieClipになってしまっているのが問題だとすれば、読み込まれる側のresource.swfのすべてのシンボルを「アクションスクリプトに書き出し」して、きちんとクラスとして定義すれば、問題が解決するのではないかと考えました。

ということで、早速試して見たところ・・・
TypeError: Error #1034: 強制型変換に失敗しました。flash.display::MovieClip@677b34c1
を rightArm に変換できません。

at flash.display::MovieClip/gotoAndPlay()
at com.akisute::Main/onLoadCompleted()
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at flash.net::URLLoader/onComplete()
・・・bullshit。ジョブズがどうしてあんなにFlashを毛嫌いするのか分かった気がします(絶対違)。

まぁスタックトレースが出るようになってくれたので、なんとかなりそうです。さっきまではスタックトレースすら出さずに勝手にバグを出してくれやがってたので大変困っていました。まったく、これ読んで出直せ、Adobe。
Errors should never pass silently.


■考察:どうしてこうなるのか
さらに考えてみます。一見さっきのスタックトレースはロードに失敗してバグが発生している用に思われますが、実際にエラーを吐いているのはonLoadCompletedの中のgotoAndPlayです。そう、ロードが完了した瞬間にgotoAndPlay("right")を実行しているのですが、そこでエラーが発生しているのです。

ここでちょっと仮説を立ててみます。
  • ロードが完了された瞬間のresourceのcurrentFrameは1、つまりleftである。このとき、right側のパーツは一切読み込まれていない(nullである)。
  • gotoAndPlay("right")を実行すると、おそらくロードが完了しているMovieClip自身がタイムラインに従って101フレーム目以降の内部構造を補完しようとする。すなわち、right側のパーツ(MovieClipとかShapeとか)をどっからか取ってきてセットし、left側のパーツをnullにセットしようとする。ここまでの動きはデバッガを使って確認できました。
  • このとき、先ほどのflash.net.Loaderは完全な情報を持っていたのできちんとMovieClipを作ることができた(left側のパーツをセットできた)が、MovieClip自身にはその情報がないので、right側のパーツをセットしようとしてタダのMovieClipをrightArmクラスの変数にセットしようとし、キャストに失敗して落ちている。
・・・長くてすみません><
要するに、ローカルファイルからロードし生成されたMovieClipはデータを完全に保有しているのでMovieClip自身でnullフレームを埋めることができるが、リモートサーバーからロードしてきたswfから生成されたMovieClipは生成時にLoaderクラスによってデータを欠落させられているので、自力でnullフレームを埋めることができないのではないかと考えたのです。


■解決策:外部ロードするswfは、絶対に空のフレームを作るな
そこで以下のような対応をしてみました。
  • leftラベルの箇所にもright側のパーツを配置する。ただし見えては困るので、透明にして配置する。
  • 同様に、rightラベルの箇所にもleft側のパーツを配置する。
  • こうすることで、最初からすべてのフレームのデータがLoaderクラスによって読み込まれ、 nullフレームがないのでタイムラインがどのように動いても一度ロードされたデータが欠落することがない。従って問題なく動作する。
はたしてこの方法が功を奏し、見事に動作するようになりました!


■というのを
@moriyoshiさんから教えて貰いました。ありがとうございます!
・・・というか、なんでFlash使いでもないのにこんな詳しいんですか><

2010年1月9日土曜日

ASDoc の書き方講座

仕事用に作ったまとめをそのまま転載。


■参考リンク
以下のリンクを見ておけば一通り大丈夫です。
ASDoc コメントの作成
ASDoc タグ一覧
@see タグや@copy タグの使用方法
びんた先生(beinteractive)のパーフェクトASDocタグ講座


■Flex 3 SDKのasdocコマンドを使ってみる
asdoc -helpを見れば良いのですが、helpが見づらくてややっこしいので、まとめてみました。
http://livedocs.adobe.com/flex/3/html/help.html?content=asdoc_9.html#142061
以下のコマンドを入力すればOKです。ここで、$FLEX_HOMEは Flex SDKをインストールしたパスです。
asdoc -compiler.source-path=自分が追加したいソースパス, -compiler.library-path=$FLEX_HOME/frameworks/libs,自分が追加したいライブラリパス -doc-sources=ドキュメントに出力したいソースのパス
$FLEX_HOME/frameworks/libsをライブラリパスに含めないとビルドが通らないのがくせ者です。JavaDocはtools.jarを自動的にパスに含めてくれるんだから、ASDocだって自動的にやってくれと言う気がします>< ちなみに、Flex Builderとか使えばこんな面倒な事をしなくても簡単にASDocを出力することができるみたいです。


■気をつけるところ
ASDocの最初の一文は必ずピリオドで終了する
腹立たしいですがピリオド{{.}}が最初の一文の区切りになっているようなので、句読点{{。}}ではなくピリオドを使いましょう。 たとえば、
package {
/**
 * ASDocのテスト用のクラスです.
 * 
 * 第一文は必ずピリオドで終了する必要があります。
 * 試しに上記のピリオドを句読点に変更してドキュメントを出力すると違いが分かると思います。
 */
 public class ASDocTestClass {}
 }
のように書く必要があります。こうしないと、ドキュメントを生成したときに説明文が長ったらしくなって困ります。