[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>