[Workaround] AIR randomly crash on Mac

这几天很郁闷, AIR 会时不时的罢工:

AIR Crash

这个错误属于 AIR 内部 crash,没有任何 actionscript error 抛出来,我一句一句 trace 最后发现发生在 sqlite 的 sql 语句调用。但。。。它又不一定每次都会 crash。。。。

基本上如果最后的产品在 Mac 上跑然后撞到这个问题就会很欢乐。。。

不幸的是,crash 的 workaround 又被我我无情的发现了。

写轮眼

根本原因是 AIR 在 de-serialize 数据库中的 column 数据时,如果多个 column 都是复杂对象,而且对象的 ref 又纠结在一起的时候就会 boom。

解决办法就是在写入 complex object 的时候先手动 serialize 到

1
ByteArray

里,然后再塞给 db,取数据的时候再手动把

1
ByteArray

de-serialize 还原成 complex object。

详细的 crash reproduce 以及 workaround 实例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="init()">
    <mx:Script>
        <![CDATA[
            private namespace safe;        
            private namespace normal;
           
            private function init():void
            {
                use namespace safe;
                //use namespace normal; //如果把 namespace 换成 normal 在 mac 上会造成 crash
               
                var dbFile:File = new File("app-storage:/test.db");
                if(dbFile.exists)
                {
                    dbFile.deleteFile();
                }
                var connection:SQLConnection = new SQLConnection();
                connection.open(dbFile);
               
                connection.begin();
                executeSql(connection,"CREATE TABLE foo (id INTEGER PRIMARY KEY AUTOINCREMENT, bar0 OBJECT NOT NULL, bar1 OBJECT NOT NULL);");
                connection.commit();
               
                //喂点复杂的 object graph 给 sqlite...
                var obj0:Object = {prop0:"blah"};
                var obj1:Object = {prop0:obj0, prop1:"test"};
                var obj2:Object = {prop0:obj1, prop2: obj0};
               
                connection.begin();
                executeSql(connection,"INSERT INTO foo (bar0, bar1) VALUES(:bar0, :bar1);",{bar0:obj1,bar1:obj2});
   
                for(var i:int=0;i<1000;i++)
                {
                    obj0.prop0=Math.random();
                    executeSql(connection,"UPDATE foo SET bar0=:bar0, bar1=:bar1;",{bar0:obj1,bar1:obj2});
                    executeSql(connection,"SELECT * FROM foo");
                }
               
                connection.commit();
               
            }

            normal function executeSql(connection:SQLConnection,str:String,param:Object=null,mapping:Class=null):SQLResult
            {
                var s:SQLStatement = new SQLStatement();
                s.sqlConnection = connection;
                s.text = str;              
                if(param!=null)
                {
                    for(var k:String in param)
                    {
                        var p:Object = param[k];
                        s.parameters[":"+k] = p;
                    }
                }              
                if(mapping!=null)
                {
                    s.itemClass = mapping;
                }
                s.execute();               
                return s.getResult();
            }
           
            safe function executeSql(connection:SQLConnection,str:String,param:Object=null,mapping:Class=null):SQLResult
            {

                var s:SQLStatement = new SQLStatement();
                s.sqlConnection = connection;
                s.text = str;
               
                if(param!=null)
                {
                    for(var k:String in param)
                    {
                        var p:Object = param[k];
                        if(!isPlainType(p))
                        {//将 复杂 Object 先 write 到 byteArray 里
                            p = objectToByteArray(p);
                        }
                        s.parameters[":"+k] = p;
                    }
                }
               
                if(mapping!=null)
                {
                    s.itemClass = mapping;
                }
                s.execute();
               
                var result:SQLResult = s.getResult();
                if(result.data!=null)
                {
                    var c:int = result.data.length;
                    for(var i:int=0;i<c;i++)
                    {
                        var row:Object = result.data[i];
                        for(var j:String in row)
                        {
                            var val:Object = row[j];
                            if(val is ByteArray)
                            {// 还原 byteArray 里面的 object
                                val = objectFromByteArray(val as ByteArray);
                                row[j]=val;
                            }
                        }
                    }
                }
                return result;
            }
           
            private function isPlainType(obj:Object):Boolean
            {
                var type:String = typeof(obj);
                switch (type)
                {
                    case "number":
                    case "string":
                    case "boolean":
                    {
                        return true;
                    }
                    case "object":
                    {
                        return (obj is Date);
                    }
                }
                return false;
            }
           
            private function objectToByteArray(obj:Object):ByteArray
            {
                var bytes:ByteArray = new ByteArray();
                bytes.writeObject(obj);
                bytes.position=0;
                return bytes;
            }
           
            private function objectFromByteArray(bytes:ByteArray):Object
            {
                return bytes.readObject();
            }
        ]]>
    </mx:Script>
</mx:WindowedApplication>

E4X 的另类应用

一直以来我们在 actionscript 中内嵌大段的多行文本时必须在每一号加上引号,然后再在结尾写加号。问题是一天三遍的写,太麻烦了。。。一直也没见过什么解决办法(除非写个外部文件载入,不过那个不爽,请忽略)。直到有一天。。。也就是昨天晚上,我一不小心想到了一个解决方案,大家请看:

1
2
3
4
5
6
7
8
9
10
11
12
13
var t:String=plain()(<!--
The Flash Player API classes are in the flash.* packages.
The Flash Player API refers to all packages, classes, functions,
properties, constants, events, and errors that are in the flash package.
They are unique to Flash Player, as opposed to the top-level classes,
such as Date, Math, and XML, or the language elements, which are based
on ECMAScript. The Flash Player API contains features that you expect
to find in object-oriented programming languages, such as the
flash.geom package for geometry classes, as well as features specific
to the needs of rich Internet applications, such as the flash.filters
package for expressiveness and the flash.net package for handling data
transmission to and from a server.
-->);

这个 plain 方法把代码中的一整段文本复给 t 变量,而字符串本身不用加引号!关键点就在 标识符,挖哈哈,这个 e4x 的应用还不错吧。

另外一个想到的应用是不用 Flex 的 embed 标签也可以内嵌图片等二进制元素到源码中,因为 Flex 的 embed 语法是不兼容 flash9 的,而且需要莫名其妙的引入 mx 包下的一些类。所以这个应用也是有一定用处地:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var b:String=plain(/W*/g)(<!--
89504e470d0a1a0a0000000d49484452
0000000e000000100803000000114abe
080000000467414d410000afc837058a
e90000001974455874536f6674776172
650041646f626520496d616765526561
647971c9653c0000000f504c5445ffff
ffff0000cccccc9999990000003952e0
8a000000314944415478da626046010c
cc0c08c0c4423a9791118261b28c8c20
1e4231988743165d2fb9ce6042022c0c
2c280020c0006dc501097dfe85550000
000049454e44ae426082
-->);

至于这一大堆乱码是什么意思,大家意会吧。。。

plain 方法的具体实现就不说了,很简单。有兴趣的朋友可以 在这里下载源码和实例 研究一蛤。

另外通过这个方法,我还发现了 as3 的编译器,以及 flex builder 2 的 as3 编辑器在词法分析上还是存在问题的。比如有时候我在 之间加入一整段类/包定义,编译器居然报错了。。。而且编辑器也很勤劳的加上语法高亮什么的。。。

ContextMenuManager

In Flex, only top-level components in the application can have context menus. For example, if a DataGrid control is a child of a TabNavigator or VBox container, the DataGrid control cannot have its own context menu.

这是引自Flex文档里的一段话,而且Flash里也是这样,嵌套的MovieClip没法设置menu。我觉得这实在是没道理,在实际应用中,我们很容易碰到需要给嵌套MovieClip设置menu的需求。比如在一个Tree组件中,我们有时会需要根据上下文为每一个TreeNode指定不同的ContextMenu。

不过这种需求在现在MM给的环境中无法直接实现。

于是我开始动手自己写了个ContextMenuManager,目的很明确,就是可以给任意一个MovieClip或者TextField添加相关的ContextMenu。

完整的源码和示例文件可以 从这里下载

ContextMenuManager常规的用法包含以下几步:

  • 从ContextMenuManager中获取一个新的ContextMenu;
1
var cm:ContextMenu = ContextMenuManager.newMenuTemplate();
  • 向ContextMenu中添加ContextMenuItem;
1
ContextMenuManager.appendCustomMenuItem(cm,"pawa!");
  • 把ContextMenu添加到指定的movieclip或者textfield上。
1
ContextMenuManager.setMenu(yourMovieclipOrTextField,cm);

就这样!Enjoy!