Típusos adatok kimentésére régen is volt lehetőség erre szolgált a SharedObject ami szintén AMF formátumban mentette az adatainkat azonban a lementett bináris fájlokhoz nem férünk hozzá. Hogyha egyszerűen átláthatóan akartunk adatokat lementeni XML-t vagy más szöveges formátumot használhattunk. AS3-ban továbbra is lehetőség van SharedObject-el menteni és visszatölteni adatokat azonban ha
nekünk egy adatbázisba kellene letárolni a flash mozink egy bizonyos állapotát, sablonokat vagy komplex objektumokat kellene lementeni, használhatjuk a ByteArray osztályt. Ez az osztály hasonlít egy tömb-re csak annyiban különbözik hogy byte-okat tárol és rendelkezik olyan metódusokkal amiknek segítségével különböző adattípusokat lehet beszúrni a byte folyamba. Egyik hasznos metódusa a writeObject(object:*):void aminek segítségével egy AS objektum szúródik be valamint a readObject():* ami pedig kiolvas egy objektumot a position által mutatott pozíciótól kezdve.
A mentés egy módszerét a lenti alkalmazás szemlélteti. Használatához csak hozzá kell adnunk pár adatot a listákhoz, majd vágólapra másolnunk a lent megjelenő szöveget ami lap újratöltés után visszamásolhatunk és a betöltés gombra kattintva viszont láthatjuk a korábban hozzáadott adatainkat.
Típusos adatok mentése
import flash.utils.ByteArray; var object:Object = new Object() object.name = "test" object.num = 1 var bytes:ByteArray = new ByteArray() bytes.writeObject(object) trace(bytes.position) //21 bytes.position = 0 var newObj:Object = bytes.readObject() trace(newObj.name) // test trace(newObj.num) // 1 trace(newObj.num+newObj.num) // 2
Ebben a mintában létrehozunk egy sima objektumot amin létrehozunk 2 tulajdonságot majd beleírjuk azt a ByteArray-ba. Amikor ez megtörténik flash átnézi az objektum struktúráját és binárisan leírja a szerkezetét valamint menti a benne található adatok értékeit. Mielőtt visszaolvasnánk a pozíciót be kell állítanunk 0-ra mivel írás után ez a legutolsó pozíciót veszi fel ahol nem kezdődik objektum leírás. Olvasásnál a struktúra elemzésével újra felépíti az objektumot ami ebben az esetben egy pontos másolat lesz. Gyakran használják ezt a módszert másolásra is mivel minden szinten minden tulajdonság újra létrejön és nem fog referenciákat tartalmazni.
import flash.utils.ByteArray;
import flash.utils.getQualifiedClassName;
import flash.display.MovieClip;
var object:Object = new Object()
object.name = "test2"
object.date = new Date()
var items:Array = new Array()
items.push("Elem1")
items.push("Elem2")
object.items = items
var bytes:ByteArray = new ByteArray()
bytes.writeObject(object)
trace(bytes.position) //54
bytes.position = 0
var newObj:Object = bytes.readObject()
trace(newObj.name) // test2
trace(newObj.items) // Elem1,Elem2
trace(getQualifiedClassName(newObj.date)) // Date
trace(newObj.date.fullYear) //2010
trace(newObj.items == items) // false
Ebben a mintában már egy tömböt és egy Date-et is tároltam majd visszaolvasás után probléma nélkül használhatóak lettek és a típusukat is megtartották. Az utolsó trace üzenetből látszik hogy az új tömböm nem azonos a mentettel tehát egy pontos másolat keletkezet, ez a másolás több szinten is végrehajtódik. A dátum objektumom is visszakaptam ami szintén megőrizte a típusát és az összes értéket amit tárolt.
A következő lépés hogy hogyan tudnánk a ByteArray tartamát valhogyan String-ként megkapni. Erre létezik a neten sok megvalósítás én ezután a Base64 generic buspar buy formátumot fogom használni. Ha valakinek nem lenne Base64 konvertáló osztálya megtalálhatja az optimalizált verziót itt:
http://code.google.com/p/jpauclair-blog/source/browse/trunk/Experiment/Base64/src/Base64.as vagy letöltheti innen.
Base64.zip
Mivel ez a verzió Vector-t használ aki nem tud flash player 10-re csak 9-re exportálni használjon valami régebbi megvalósítást például ezt:
http://garry-lachman.com/2010/04/21/base64-encoding-class-in-actionscript-3/
Ha megvan a szimpatikus Base64 konvertáló osztály rakjuk a src mappánkba vagy az fla fájlunk mellé és már kezdhetjük is használni. A Base64 bináris adatokhoz karaktereket (64 db) rendel amik mind biztonságosan használhatók webes környezetben. Módosítsuk a kódunkat, hogy most már ezt is használja.
import flash.utils.ByteArray;
var object:Object = new Object()
object.name = "test3"
object.num = 1
var str:String = convertObj(object)
trace(str) //CgsBB251bQQBCW5hbWUGCXRlc3QB
var newObj:Object = createObj(str)
trace(newObj.name) // test3
trace(newObj.num) // 1
function convertObj(obj:*):String
{
try{
var bytes:ByteArray = new ByteArray()
bytes.writeObject(obj)
return Base64.encode(bytes)
}catch(error:Error){
trace("Hiba konvertáláskor ", error.message)
}
return null
}
function createObj(base64Str:String):*
{
try{
var bytes:ByteArray = Base64.decode(base64Str)
bytes.position = 0
return bytes.readObject()
}catch(error:Error){
trace("Hiba létrehozáskor ", error.message)
}
return null
}
A convertObj függvényünk létrehozza a String reprezentációt amit a createObj függvényünk olvas vissza. Látható hogy a 2 művelet közé könnyen beiktatható egy php-val fájlba vagy adatbázisba mentés így lehetőségünk van komplex adatstruktúrákat pár sorral lementeni majd pont olyan egyszerűen visszatölteni a típusok elvesztése nélkül.
Saját osztályok mentése
Amennyiben saját osztályokat kívánunk menteni be kell regisztrálnunk azokat a registerClassAlias(aliasName:String, classObject:Class):void metódus segítségével. Az aliasName egy tetszőleges név lesz amit a player eltárol a kód futása során a classObject pedig a hozzá rendelt osztály. A név minden objektum elkódolásnál elmentődik és visszatöltéskor a player kikeresi a hozzá tartozó osztályt, készít belőle egy példányt majd beállítja rajta a publikus tulajdonságokat.
Használni fogunk 2 minta osztályt amiket elmentünk majd később visszaöltünk továbbá átrakjuk a mentést és a visszatöltés egy külön statikus osztályba.
data/ItemHolder.as
Egyszerű, elemeket tömbben táróló osztály.
package data
{
public class ItemHolder
{
public var items:Array
public function ItemHolder()
{
items = new Array()
}
public function addItem(item:*):void
{
items.push(item)
}
public function getItem(index:int):*
{
return items[index]
}
}
}
data/DataHolder.as
Egyszerű, kulcs érték párokat objektumban tároló osztály.
package data
{
public class DataHolder
{
public var itemHolder:ItemHolder
public var properties:Object
private var _name:String
public function get name():String { return _name; }
public function set name(value:String):void{
_name = value;
}
public function DataHolder(name:String=null)
{
_name = name
itemHolder = new ItemHolder()
properties = new Object()
}
public function addProperty(key:String, value:*):void
{
properties[key] = value
}
public function getProperties(key:String):*
{
return properties[key]
}
}
}
util/SaveUtil.as
package util
{
import flash.net.registerClassAlias;
import flash.utils.ByteArray;
public class SaveUtil
{
public static function register(alias:String, clazz:Class):void
{
registerClassAlias(alias, clazz);
}
public static function convertObj(obj:*):String
{
var bytes:ByteArray = new ByteArray()
bytes.writeObject(obj)
return Base64.encode(bytes)
}
public static function createObj(base64Str:String):*
{
var bytes:ByteArray = Base64.decode(base64Str)
bytes.position = 0
return bytes.readObject()
}
}
}
Ezek lesznek a majd később mentést szemléltető osztályok, lehet látni hogy a DataHolder tárol egy ItemHolder példányt ami a saját osztályok egymáson belül használatát szemlélteti.
Fontos odafigyelni az osztályok felépítésnél hogy nem rendelkezhet kötelezően megadandó konstruktor paraméterrel mivel akkor nem tud a player új példányt létrehozni belőlük és hibát fogunk kapni. Továbbá a privát tulajdonságok értékei nem mentődnek és állítódnak vissza, tehát ha egy alap értékkel rendelkező konstruktornak egy eltérő értéket adunk át de az nem érhető el se publikus tulajdonságból sem geter, seter segítségével nem fog visszatöltődni. A DataHolder-ben láthatunk erre egy mintát. Mivel a name megadható konstruktorban is de létezik hozzá geter, seter pár ezért visszatöltéskor ez is be fog állítódni de nem a konstruktor hanem a set name segítségével.
A menteni kívánt osztályok regisztrációját még mentés vagy betöltés előtt meg kell tennünk ezek utána minden olyan műveletnél felhasználásra kerülnek ami valamilyen módon bináris formába konvertálja az objektumokat. Az alábbi osztályokban történik ilyen:
LocalConnection, ByteArray, SharedObject, NetConnection, NetStream
import data.DataHolder;
import data.ItemHolder;
import util.SaveUtil;
private function test():void
{
SaveUtil.register("reg.DataHolder", DataHolder);
SaveUtil.register("reg.ItemHolder", ItemHolder);
var dataHolder:DataHolder = new DataHolder("holder")
dataHolder.addProperty("date", new Date())
var itemHolder:ItemHolder = new ItemHolder()
itemHolder.addItem("Elem1")
itemHolder.addItem("Elem2")
dataHolder.itemHolder = itemHolder
var str:String = SaveUtil.convertObj(dataHolder)
trace("Base64:",str)
var savedHolder:DataHolder = SaveUtil.createObj(str)
trace("Name:",savedHolder.name)
trace("Year:",savedHolder.getProperties("date").fullYear)
trace("Item0:",savedHolder.itemHolder.getItem(0))
}
A fenti kód futtatva a következő outputot írja a konzolra:
Base64: CjMdcmVnLkRhdGFIb2xkZXIVaXRlbUhvbGRlchVwcm9wZXJ0aWVzCW5hbWUKEx1yZWcuSXRlbUhvbGRlcgtpdGV tcwkFAQYLRWxlbTEGC0VsZW0yCgsBCWRhdGUIAUJyiKcZFLAAAQYNaG9sZGVy Name: holder Year: 2010 Item0: Elem1
Komplex adat vagy display objektumok mentése.
Mivel a writeObject alap esetben a paraméterül kapott objektum minden publikus tulajdonságát lementi akkor is ha azokon az alap értékek vannak beállítva, sok felesleges adat mentődne le mondjuk egy Sprite alapú osztály mentésekor amikre semmi szükség. Ez elkerülendő több lehetőségünk is van a feladat megoldására a problémától függően.
IExternalizable interface
Amennyiben saját osztályok kimentését akarjuk megvalósítani amik private tulajdonságokkal is rendelkeznek, lehetőségünk van implemnetálni az IExternalizable interface-t ami 2 publikus metódust definiál.
readExternal(input:IDataInput):void writeExternal(output:IDataOutput):void
Ezeket metódusokat flash automatikusan hívja meg amikor a kiírás vagy visszaolvasás megtörténik paraméterül pedig IDataOutput,IDataInput típusú objektumot kapnak amikre nekünk kell mentenünk vagy kiolvasnunk róluk a szükséges változókat. Az IDataOutput, IDataInput szintén egy interface ami pár fontosabb kiíró és beolvasó utasítást definiál ezeket közül egyet vagy mindkettőt implementálják a bináris adatmanipulációt megengedő osztályok az adatfolyam irányától függően. Például a ByteArray osztály mindkettőt az URLStream viszont csak az input-ot. Lássuk hogy néznének ki a fent használt osztályok ha private tulajdonságokat is tartalmaznának és implementálnák az IExternalizable interface-t.
data/DataHolder.as
package data
{
import flash.utils.IDataInput;
import flash.utils.IDataOutput;
import flash.utils.IExternalizable;
public class DataHolderP implements IExternalizable
{
private var properties:Object
private var _itemHolder:ItemHolderP
private var _name:String
public function get name():String { return _name; }
public function get itemHolder():ItemHolderP
{
return _itemHolder
}
public function DataHolderP(name:String=null)
{
_name = name
_itemHolder = new ItemHolderP()
properties = new Object()
}
public function addProperty(key:String, value:*):void
{
properties[key] = value
}
public function getProperties(key:String):*
{
return properties[key]
}
public function writeExternal(output:IDataOutput):void
{
output.writeObject(properties)
output.writeObject(_itemHolder)
output.writeUTF(_name)
}
public function readExternal(input:IDataInput):void
{
properties = input.readObject()
_itemHolder = input.readObject()
_name = input.readUTF()
}
}
}
data/ItemHolder.as
package data
{
import flash.utils.IDataInput;
import flash.utils.IDataOutput;
import flash.utils.IExternalizable;
public class ItemHolderP implements IExternalizable
{
private var items:Array
public function ItemHolderP()
{
items = new Array()
}
public function addItem(item:*):void
{
items.push(item)
}
public function getItem(index:int):*
{
return items[index]
}
public function writeExternal(output:IDataOutput):void
{
output.writeObject(items)
}
public function readExternal(input:IDataInput):void
{
items = input.readObject()
}
}
}
Látható hogy mostmár nekünk kell megírni az adatok mentését majd visszatöltését. Fontos hogy a típusnak megfelelő metódust hívjuk meg és hogy mostmár a publikus tulajdonságok mentéséről is nekünk kell gondoskodnunk. Amennyiben egy olyan osztályból származtatunk amelyik már implementálja az IExternalizable interface-t felül kell írnunk a kiíró és beolvasó metódusokat és az új tulajdonságokra szintén meg kell írni a mentést és visszatöltést.
Saját megvalósítás készítése
Mivel a fenti módszer használatához mindenképpen imlementálni kell a már említett interface-t előfordulhat hogy olyan oszályokat kell mentenünk amikhez nincs hozzáférésünk így kényelmetlen lenne származtatni mindegyikből egy újat csak a szükséges interface miatt. A private tulajdonságok mentésének igénye nélkül 2 extra osztály megírásával gond nélkül tudjuk menteni és visszaálíltani a lent bemutatott módszerrel is. Ehhez szükségünk lesz egy olyan osztályra ami hasonló módon fog működni mint ahogyan flash készíti el a bináris leírásból a mentett objektumokat. Szükségünk lesz egy ItemInfo nevű osztályra ami elmenti a szükséges adatokat valamint egy ClassRegister-re ami központilag fogja számon tartani az ItemInfo-k által használt alias és osztály kapcsolatokat.
data/ItemInfo.as
Komplex osztályok megadott értékeinek mentésért és újra létrehozásáért felelős osztály.
package data
{
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
public class ItemInfo
{
public var classNameAlias:String
public var properties:Object
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
public function ItemInfo(obj:Object=null, props:Object=null)
{
if(obj){
classNameAlias = ClassRegister.getInfoAliasForomInstance(obj)
}
if(props){
properties = props
}else{
properties = new Object()
}
}
//--------------------------------------------------------------------------
//
// Public Methods
//
//--------------------------------------------------------------------------
public function addProperty(base:Object, name:String):void
{
properties[name] = base[name]
}
public function createClass():*
{
var clazz:Class = ClassRegister.getInfoClassFromAlias(classNameAlias)
var factory:ClassFactory = new ClassFactory(clazz, properties)
return factory.newInstance()
}
public function clone():ItemInfo
{
var info:ItemInfo = new ItemInfo()
info.classNameAlias = classNameAlias
for(var key:String in properties){
info.properties[key] = properties[key]
}
return info
}
}
}
//--------------------------------------------------------------------------
//
// internal ClassFactory
//
//--------------------------------------------------------------------------
internal class ClassFactory
{
public var clazz:Class;
public var properties:Object;
public function ClassFactory(clazz:Class = null, properties:Object=null)
{
this.clazz = clazz;
this.properties = properties
}
public function newInstance():*
{
var instance:Object = new clazz();
if (properties != null){
for (var p:String in properties){
instance[p] = properties[p];
}
}
return instance;
}
}
ClassRegister.as
Központilag az ItemInfo aliasokat tároló osztály.
package
{
import data.ItemInfo;
import flash.net.registerClassAlias;
import flash.text.TextField;
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;
public class ClassRegister
{
private static var infoClassMap:Dictionary = new Dictionary()
public static function inititalize():void
{
registerClass("reg.ItemInfo", ItemInfo)
registerInfoClass("reg.TextField", TextField)
}
public static function registerInfoClass(classNameAlias:String, clazz:Class):void
{
infoClassMap[classNameAlias] = clazz
}
public static function registerClass(alias:String, clazz:Class):void
{
registerClassAlias(alias, clazz);
}
public static function getInfoClassFromAlias(classNameAlias:String):Class
{
if(infoClassMap[classNameAlias] == null){
throw new Error("Alias "+classNameAlias+" not found")
}
return infoClassMap[classNameAlias]
}
public static function getInfoAliasForomInstance(instance:Object):String
{
for(var key:String in infoClassMap){
if(getQualifiedClassName(instance) == getQualifiedClassName(infoClassMap[key])){
return key
}
}
throw new Error("Info alias for instance "+getQualifiedClassName(instance)+" not found")
return null
}
}
}
A ClassRegister központilag tárolja az inititalize() metódusában beregisztrált osztályokat. A registerClass metódusa a palayeren belül végzi el a regisztrációt a registerInfoClass pedig helyben tárolja. Mivel az ItemInfo osztályok csak az alias nevet tárolják amit később visszatöltéskor valahogy példányosítaniuk kell innen fogják lekérni hogy egy alias-hoz milyen osztály definíció lett hozzárendelve. Lehetőség lenne alias helyett az osztály teljes nevét menteni és visszakéréskor azt használni ekkor nem lenne szükség a központi alias regisztrációra de ha struktúrát változtatnák vagy osztály nevet akkor már nem lenne elérhető később. A mentés a fenti osztályok használatával a következőképpen nézne ki.
import data.ItemInfo;
import util.SaveUtil;
ClassRegister.inititalize()
private function test():void
{
var oldField:TextField = new TextField()
oldField.text = "This is some text"
oldField.width = 200
oldField.height = 20
oldField.border = true
var oldInfo:ItemInfo = new ItemInfo(oldField)
oldInfo.addProperty(oldField, "text")
oldInfo.addProperty(oldField, "width")
oldInfo.addProperty(oldField, "height")
oldInfo.addProperty(oldField, "border")
var base64Str:String = SaveUtil.convertObj(oldInfo)
var newInfo:ItemInfo = SaveUtil.createObj(base64Str)
var newField:TextField = newInfo.createClass()
addChild(newField)
}
A kódban először létrehozunk egy TextField-et beálíltjuk a text tulajdonságát és még pár extra értéket. Ezután készítünk egy ItemInfo-t ami elmenti pár tulajdonságát. Dekódolás után egy info objektumot kapunk ami még csak az elmentett alias nevet és a szükséges tulajdonságokat fogja tárolni. A createClass() meghívása után lekéri az alias-hoz rendelt osztály definíciót amit példányosít, majd a szintén mentett tulajdonságokat sorban beállítja rajta így megkapunk egy a leírásnak megfelelő másolatot.
Nem csak mentésere lehet használni ezt a módszert hanem objektumok klónozására is alkalmas. Az alábbi sorral kapunk egy másolatot a TextField objektumról.
var oldInfo:ItemInfo = new ItemInfo(oldField) oldInfo.addProperty(oldField, "text") oldInfo.addProperty(oldField, "width") oldInfo.addProperty(oldField, "height") oldInfo.addProperty(oldField, "border") var copyField:TextField = oldInfo.createClass()
A mintákból lehet látni mennyire komplex adat struktúrákat lehet egyszerűen kimenteni típus vesztés nélkül. SharedObject-et használata esetén a szöveggé konvertálásra nincs is szükségünk a regisztrációs lépések után menthetünk a gépre adatokat vagy saját osztályokat így nem kell bonyolult XML struktúrák kialakításával és betöltésével bajlódnunk.
Források: SaveTypedData.zip





Mindent lehet azt is amit nem, ez a jelszó mára.
Oké-oké, nem akartam agresszívnek tűnni
Egy már meglévő TextField-et (vagy bármi hasonlót, ami nincs felkészítve rá) természetesen csak kívülről lehet szelektíven szerializálni. És persze megoldani is mindent lehet
“Interfészről van szó, bármelyik osztály implementálhatja, nem kell egy bizonyos közös ősből származtatni a szerializálandó osztályokat.”
Ha bármelyik implementálhatja akkor például a beépített TextField osztályt hogyan tennénk serializálhatóvá ha nem származtatunk belőle egy olyan osztályt ami implentálja az interface-t?
“Ezt semmilyen deszerializáció nem kezeli, se a readObject() alap AMF deszerializációja, se a tiéd”
Nem írtam egy szóval sem hogy kezeli azt írtam hogy meg tudnánk oldani ami igaz
Amúgy pedig a témakör adatok osztályok mentése visszatöltése nem serializáció és ennek is többféle módja van az enyém csak az egyik. A leírást viszont frissítettem talán most már látható a 2 módszer közti különbség előnyei, hátrányai.
De persze ha egy olyan osztályt kell szelektíven szerializálni, ami nincs felkészítve rá, akkor ez az ItemData-s dolog mindenképpen jó megoldás.
“származtatni kell a menteni kívánt osztályt”
Interfészről van szó, bármelyik osztály implementálhatja, nem kell egy bizonyos közös ősből származtatni a szerializálandó osztályokat.
“kötelező konstruktor paraméterrel rendelkező osztályokat nem is lehetne vele menteni”
Ezt semmilyen deszerializáció nem kezeli, se a readObject() alap AMF deszerializációja, se a tiéd
Ez egy ilyen dolog, kötelező paraméterű konstruktoros objektumot ne (de)szerializáljon az ember, hacsak egyedileg nem okosítja ki a deszerializációt vezénylő programrészt az egyes osztályok konstruktorának kezeléséről (de általános esetben ilyet az ember nem csinál
)
Igaz az IExternalizable interface kimaradt a leírásból viszont mint mindennek annak is vannak hátrányai például hogy származtatni kell a menteni kívánt osztályt amit ItemInfo használatával anélkül is egyszerűen meg lehet oldani, továbbá kötelező konstruktor paraméterrel rendelkező osztályokat nem is lehetne vele menteni viszont mivel saját magunk irányítjuk az utolsó mintában a serializációt ezt meg tudnánk oldani.
A leírást mindenképpen bővíteni fogom mert egy fontos funkciót felejtettem ki köszönöm.
Szép ez a saját szerializáció, de felhívnám a figyelmet az IExternalizable interfész implementálásának lehetőségére. Ezzel talán egy fokkal egyszerűbb a dolog