我是 Ticore,已經好久沒有貼 Flash Player Crash Bug 了
今天突然又發現一個會造成 Flash Player 死機的問題
幾乎發生在所有 Flash Player 9, 10, 11 主要版本上
本來想回報到 Adobe Bug System
結果它把 Flash Player Project 關閉不讓我回報了
所以只好貼在 Blog 上

後來找到了是要在這裡回報 Flash Player

言歸正傳,這是關於巢狀遮罩的 Bug
由於一個 AS3 Mask 物件不能同時用在多個對象上
所以突發奇想,將主要 Mask 物件嵌套多層 Sprite
不同層的 Sprite 不就能夠用在多個對象上了嗎?

結果當然是失敗了,而且造成 Flash Player Crash 掉
以下是簡單的測試程式:

package {
	import flash.display.Graphics;
	import flash.display.Sprite;
	import flash.system.Capabilities;
 
	public class NestedMaskCrash extends Sprite {
		public function NestedMaskCrash() {
			trace(Capabilities.version);
 
			var innerMask:Sprite = new Sprite();
			var g:Graphics = innerMask.graphics;
			g.beginFill(0, 1);
			g.drawRect(0, 0, 1, 1);
			g.endFill();
 
			var outerMask:Sprite = new Sprite();
			outerMask.addChild(innerMask);
 
			var childSp:Sprite = new Sprite();
			addChild(childSp);
 
			childSp.mask = innerMask;
			this.mask = outerMask;
 
			trace("Nested mask constructed.");
		}
	}
}

編譯並執行以上的 AS3 程式,會看到 Busy Cursor
不一會兒,就 Crash 了

以上的 Bug 至少會發生在以下版本上
Flash Player 9.0.289.0
Flash Player 10.3.183.7
Flash Player 11.0.1.98 Beta 2

作業系統 Windows 7 64bit, Mac OSX Lion

2011/09/06 更新
隔天又想到用 AS1 測試看看
寫了以下測試程式

var outerMask = this.createEmptyMovieClip("outerMask", 100);
var innerMask = outerMask.createEmptyMovieClip("innerMask", 100);

innerMask.beginFill(0, 1);
innerMask.lineTo(1, 0);
innerMask.lineTo(0, 1);
innerMask.endFill();

var mc1 = createEmptyMovieClip("mc1", 200);
var mc2 = createEmptyMovieClip("mc2", 300);

mc1.setMask(innerMask);
mc2.setMask(outerMask);

結果一樣會 Crash
而且這個 Crash Bug 幾乎貫穿全部版本 Flash Player 6, 7, 8, 9, 10, 11
除了 Flash Player 5 以前版本,不支援 setMask
很明顯是從 Macromedia 時代就種下的禍因

分享到新浪微博 分享到人人网 分享到豆瓣 分享到鲜果 分享到百度空间 分享到开心网 QQ书签 分享到YAHOO! 分享到Google Google Buzz 分享到Facebook 分享到Plurk Digg delicious Technorati Twitter

Flash/Flex 移动端开发 之 DPI

2 2011 In: Flash/Flex Mobile Dev, Mobile Dev

DPI是Dots Per Inch的缩写,中文即每英寸点数,或者每英寸像素数。
100 * 100的图片
在DPI为100的地方就是1英寸*1英寸的大小
在DPI为200的地方就是0.5英寸*0.5英寸的大小
在iPhone4(DPI为326)就是0.3英寸*0.3英寸左右的大小

同样,只要是我们看得到的东西,包括图片,文字,边框粗细,都会在DPI的不同下会看到不一样的大小。

DPI在Flash中可以通过 flash.system.Capabilities.screenDPI 可以获得当前移动设备的DPI。在Flex可以通过 FlexGlobals.topLevelApplication.runtimeDPI 设备的近似DPI,只有160、240、320这3个值。
为什么只有这3个DPI值呢?
先看看各大厂商流行的DPI。

 1. 移动设备分辨率、大小和 DPI 值的示例

生产商 设备 分辨率 (px) 屏幕对角线长度(in) DPI
Apple iPhone 4, iPod 4 960 x 640 3.5 326
Apple iPad 1, iPad 2 1024 x 768 9.7 132
BlackBerry PlayBook 1024 x 600 7 170
HTC Evo 800 x 480 4.3 217
Motorola Atrix 960 x 540 4 275
Motorola Xoom 1280 x 800 10.1 150
Samsung Galaxy Tab 1024 x 600 7 170

在移动设备和桌面设备上,每一家的DPI实际值都不一样,但是综合看起来,他们都非常接近于160, 240, 320 这3个值。
而且通过这3个值也能很好的把握住位图缩放的尺寸,不太容易出现锯齿的情况。

分享到新浪微博 分享到人人网 分享到豆瓣 分享到鲜果 分享到百度空间 分享到开心网 QQ书签 分享到YAHOO! 分享到Google Google Buzz 分享到Facebook 分享到Plurk Digg delicious Technorati Twitter

大家好,我是 Ticore,這次來介紹關於 Flex Compiler 編譯參數與嵌入外部資源的技巧
看到有人問 Embed source 是否能使用變數
一般人直覺的反應大概都是不可能,因為 Embed 是編譯期就決定了
但是對於編譯期的變數呢? 測試之後發現是可行的

Flex Compiler – mxmlc 有支援定義變數的功能
最常見的是用來條件式編譯除錯
Stupid Flex MXMLC Compiler Tricks Part 1: Conditional Compiling

除了 Boolean 之外,它也是可以用來定義字串的
並且能夠指定到 Embed source 上
以下是完整的測試範例:

package {
	import flash.display.Sprite;
 
	public class EmbedTest extends Sprite {
 
		config::EMBED_SRC {
			[Embed(source=config::EMBED_SRC)]
			public var Cls:Class;
		}
 
		public function EmbedTest() {
			config::EMBED_SRC {
				addChild(new Cls());
			}
		}
 
	}
}

mxmlc 編譯參數寫法為

-define+=config::EMBED_SRC,"'OrigamiMurex.jpg'"

假如不想要嵌入外部資源了,改成以下寫法
Class 內相關程式碼區塊都會被 disable

-define+=config::EMBED_SRC,false

分享到新浪微博 分享到人人网 分享到豆瓣 分享到鲜果 分享到百度空间 分享到开心网 QQ书签 分享到YAHOO! 分享到Google Google Buzz 分享到Facebook 分享到Plurk Digg delicious Technorati Twitter

如何更新 Flash CS5.5 內建除錯 Player

11 2011 In: Adobe, Flash

最近一年來 Flash Player 改版速度變快
光是 10 Major Version,就出了 10.1, 10.2, 10.3 三種 Minor Version
而且還有新增少量的 API
倘若習慣用 Flash IDE Test Movie 方式測試開發新 API 程式
官方又沒發更新檔案的話,就會遇到問題了

相較之下,Flash Builder 開發就沒有這種問題
它完全是用獨立版或是外掛版 Flash Player 來除錯
可以自行更新安裝

我找了一下 Google,國外雖然有人遇到這樣問題
只知道 Flash IDE 除錯播放器是這個檔案

C:\Program Files (x86)\Adobe\Adobe Flash CS5.5\Common\Configuration\authplay.dll

可是沒有找到更新版的方法
再用 Google 找 authplay.dll,只有找到 Acrobat 相關安全更新
於是直接往 Adobe 安裝目錄下一找,發現一堆各種版本的 authplay.dll
Acrobat 安裝目錄下有比較新的 10.3 版

手動替換到 Flash CS5.5 安裝目錄下之後
隨便用 Flash IDE 測試輸出版號,結果成功更新了!

但是還是無法用新的 API
這是因為少了新版的 AS3 Global Library – playerglobal.swc
一樣上網找一下發現在這裡可以下載

http://download.macromedia.com/pub/labs/flashplatformruntimes/flashplayer10-3/flashplayer10-3_playerglobal_052011.swc

自行放到以下位置

C:\Program Files (x86)\Adobe\Adobe Flash CS5.5\Common\Configuration\ActionScript 3.0\FP10.3\playerglobal.swc

然後準備一份 10.3 發布設定檔案,直接從 10.2 複製過來修改就好了

C:\Program Files (x86)\Adobe\Adobe Flash CS5.5\Common\Configuration\Players\FlashPlayer10_2.xml

裡面所有 10.2 的都改為 10.3,SWF version 改為 12

<?xml version="1.0" encoding="UTF-8"?>
<players>
  <player id="FlashPlayer10.3" version="12" asversion="3">
   <name>Flash Player 10.3</name>
   <path builtin="true"/>
   <path platform="WIN">Device Central/adcdl.exe</path>
   <path platform="MAC">Device Central/adcdl</path>
   <playerDefinitionPath
      as2="$(UserConfig)/Classes/FP10;$(UserConfig)/Classes/FP9;$(UserConfig)/Classes/FP8;$(UserConfig)/Classes/FP7"
      as3="$(AppConfig)/ActionScript 3.0/FP10.3/playerglobal.swc" />
....

重開 Flash IDE 會發現多出 10.3 發布設定
也能使用並編譯新的 API 了

後記,假如除錯用 Player 只是一個 dll 就搞定,而且被廣泛用於 CS 系列軟體內
那麼是不是有可能讓 Flash CS3 也能開發 Flash Player 10.x 的程式呢?
晚點再來試試看!

好吧,我測試過了 Flash CS3, CS5,Test Movie 就當掉了

分享到新浪微博 分享到人人网 分享到豆瓣 分享到鲜果 分享到百度空间 分享到开心网 QQ书签 分享到YAHOO! 分享到Google Google Buzz 分享到Facebook 分享到Plurk Digg delicious Technorati Twitter

當在 TextInput, TextArea 等文字組件設定 maxChars 最大字數屬性
使用中文輸入法打了數個字到文字佇列上,超過最大限制字數
然後用 Ctrl + Space 切換輸入法或是按下 Enter 方式輸入文字
結果會發現其它 Binding 到 text 屬性的目標無法取得正確的 text 字串
之後再利用按鈕事件 trace 組件的 text 屬性也是取到錯誤的字串資料

來回 trace 好多程式碼位置之後,發現問題主要是出在 RichEditableText 組件上
不過由於問題原因非常複雜,所以要分成很多段落先解釋一些 TLF 與文字組件運作方式

為什麼要花這麼大力氣找原因?
除了解決問題之外,主要還想要了解一下 TLF 文字引擎的運作方式
令我最驚訝的是,輸入法待選文字佇列功能,居然是 TLF 內建,完全用 AS3 寫的!

相關 Bug Report

http://bugs.adobe.com/jira/browse/SDK-28999

http://www.fxug.net/modules/xhnewbb/viewtopic.php?topic_id=4338

RichEditableText maxChars 與輸入法問題測試程式:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
     xmlns:s="library://ns.adobe.com/flex/spark" 
     xmlns:mx="library://ns.adobe.com/flex/mx" fontSize="14">
 <s:layout>
  <s:HorizontalLayout verticalAlign="middle" horizontalAlign="center" />
 </s:layout>
 
 <s:VGroup gap="12">
  <s:Label text="TextInput" />
  <s:TextInput id="txt1" contentBackgroundColor="#DDDDDD"
    maxChars="3" widthInChars="8" />
  <s:TextInput text="{txt1.text}"/>
  <s:Label text="txt1.text: {txt1.text}"/>
  <s:Button label="trace(txt1.text);" click="trace(txt1.text);"/>
 </s:VGroup>
 
 <s:VGroup gap="12">
  <s:Label text="TextArea" />
  <s:TextArea id="txt2" contentBackgroundColor="#DDDDDD"
    maxChars="3" heightInLines="1" widthInChars="8" />
  <s:TextInput text="{txt2.text}"/>
  <s:Label text="txt2.text: {txt2.text}"/>
  <s:Button label="trace(txt2.text);" click="trace(txt2.text);"/>
 </s:VGroup>
 
 <s:VGroup gap="12">
  <s:Label text="RichEditableText" />
  <s:RichEditableText id="txt3" backgroundColor="#DDDDDD"
    paddingLeft="4" paddingRight="4" paddingTop="4" paddingBottom="4"
    maxChars="3" heightInLines="1" widthInChars="8" />
  <s:TextInput text="{txt3.text}"/>
  <s:Label text="txt3.text: {txt3.text}"/>
  <s:Button label="trace(txt3.text);" click="trace(txt3.text);"/>
 </s:VGroup>
</s:Application>

※ RichEditableText 內部 TLF 文字管理
RichEditableText 本身並不直接控制文字表現
而是透過內部一個 TLF TextContainerManager 實體
負責表現、捲動、編輯文字以及文字輸入法佇列
每當文字發生變動
TextContainerManager 有三個事件會依序發出,當然還有其它的 Damage, Composition 等事件

FlowOperationEvent.FLOW_OPERATION_BEGIN
FlowOperationEvent.FLOW_OPERATION_END
FlowOperationEvent.FLOW_OPERATION_COMPLETE

RichEditable 內三個私有偵聽函式負責處理

textContainerManager_flowOperationBeginHandler
處理完立即發出 TextOperationEvent.CHANGING 事件

textContainerManager_flowOperationEndHandler

textContainerManager_flowOperationCompleteHandler
處理完立即發出 TextOperationEvent.CHANGE 事件

在 textContainerManager_flowOperationBeginHandler 內
也會檢查插入文字 + 原本文字總長是否超出 maxChars,並加以裁切
輸入法問題就是出在這裡

※ RichEditableText.text 屬性的內部 cache
RichEditableText 內部用一個私有變數存放 Plain Text 叫做 _text
每當 textFlow, content 被重新指定或是 damage 事件發生時,_text 會被重設為 null
假如取用 text 時 _text 為 null,會從 TextFlow 重新 export plain text 並存放到 _text

※ 從 TextFlow 取得 Plain 文字的方式
var staticPlainTextExporter:ITextExporter =
TextConverter.getExporter(TextConverter.PLAIN_TEXT_FORMAT);
var txt:String = staticPlainTextExporter.export(textFlow, ConversionType.STRING_TYPE) as String;

// 或是

textFlow.getText();

※ RichEditableText maxChars 與輸入法問題的成因
IME 輸入法輸入文字到佇列,然後確認文字是一連串複雜的動作
由於佇列文字是由 TLF 負責表現
當確認佇列文字時,必須先銷毀 TLF 佇列文字
然後復原畫面到不包含佇列文字的狀況
最後再合併輸入輸入文字

以上動作會在 FlowOperationEvent.FLOW_OPERATION_BEGIN 事件後開始進行
預設情況下,由於輸入佇列文字動作比較複雜
TLF EditManager 不會立刻完成所有的動作,而是會透過影格事件分批完成
然後才發出 FlowOperationEvent.FLOW_OPERATION_END 事件
導致此時從 TextFlow 取得的文字並非最後的結果

假如又要檢查 text.length 是否超過 maxChars 的話
會觸發 export plain text 存到 _text 快取
之後完全沒有更新的 _text 快取機會
導致取到的 text 資料都是舊的

※ 為什麼 Flex 4.0 沒有這問題
基本上 RichEditableText 沒有很大差異
而 Flex 4.0 是使用 TLF 1.1,Flex 4.5 是使用 TLF 2.0
主要是 TLF 1.1 TextContainerManager 最後完成所有操作之後
都會再多發一次 Damage 事件,讓 _text 快取被清掉了

※ 強迫 TLF EditManager 立刻完成所有的動作的方式
(Spark ComboBox 會設定 batchTextInput = false 所以才避開了這個問題)

RichEditableText Class:

/**
* @private
* The TLF edit manager will batch all inserted text until the next
* enter frame event. This includes text inserted via the GUI as well
* as api calls to EditManager.insertText(). Set this to false if you
* want every keystroke to be inserted into the text immediately which will
* result in a TextOperationEvent.CHANGE event for each character. One
* place this is needed is for the type-ahead feature of the editable combo
* box.
*/
mx_internal var batchTextInput:Boolean = true;
以上屬性會在 RichEditableTextContainerManager createEditManager 時
設定到 EditManager 上
editManager.allowDelayedOperations = textDisplay.batchTextInput;

※ 問題解決方式
方式 1.
修改 RichEditableText 內的 textContainerManager_flowOperationBeginHandler 函式
取用完 text.length 之後,指定 _text 為 null

if (maxChars != 0) {
var length1:int = text.length – delLen;
var length2:int = textToInsert.length;
if (length1 + length2 > maxChars)
textToInsert = textToInsert.substr(0, maxChars – length1);
_text = null;
}
方式 2.
在 RichEditableText 組件 change 事件上,重新指定 textFlow,強迫更新 _text 快取

var tf:TextFlow = event.target.textFlow;
event.target.textFlow = new TextFlow();
event.target.textFlow = tf;
Spark TextInput, TextArea 可以在自訂 Skin 裡面添加以上程式

方式 3. (建議)
在 RichEditableText 組件 preinitialize 事件中,設定 batchTextInput 為 false

event.target.mx_internal::batchTextInput = false;
Spark TextInput, TextArea 可以在自訂 Skin 裡面添加以上程式

分享到新浪微博 分享到人人网 分享到豆瓣 分享到鲜果 分享到百度空间 分享到开心网 QQ书签 分享到YAHOO! 分享到Google Google Buzz 分享到Facebook 分享到Plurk Digg delicious Technorati Twitter