JPEGEncoder处理类,据说比官网的快4倍!

2011年11月22日 没有评论
package
{
	import flash.display.BitmapData;
	import flash.utils.ByteArray;

	public final class JPEGEncoder
	{
		// Static table initialization
		private const ZigZag:Vector.<int> = Vector.<int>([
			 0, 1, 5, 6,14,15,27,28,
			 2, 4, 7,13,16,26,29,42,
			 3, 8,12,17,25,30,41,43,
			 9,11,18,24,31,40,44,53,
			10,19,23,32,39,45,52,54,
			20,22,33,38,46,51,55,60,
			21,34,37,47,50,56,59,61,
			35,36,48,49,57,58,62,63
		]);
		private var YTable:Vector.<int> = new Vector.<int>(64, true);
		private var UVTable:Vector.<int> = new Vector.<int>(64, true);
		private var outputfDCTQuant:Vector.<int> = new Vector.<int>(64, true);
		private var fdtbl_Y:Vector.<Number> = new Vector.<Number>(64, true);
		private var fdtbl_UV:Vector.<Number> = new Vector.<Number>(64, true);
		private var sf:int;

		private const aasf:Vector.<Number> = Vector.<Number>([
				1.0, 1.387039845, 1.306562965, 1.175875602,
				1.0, 0.785694958, 0.541196100, 0.275899379
			]);

		private var YQT:Vector.<int> = Vector.<int>([
				16, 11, 10, 16, 24, 40, 51, 61,
				12, 12, 14, 19, 26, 58, 60, 55,
				14, 13, 16, 24, 40, 57, 69, 56,
				14, 17, 22, 29, 51, 87, 80, 62,
				18, 22, 37, 56, 68,109,103, 77,
				24, 35, 55, 64, 81,104,113, 92,
				49, 64, 78, 87,103,121,120,101,
				72, 92, 95, 98,112,100,103, 99
			]);

		private const UVQT:Vector.<int> = Vector.<int>([
				17, 18, 24, 47, 99, 99, 99, 99,
				18, 21, 26, 66, 99, 99, 99, 99,
				24, 26, 56, 99, 99, 99, 99, 99,
				47, 66, 99, 99, 99, 99, 99, 99,
				99, 99, 99, 99, 99, 99, 99, 99,
				99, 99, 99, 99, 99, 99, 99, 99,
				99, 99, 99, 99, 99, 99, 99, 99,
				99, 99, 99, 99, 99, 99, 99, 99
			]);

		private function initQuantTables(sf:int):void
		{
			var i:int;
			const I64:int = 64;
			const I8:int = 8;
			for (i = 0; i < I64; ++i)
			{
				var t:int = int((YQT[i]*sf+50)*0.01);
				if (t < 1) {
					t = 1;
				} else if (t > 255) {
					t = 255;
				}
				YTable[ZigZag[i]] = t;
			}

			for (i = 0; i < I64; i++)
			{
				var u:int = int((UVQT[i]*sf+50)*0.01);
				if (u < 1) {
					u = 1;
				} else if (u > 255) {
					u = 255;
				}
				UVTable[ZigZag[i]] = u;
			}
			i = 0;
			for (var row:int = 0; row < I8; ++row)
			{
				for (var col:int = 0; col < I8; ++col)
				{
					fdtbl_Y[i]  = (1 / (YTable [ZigZag[i]] * aasf[row] * aasf[col] * I8));
					fdtbl_UV[i] = (1 / (UVTable[ZigZag[i]] * aasf[row] * aasf[col] * I8));
					i++;
				}
			}
		}

		private var YDC_HT:Vector.<BitString>;
		private var UVDC_HT:Vector.<BitString>;
		private var YAC_HT:Vector.<BitString>;
		private var UVAC_HT:Vector.<BitString>;

		private function computeHuffmanTbl(nrcodes:Vector.<int>, std_table:Vector.<int>):Vector.<BitString>
		{
			var codevalue:int = 0;
			var pos_in_table:int = 0;
			var HT:Vector.<BitString> = new Vector.<BitString>(251, true);
			var bitString:BitString;
			for (var k:int=1; k<=16; ++k)
			{
				for (var j:int=1; j<=nrcodes[k]; ++j)
				{
					HT[std_table[pos_in_table]] = bitString = new BitString();
					bitString.val = codevalue;
					bitString.len = k;
					pos_in_table++;
					codevalue++;
				}
				codevalue<<=1;
			}
			return HT;
		}

		private var std_dc_luminance_nrcodes:Vector.<int> = Vector.<int>([0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]);
		private var std_dc_luminance_values:Vector.<int> = Vector.<int>([0,1,2,3,4,5,6,7,8,9,10,11]);
		private var std_ac_luminance_nrcodes:Vector.<int> = Vector.<int>([0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]);
		private var std_ac_luminance_values:Vector.<int> = Vector.<int>([0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
																				0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
																				0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
																				0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
																				0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
																				0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
																				0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
																				0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
																				0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
																				0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
																				0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
																				0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
																				0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
																				0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
																				0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
																				0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
																				0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
																				0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
																				0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
																				0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
																				0xf9,0xfa]);

		private var std_dc_chrominance_nrcodes:Vector.<int> = Vector.<int>([0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]);
		private var std_dc_chrominance_values:Vector.<int> = Vector.<int>([0,1,2,3,4,5,6,7,8,9,10,11]);
		private var std_ac_chrominance_nrcodes:Vector.<int> = Vector.<int>([0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]);
		private var std_ac_chrominance_values:Vector.<int> = Vector.<int>([0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
																				0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
																				0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
																				0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
																				0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
																				0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
																				0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
																				0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
																				0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
																				0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
																				0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
																				0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
																				0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
																				0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
																				0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
																				0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
																				0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
																				0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
																				0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
																				0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
																				0xf9,0xfa
																			]);

		private function initHuffmanTbl():void
		{
			YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);
			UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);
			YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);
			UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);
		}

		private var bitcode:Vector.<BitString> = new Vector.<BitString>(65535, true);
		private var category:Vector.<int> = new Vector.<int>(65535, true);

		private function initCategoryNumber():void
		{
			var nrlower:int = 1;
			var nrupper:int = 2;
			var bitString:BitString;
			const I15:int = 15;
			var pos:int;
			for (var cat:int=1; cat<=I15; ++cat)
			{
				//Positive numbers
				for (var nr:int=nrlower; nr<nrupper; ++nr)
				{
					pos = int(32767+nr);
					category[pos] = cat;
					bitcode[pos] = bitString = new BitString();
					bitString.len = cat;
					bitString.val = nr;
				}
				//Negative numbers
				for (var nrneg:int=-(nrupper-1); nrneg<=-nrlower; ++nrneg)
				{
					pos = int(32767+nrneg);
					category[pos] = cat;
					bitcode[pos] = bitString = new BitString();
					bitString.len = cat;
					bitString.val = nrupper-1+nrneg;
				}
				nrlower <<= 1;
				nrupper <<= 1;
			}
		}

		// IO functions

		private var byteout:ByteArray;
		private var bytenew:int = 0;
		private var bytepos:int = 7;

		private function writeBits(bs:BitString):void
		{
			var value:int = bs.val;
			var posval:int = bs.len-1;
			while ( posval >= 0 )
			{
				if (value & uint(1 << posval) )
					bytenew |= uint(1 << bytepos);
				posval--;
				bytepos--;
				if (bytepos < 0)
				{
					if (bytenew == 0xFF)
					{
						byteout.writeByte(0xFF);
						byteout.writeByte(0);
					}
					else byteout.writeByte(bytenew);
					bytepos=7;
					bytenew=0;
				}
			}
		}

		// DCT & quantization core

		private function fDCTQuant(data:Vector.<Number>, fdtbl:Vector.<Number>):Vector.<int>
		{
			/* Pass 1: process rows. */
			var dataOff:int=0;
			var d0:Number, d1:Number, d2:Number, d3:Number, d4:Number, d5:Number, d6:Number, d7:Number;
			var i:int;
			const I8:int = 8;
			const I64:int = 64;
			for (i=0; i<I8; ++i)
			{
                d0 = data[int(dataOff)];
				d1 = data[int(dataOff+1)];
				d2 = data[int(dataOff+2)];
				d3 = data[int(dataOff+3)];
				d4 = data[int(dataOff+4)];
				d5 = data[int(dataOff+5)];
				d6 = data[int(dataOff+6)];
				d7 = data[int(dataOff+7)];

				var tmp0:Number = d0 + d7;
				var tmp7:Number = d0 - d7;
				var tmp1:Number = d1 + d6;
				var tmp6:Number = d1 - d6;
				var tmp2:Number = d2 + d5;
				var tmp5:Number = d2 - d5;
				var tmp3:Number = d3 + d4;
				var tmp4:Number = d3 - d4;

				/* Even part */
				var tmp10:Number = tmp0 + tmp3;	/* phase 2 */
				var tmp13:Number = tmp0 - tmp3;
				var tmp11:Number = tmp1 + tmp2;
				var tmp12:Number = tmp1 - tmp2;

				data[int(dataOff)] = tmp10 + tmp11; /* phase 3 */
				data[int(dataOff+4)] = tmp10 - tmp11;

				var z1:Number = (tmp12 + tmp13) * 0.707106781; /* c4 */
				data[int(dataOff+2)] = tmp13 + z1; /* phase 5 */
				data[int(dataOff+6)] = tmp13 - z1;

				/* Odd part */
				tmp10 = tmp4 + tmp5; /* phase 2 */
				tmp11 = tmp5 + tmp6;
				tmp12 = tmp6 + tmp7;

				/* The rotator is modified from fig 4-8 to avoid extra negations. */
				var z5:Number = (tmp10 - tmp12) * 0.382683433; /* c6 */
				var z2:Number = 0.541196100 * tmp10 + z5; /* c2-c6 */
				var z4:Number = 1.306562965 * tmp12 + z5; /* c2+c6 */
				var z3:Number = tmp11 * 0.707106781; /* c4 */

				var z11:Number = tmp7 + z3;	/* phase 5 */
				var z13:Number = tmp7 - z3;

				data[int(dataOff+5)] = z13 + z2;	/* phase 6 */
				data[int(dataOff+3)] = z13 - z2;
				data[int(dataOff+1)] = z11 + z4;
				data[int(dataOff+7)] = z11 - z4;

				dataOff += 8; /* advance pointer to next row */
			}

			/* Pass 2: process columns. */
			dataOff = 0;
			for (i=0; i<I8; ++i)
			{
				d0 = data[int(dataOff)];
				d1 = data[int(dataOff + 8)];
				d2 = data[int(dataOff + 16)];
				d3 = data[int(dataOff + 24)];
		        d4 = data[int(dataOff + 32)];
				d5 = data[int(dataOff + 40)];
				d6 = data[int(dataOff + 48)];
				d7 = data[int(dataOff + 56)];

				var tmp0p2:Number = d0 + d7;
				var tmp7p2:Number = d0 - d7;
				var tmp1p2:Number = d1 + d6;
				var tmp6p2:Number = d1 - d6;
				var tmp2p2:Number = d2 + d5;
				var tmp5p2:Number = d2 - d5;
				var tmp3p2:Number = d3 + d4;
				var tmp4p2:Number = d3 - d4;

				/* Even part */
				var tmp10p2:Number = tmp0p2 + tmp3p2;	/* phase 2 */
				var tmp13p2:Number = tmp0p2 - tmp3p2;
				var tmp11p2:Number = tmp1p2 + tmp2p2;
				var tmp12p2:Number = tmp1p2 - tmp2p2;

				data[int(dataOff)] = tmp10p2 + tmp11p2; /* phase 3 */
				data[int(dataOff+32)] = tmp10p2 - tmp11p2;

				var z1p2:Number = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
				data[int(dataOff+16)] = tmp13p2 + z1p2; /* phase 5 */
				data[int(dataOff+48)] = tmp13p2 - z1p2;

				/* Odd part */
				tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
				tmp11p2 = tmp5p2 + tmp6p2;
				tmp12p2 = tmp6p2 + tmp7p2;

				/* The rotator is modified from fig 4-8 to avoid extra negations. */
				var z5p2:Number = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
				var z2p2:Number = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
				var z4p2:Number = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
				var z3p2:Number= tmp11p2 * 0.707106781; /* c4 */

				var z11p2:Number = tmp7p2 + z3p2;	/* phase 5 */
				var z13p2:Number = tmp7p2 - z3p2;

				data[int(dataOff+40)] = z13p2 + z2p2; /* phase 6 */
				data[int(dataOff+24)] = z13p2 - z2p2;
				data[int(dataOff+ 8)] = z11p2 + z4p2;
				data[int(dataOff+56)] = z11p2 - z4p2;

				dataOff++; /* advance pointer to next column */
			}

			// Quantize/descale the coefficients
			var fDCTQuant:Number;
			for (i=0; i<I64; ++i)
			{
				// Apply the quantization and scaling factor & Round to nearest integer
				fDCTQuant = data[int(i)]*fdtbl[int(i)];
				outputfDCTQuant[int(i)] = (fDCTQuant > 0.0) ? int(fDCTQuant + 0.5) : int(fDCTQuant - 0.5);
			}
			return outputfDCTQuant;
		}

		// Chunk writing
		private function writeAPP0():void
		{
			byteout.writeShort(0xFFE0); // marker
			byteout.writeShort(16); // length
			byteout.writeByte(0x4A); // J
			byteout.writeByte(0x46); // F
			byteout.writeByte(0x49); // I
			byteout.writeByte(0x46); // F
			byteout.writeByte(0); // = "JFIF",'\0'
			byteout.writeByte(1); // versionhi
			byteout.writeByte(1); // versionlo
			byteout.writeByte(0); // xyunits
			byteout.writeShort(1); // xdensity
			byteout.writeShort(1); // ydensity
			byteout.writeByte(0); // thumbnwidth
			byteout.writeByte(0); // thumbnheight
		}

		private function writeSOF0(width:int, height:int):void
		{
			byteout.writeShort(0xFFC0); // marker
			byteout.writeShort(17);   // length, truecolor YUV JPG
			byteout.writeByte(8);    // precision
			byteout.writeShort(height);
			byteout.writeShort(width);
			byteout.writeByte(3);    // nrofcomponents
			byteout.writeByte(1);    // IdY
			byteout.writeByte(0x11); // HVY
			byteout.writeByte(0);    // QTY
			byteout.writeByte(2);    // IdU
			byteout.writeByte(0x11); // HVU
			byteout.writeByte(1);    // QTU
			byteout.writeByte(3);    // IdV
			byteout.writeByte(0x11); // HVV
			byteout.writeByte(1);    // QTV
		}

		private function writeDQT():void
		{
			byteout.writeShort(0xFFDB); // marker
			byteout.writeShort(132);	   // length
			byteout.writeByte(0);

			var i:int;
			const I64:int = 64;
			for (i=0; i<I64; ++i)
				byteout.writeByte(YTable[i]);

			byteout.writeByte(1);

			for (i=0; i<I64; ++i)
				byteout.writeByte(UVTable[i]);
		}

		private function writeDHT():void
		{
			byteout.writeShort(0xFFC4); // marker
			byteout.writeShort(0x01A2); // length

			byteout.writeByte(0); // HTYDCinfo
			var i:int;
			const I11:int = 11;
			const I16:int = 16;
			const I161:int = 161;
			for (i=0; i<I16; ++i)
				byteout.writeByte(std_dc_luminance_nrcodes[int(i+1)]);

			for (i=0; i<=I11; ++i)
				byteout.writeByte(std_dc_luminance_values[int(i)]);

			byteout.writeByte(0x10); // HTYACinfo

			for (i=0; i<I16; ++i)
				byteout.writeByte(std_ac_luminance_nrcodes[int(i+1)]);

			for (i=0; i<=I161; ++i)
				byteout.writeByte(std_ac_luminance_values[int(i)]);

			byteout.writeByte(1); // HTUDCinfo

			for (i=0; i<I16; ++i)
				byteout.writeByte(std_dc_chrominance_nrcodes[int(i+1)]);

			for (i=0; i<=I11; ++i)
				byteout.writeByte(std_dc_chrominance_values[int(i)]);

			byteout.writeByte(0x11); // HTUACinfo

			for (i=0; i<I16; ++i)
				byteout.writeByte(std_ac_chrominance_nrcodes[int(i+1)]);

			for (i=0; i<=I161; ++i)
				byteout.writeByte(std_ac_chrominance_values[int(i)]);
		}

		private function writeSOS():void
		{
			byteout.writeShort(0xFFDA); // marker
			byteout.writeShort(12); // length
			byteout.writeByte(3); // nrofcomponents
			byteout.writeByte(1); // IdY
			byteout.writeByte(0); // HTY
			byteout.writeByte(2); // IdU
			byteout.writeByte(0x11); // HTU
			byteout.writeByte(3); // IdV
			byteout.writeByte(0x11); // HTV
			byteout.writeByte(0); // Ss
			byteout.writeByte(0x3f); // Se
			byteout.writeByte(0); // Bf
		}

		// Core processing
		internal var DU:Vector.<int> = new Vector.<int>(64, true);

		private function processDU(CDU:Vector.<Number>, fdtbl:Vector.<Number>, DC:Number, HTDC:Vector.<BitString>, HTAC:Vector.<BitString>):Number
		{
			var EOB:BitString = HTAC[0x00];
			var M16zeroes:BitString = HTAC[0xF0];
			var pos:int;
			const I16:int = 16;
			const I63:int = 63;
			const I64:int = 64;
			var DU_DCT:Vector.<int> = fDCTQuant(CDU, fdtbl);
			//ZigZag reorder
			for (var j:int=0;j<I64;++j) {
				DU[ZigZag[j]]=DU_DCT[j];
			}
			var Diff:int = DU[0] - DC; DC = DU[0];
			//Encode DC
			if (Diff==0) {
				writeBits(HTDC[0]); // Diff might be 0
			} else {
				pos = int(32767+Diff);
				writeBits(HTDC[category[pos]]);
				writeBits(bitcode[pos]);
			}
			//Encode ACs
			const end0pos:int = 63;
			for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};
			//end0pos = first element in reverse order !=0
			if ( end0pos == 0) {
				writeBits(EOB);
				return DC;
			}
			var i:int = 1;
			var lng:int;
			while ( i <= end0pos ) {
				var startpos:int = i;
				for (; (DU[i]==0) && (i<=end0pos); ++i) {}
				var nrzeroes:int = i-startpos;
				if ( nrzeroes >= I16 ) {
					lng = nrzeroes>>4;
					for (var nrmarker:int=1; nrmarker <= lng; ++nrmarker)
						writeBits(M16zeroes);
					nrzeroes = int(nrzeroes&0xF);
				}
				pos = int(32767+DU[i]);
				writeBits(HTAC[int((nrzeroes<<4)+category[pos])]);
				writeBits(bitcode[pos]);
				i++;
			}
			if ( end0pos != I63 ) {
				writeBits(EOB);
			}
			return DC;
		}

		private var YDU:Vector.<Number> = new Vector.<Number>(64, true);
		private var UDU:Vector.<Number> = new Vector.<Number>(64, true);
		private var VDU:Vector.<Number> = new Vector.<Number>(64, true);

		private function RGB2YUV(img:BitmapData, xpos:int, ypos:int):void
		{
			var pos:int=0;
			const I8:int = 8;
			for (var y:int=0; y<I8; ++y) {
				for (var x:int=0; x<I8; ++x) {
					var P:uint = img.getPixel32(xpos+x,ypos+y);
					var R:int = (P>>16)&0xFF;
					var G:int = (P>> 8)&0xFF;
					var B:int = (P    )&0xFF;
                    YDU[int(pos)]=((( 0.29900)*R+( 0.58700)*G+( 0.11400)*B))-0x80;
					UDU[int(pos)]=(((-0.16874)*R+(-0.33126)*G+( 0.50000)*B));
					VDU[int(pos)]=((( 0.50000)*R+(-0.41869)*G+(-0.08131)*B));
					++pos;
				}
			}
		}

		public function JPEGEncoder(quality:int=50)
		{
			if (quality <= 0)
				quality = 1;

			if (quality > 100)
				quality = 100;

			sf = quality < 50 ? int(5000 / quality) : int(200 - (quality<<1));
			init();
		}

		private function init():void
		{
			ZigZag.fixed = true;
			aasf.fixed = true;
			YQT.fixed = true;
			UVQT.fixed = true;
			std_ac_chrominance_nrcodes.fixed = true;
			std_ac_chrominance_values.fixed = true;
			std_ac_luminance_nrcodes.fixed = true;
			std_ac_luminance_values.fixed = true;
			std_dc_chrominance_nrcodes.fixed = true;
			std_dc_chrominance_values.fixed = true;
			std_dc_luminance_nrcodes.fixed = true;
			std_dc_luminance_values.fixed = true;
			// Create tables
			initHuffmanTbl();
			initCategoryNumber();
			initQuantTables(sf);
		}

		public function encode(image:BitmapData):ByteArray
		{
			// Initialize bit writer
			byteout = new ByteArray();

			bytenew=0;
			bytepos=7;

			// Add JPEG headers
			byteout.writeShort(0xFFD8); // SOI
			writeAPP0();
			writeDQT();
			writeSOF0(image.width,image.height);
			writeDHT();
			writeSOS();

			// Encode 8x8 macroblocks
			var DCY:Number=0;
			var DCU:Number=0;
			var DCV:Number=0;
			bytenew=0;
			bytepos=7;

			var width:int = image.width;
			var height:int = image.height;

			for (var ypos:int=0; ypos<height; ypos+=8)
			{
				for (var xpos:int=0; xpos<width; xpos+=8)
				{
					RGB2YUV(image, xpos, ypos);
					DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
					DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
					DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
				}
			}

			// Do the bit alignment of the EOI marker
			if ( bytepos >= 0 )
			{
				var fillbits:BitString = new BitString();
				fillbits.len = bytepos+1;
				fillbits.val = (1<<(bytepos+1))-1;
				writeBits(fillbits);
			}
			byteout.writeShort(0xFFD9); //EOI
			return byteout;
		}
	}
}

final class BitString
{
	public var len:int = 0;
	public var val:int = 0;
}
分类: ActionSprite3.0 标签:

图片与二进制或字符串相互转换

2011年11月22日 没有评论

一:图片转字符

过程DisplayObject>bitmapData>ByteArray>Base64>String
把图片转化为二进制或字符,使用AS3自带的JPEGEncoder和PNGEncoder,其中JPEG格式就算使用100%的品质在还原时还是失真得比较严重.

var bitmapData:BitmapData = new BitmapData(image.width,image.height);

bitmapData.draw(image);//转化为BitmapData数据

var encoder:PNGEncoder = new PNGEncoder();//也可以使用JPEG格式 new JPEGEncoder(100)

var bytes:ByteArray=encoder.encode(bitmapData);//转化为二进制数据

var Base64:Base64Encoder=new Base64Encoder;//将字符串或 ByteArray 编码为 Base64 编码的字符串。

var imageStr:String=Base64.toString();//输出为字符串

 

二:字符转图片

过程:String>Base64>ByteArray>Bitmap>DisplayObject
还原时,先使用Base64Decoder转为Base64编码的ByteArray,再用Loader对象来读取为Bitmap,完成还原过程.

var spr:Sprite=new Sprite;

var Base64:Base64Decoder=new Base64Decoder;

Base64.decode(imageStr);//读取字符串

var bytes:ByteArray=Base64.toByteArray();//转化为ByteArray数据

var load:Loader=new Loader();

load.loadBytes(bytes);//读取ByteArray

load.contentLoaderInfo.addEventListener(Event.COMPLETE, complete);

function complete(event:Event):void {

var bitMap:Bitmap=event.target.content as Bitmap;//读取Bitmap

  spr.addChild(bitMap);

}

分类: YangDan 标签:

Flash2D游戏引擎简介

2011年11月21日 没有评论

Adobe Flash自诞生之日就与游戏结下不解之缘。时至今日,无数游戏通过Flash制作并传播,Flash游戏已经从最初的浏览器小游戏,扩展到大型的客户端单机游戏、多人即时互动网页游戏、移动平台游戏等等领域。
从本篇开始,将为大家介绍Flash游戏相关的引擎、框架及实用API,方便开发者快速的选择适合自己项目的工具,创建精彩的Flash游戏。

Flixel 位图引擎


Flixel是我非常喜欢的开源位图引擎。作者运用ActionScript的Bitmap创建了这个全位图引擎,所谓全位图,就是游戏场景中所有元件最终均绘制在一个位图对象上,在游戏过程中每帧进行重绘。也正因为如此,此引擎非常擅长处理同屏同时出现大量的游戏元件,其高效的渲染会让你激动不已。当你需要创建2D卷轴游戏或者场景中需要大量运动元素的游戏,Flixel引擎是你的首选。
Flixel还具有一些不错的特性:
• 采用QuadTree的对象链,高效的碰撞检测
• 位图动画Sprite
• 通过文本及图片创建Tiles地图
• 简单易用的粒子系统
• 高效的滚屏
• 自定义的鼠标光标
• 方便的debug显示终端
此外在引擎开发者论坛中,还有用户将此引擎移植到Android平台上。
官方网址: http://www.flixel.org/
运行环境:ActionScript 3,Flash Player 9 及以上版本
开发环境:Flex(Flash)Builder,FlashDevelop及其他ActionScript开发环境。
典型案例:
• Canabalt ( http://adamatomic.com/canabalt/ )
这个游戏在作者的网站上每月会消耗2.5T的流量,可见流行的程度。游戏中高速流畅的滚屏会让你惊叹Flash的效率。此游戏还移植到iOS上,并在App Store中销量不菲。
• Omega Crisis ( http://www.kongregate.com/games/lucidrine/omega-crisis ) 这个塔防类游戏,画面、游戏性与操作性都相当不错。
更多采用此引擎的游戏展示: http://flixel.org/games/

Pushbutton engine


PushButton是一个开源的Flash游戏引擎,其实它更像一个游戏框架。引擎由ActionScript 3开发,需要Flash Player 9及以上的运行环境。官方还提供与游戏构建相关的组件,例如Box2D,Rendering2D等。同时在引擎中包含了资源管理、日志、调试监控、线程及时间管理等功能。
开发者可以运用这套框架按模块方式集成各种渲染模块、物理模块、网络通信模块来创建游戏。
官方网址: http://pushbuttonengine.com/
运行环境:ActionScript 3,Flash Player 9 及以上版本
开发环境:Flash CS4及以上版本,Flex(Flash)Builder,FlashDevelop及其他ActionScript开发环境。
典型案例:
∗ Social City ( http://pushbuttonlabs.com/games/social-city/ )
这个在Facebook上月活跃用户超过一千万的游戏,采用了PushButton引擎。
∗ The Incredible Machine Mega Pack (http://www.gog.com/en/gamecard/the_incredible_machine_mega_pack)
不可思议的机器系列想必大家不陌生,这个版本的近400兆大小的单机游戏也出自该引擎。

FlashPunk 引擎


FlashPunk同样是个针对位图的开源引擎。它具有清晰的框架以及创建游戏需要的动画、碰撞等类库,让开发者更专注与游戏的设计与测试中。
其主要特性包括:
• 相对独立与固定的帧频时间步长控制
• 像素、矩形区及网格的碰撞检测
• 高效的运动tweening
• Z-sorted的渲染列表,方便深度排序
• 高效的粒子系统
官方网址: http://flashpunk.net/
运行环境:ActionScript 3,Flash Player 9 及以上版本
开发环境:Flex(Flash)Builder,FlashDevelop及其他ActionScript开发环境。
典型案例:
• Tiny Hawk ( http://www.kongregate.com/games/pekuja/tiny-hawk
类似超级玛丽,不过这次你脚下踩着的是滑板,一共32关。
• Mr. Fat Snake (http://www.kongregate.com/games/ReviveGames/mr-fat-snake?acomplete=mr+fat+snake )
贪吃蛇的横轴飞速版。:)
更多采用此引擎的游戏展示: http://flashpunk.net/?p=games
还有大量的开发者运用Flash创建2.5D的游戏,所谓2.5D,我们也称之为Isometric,也就是游戏视角采取倾斜视角(如斜45度角等),以平面的方式展现固定视角的3D效果。目前很多网页游戏均采用2.5D的方式。
需要注意的是,前面为大家介绍的位图2D引擎同样可以用来开发2.5D游戏。
接下来为大家介绍几个专注于2.5D开发的引擎。运用这些引擎,你可以把一些烦人的2.5D相关的坐标转换交给引擎处理,专注在你的游戏逻辑及设计上。

As3isolib - 开源的2.5D库


As3isolib是一个基于ActionScript 3的 Isometric库,开发者运用它可以方便的开发2.5D的游戏或应用。其主要特性包括:
• 简易的2.5D场景创建方式
• 方便的于各种缓动(tween)引擎交互
• 增强的2.5D元件深度排序
• 场景显示渲染的性能优化
官方网址: http://code.google.com/p/as3isolib/
运行环境:ActionScript 3,Flash Player 9 及以上版本
开发环境:Flex(Flash)Builder,FlashDevelop及其他ActionScript开发环境。
典型案例:
• Zex Lex Duel ( http://apps.facebook.com/cp_zexlexduel/
Facebook上的一个机器对战小游戏 。
• Down Town (http://apps.facebook.com/downtowngame/ )
Facebook上的虚拟城市交友。
另外,还有开发者将这个2.5D的库制作成 PushButton引擎的一个组件。http://code.google.com/p/pushbutton-ooo-extras/

OpenSpace - 多人虚拟世界引擎


OpenSpace是一个非常不错的引擎,用户可以非常快速方便的创建2.5D游戏。配合该公司的另外一款通信服务器SmartFoxServer,可以搭建多人实时交互的虚拟场景。
其特点包括:
• 完善的地图编辑方式
• 可缩放的场景
• 自定义地图滚屏方式
• 自定义的游戏角色
• 地图自动寻径
官方网址: http://www.openspace-engine.com/
运行环境:ActionScript 3,Flash Player 9 及以上版本
开发环境:Flex(Flash)Builder,FlashDevelop及其他ActionScript开发环境。
典型案例:
• The Settlers – My City ( http://apps.facebook.com/tsmycity
殖民者的网页版,你可以创建属于自己的殖民国 。
• Petpet Park (http://www.petpetpark.com/)
很可爱的宠物公园虚拟社区。
更多的案例展示: http://www.openspace-engine.com/showcase

TheoWorlds – 快速开发2.5D游戏的商业套件


TheoWorlds 除了包含Iso引擎之外,还包含聊天、地图编辑器等组件,可以帮助开发者快速的开发2.5D的虚拟世界。
主要特性有:
• 8方向的运动角色
• 自定义角色形象
• 自定义角色动作
• 快速寻径及自动滚屏
• 与SmartFox Server及ElectroServer等第三方即时通信服务器通信
• 聊天历史、表情图标等
官方网址: http://www.theoworlds.com/
运行环境: Flash Player 8 及以上版本
开发环境:Flex(Flash)Builder,FlashDevelop及其他ActionScript开发环境。
相关演示:
• 场景演示 ( http://www.theoworlds.com/labs/09/
• 地图编辑器演示 (http://www.theoworlds.com/mapeditor/)

以上为大家介绍了一些流行的Flash 2D游戏引擎,希望大家能在开发中获益。后期将为大家介绍Flash游戏相关的物理引擎、人工智能、UI、音效等第三方API,请关注。

分类: YangDan 标签:

教程:深入理解Flash的沙箱 – Application Domains

2011年11月12日 没有评论

应用程序域

Application Domains 应用程序域

和安全域一样,不同安全沙箱下的SWF有着自己独立的类定义。这种在安全域下面进行划分和管理类定义(函数、接口和命名空间的定义也类似)的子域就是应用程序域。应用程序域只存在于安全域内,并且只能属于唯一的一个安全域。但是安全域可以包含多个应用程序域。

Application Domains in Security Domains
安全域内的应用程序域

虽然安全域沙箱用于保护数据安全,应用程序沙箱域用于划分定义。但是他们都用于解决定义的冲突和判断代码的继承关系。

安全域彼此之间是相互独立的,相比之下,应用程序域之间的关系则较为复杂。应用程序域通过类似于Flash中的显示列表那样的层级关系链接在一起。应用程序域可以包含任意的子域,而子域只能有一个父域。子域继承了来自父域中的定义,就像是显示列表中父对象的位置和缩放属性被子对象继承一样。

应用程序域的根节点是一个系统域,这个域包含了Flash Player API的原生定义(Array,XML,flash.display.Sprite等等)。系统域与安全域是一一对应的关系,当安全域初始化的时候这个唯一的系统域也被建立。

当一个Flash Player的实例初始化的时候,SWF文件加到它对应的安全域内。同时也创建了一个包含了这个文件中所有编译过的ActionScript定义的应用程序域。这个应用程序域就成为安全域下的系统域的第一个子域。Flash Player API的原生定义就通过这种继承关系对所有子域开放。

Child Application Domain
在系统域下新建了一个SWF应用程序域

我们将在应用程序域的继承章节中进行更多关于继承的讨论。

Application Domain Placement 应用程序域的位置

第一个实例化Flash Player的SWF文件所包含的定义总是被加载为系统域的直接子域。父SWF去加载子SWF的时候,可以控制子SWF内的定义所要放置的位置。可选的位置共有以下4种:

  1. 父SWF的应用程序域的新建子域 (默认方式)
  2. 子SWF 与父SWF的应用程序域合并
  3. 作为父域的系统域下的新建子域
  4. 在其他安全域下的系统域的新建子域

前三种情况都是把子SWF加载到父域所处的安全域下,只有第四种是唯一一种把SWF加载到其他安全域下的方法。

Application Domain Placement
加载子SWF时放置应用程序域的4种选择

还有一种没提到的方式,是你为某个已加载的SWF创建了应用程序域,再把其他子SWF中的定义合并到(或者继承)这个域的情况。这种特殊的放置方式需要复杂的应用程序域层级管理,你需要掌握ApplicationDomain.parentDomain的用法,在此提醒读者小心:这种方法通常在不同的安全沙箱下(本地或者网络)会有不同的行为。这种方式很不常见,所以在此不进行更深的探讨。

LoaderContext对象的applicationDomain属性定义了放置应用程序域的方式。你可以用ApplicationDomain.currentDomain(类似于安全域的SecurityDomain.currentDomain)或者用new关键字新建一个ApplicationDomain实例来作为参数。在ApplicationDomain的构造函数里可以为新建的域指定父域,如果这个参数没有指定,则表示将该域直接作为系统域的子域。

// 将定义放置到父SWF所在的应用程序域(当前应用程序域)
var current:ApplicationDomain = ApplicationDomain.currentDomain;

// 将定义放置到父SWF所在的应用程序域的的子域
var currentChild:ApplicationDomain = new ApplicationDomain(current);<em>?</em>

// 将定义放置到父SWF所在的应用程序域的系统域
var systemChild:ApplicationDomain = new ApplicationDomain();

下面的代码演示了使用LoaderContext对象传递ApplicationDomain实例给Loader.load方法,把一个子SWF加载到父SWF所处的应用程序域的子域下的例子。这种方式也是默认的加载行为。

var context:LoaderContext = new LoaderContext();
// 把子应用程序域作为当前应用程序域的子域
var current:ApplicationDomain = ApplicationDomain.currentDomain;
context.applicationDomain = new ApplicationDomain(current);

var loader:Loader = new Loader();
var url:String = "child.swf";
loader.load(new URLRequest(url), context);

ApplicationDomain实例在内部包含了不对ActionScript开放的层级位置信息。每个ApplicationDomain实例都是一个唯一引用,彼此之间不能相互比较。

var current1:ApplicationDomain = ApplicationDomain.currentDomain;
var current2:ApplicationDomain = ApplicationDomain.currentDomain;
trace(current1 == current2); // false

你也不能通过parentDomain属性得到系统域的引用,只有通过new ApplicationDomain()才可以。

Application Domain Inheritance 应用程序域的继承

定义的继承和类继承有点类似,两者都是子级可以访问父级的定义,而反之则不行。

区别在于,应用程序域的继承不允许子级覆盖父级的定义。如果子域中包含有与父域一样的定义(指的是完全限定名称一致,包括包路径)。那么父域中的定义会取代掉子域。

Child Application Domain Inheritance
子域中的定义被父域覆盖

这是因为你不能改变一个已经存在的实例的类定义。如果新的定义在被加载进来以前就已经用旧的定义生成过实例,那么这个实例原先的类定义和新的类定义之间就会产生冲突。所以Flash Player保护原先的类定义不被重写来避免冲突。

这也意味着开发者不可能覆盖ActionScript API的原生定义。因为SWF所处的应用程序域肯定是系统域的子域,而系统域包含了所有原生的定义。所以就算子SWF中包含了同名的定义,也会被系统域中的定义所覆盖。

当向一个已经存在的应用程序域合并定义时,上述规则同样适用。只有与原先的域里的定义无冲突的定义才会被合并。

Adding Definitions in Application Domain
新增到应用程序域里的定义不会覆盖现有的定义

这种情况下,那些发生冲突但却被覆盖的定义就完全获取不到了。但是如果是继承的方式,就算子域中的那些冲突定义被父域中的定义覆盖掉,还是可以通过getDefinition方法从子域中提取出来,关于这点将在动态获取定义章节中讨论。

加载到应用程序域中的定义在应用程序域的生命期里一直存在。在SWF卸载后,用于保存这个SWF内的定义的应用程序域也会从内存中卸载。但如果该SWF的定义是放在其他某个已经存在的应用程序域内的话,那么这些定义将一直存在于内存中,除非目标应用程序域所关联的那个SWF被卸载。如果一直把新的定义加载到一个已经存在的域内,比如为第一个被加载的SWF创建的域,那么定义所占用的内存就会一直增加。如果是一个不停加载子SWF的滚动广告应用的话,持续增加定义到相同的应用程序域内引起的内存增长问题显然不是预期的结果。

而且,用这种方式加载的定义不会随着子SWF的卸载而卸载,而是在第二次加载相同的子SWF的时候重用第一次加载时创建的定义。这通常不会有什么问题,但是这意味着再次加载相同SWF的时候静态类的状态不会重置。静态变量有可能是上次使用过的值,一定会和第一次加载进来的时候保持一致。

所以,不同的情况需要不同的解决方法。

Child Domains: Definition Versioning 子域:定义的版本管理

定义的继承机制使得子域可以很方便的共享父域内的定义。也由于子域中的重名定义会被父域所覆盖的原因,父应用程序域拥有控制在子域中使用哪个版本的定义的权力。

Child Application Domain Inherits Parent Definitions
子应用程序域继承自父域

考虑以下情形:一个基于SWF的网站使用不同的SWF文件来代表不同的页面。主SWF负责加载这些子页面。每个页面SWF基于一个相同的类库开发,具有相似的行为。比如都有一个PageTitle类来表示页面的标题文本。

假如在相同域下有另一个SWF也用到这些相同的子页面,但是需要把子页面的标题文本变为不可选(假设原先的属性是可选择)。要实现这个例子里的目的,在PageTitle类中,我们需要把TextField的selectable属性改为false。但这样改动的问题是会影响原先的SWF文件保持其本来的行为。

为了解决这个问题,我们可以把每个子页面都复制一份并重新编译。但这么做的话会占用更多的空间和网站流量。更好的办法是只编译第二个主SWF,把更新过的PageTitle类定义一起编译进去。然后在子页面在加载到子应用程序域的时候,这个类的定义就会被父域里的定义给覆盖。

原先所有子页面用的PageTitle类如下:

package {
	import flash.display.Sprite;
	import flash.text.TextField;

	public class PageTitle extends Sprite {

		private var title:TextField;

		public function PageTitle(titleText:String){
			title = new TextField();
			title.text = titleText;
			addChild(title);
		}
	}
}

编译到第二个主文件里的更新版本的PageTitle类:

package {
	import flash.display.Sprite;
	import flash.text.TextField;

	public class PageTitle extends Sprite {

		private var title:TextField;

		public function PageTitle(titleText:String){
			title = new TextField();
			title.text = titleText;
			<strong>title.selectable = false;</strong> // changed
			addChild(title);
		}
	}
}

把更新过的PageTitle类定义编译到新的主文件里面,并加载所有子页面到它们自己的子应用程序域中。

PageTitle; // 虽然没有直接用到PageTitle,但我们可以包含一个引用,让它被一同编译进来 

// 加载子页面到它们自己的子应用程序域中
// 加载的SWF将会用父域里的PageTitle定义取代掉它们自带的
function addChildPage(url:String):void {
	var context:LoaderContext = new LoaderContext();
	var current:ApplicationDomain = ApplicationDomain.currentDomain;
	context.applicationDomain = new ApplicationDomain(current);

	var loader:Loader = new Loader();
	addChild(loader);
	loader.load(new URLRequest(url), context);
}

这种方法可以在不用重新编译子内容的前提下改变其中的类行为,这都是由于父应用程序域中的定义会覆盖子域中的定义的原因。

注意在上面的例子也可以省略LoaderContext的使用,效果是一样的。

即便子SWF无需用作多重使用目的,更新主文件中的定义也比更新所有子文件的更加简单。实际上,子文件中甚至可以完全不用包含这些定义,只依赖于主文件提供。这就是我们将在相同的域:运行时共享库章节里将展开讨论的。

Separate Domains: Preventing Conflicts 域分离:避免冲突

某些情形下,你可能不希望加载的子SWF内容被父应用程序域里的定义继承关系所影响。因为有可能你甚至不知道父域中存在哪些定义。不论哪种情况,最好都要避免主SWF和子SWF中的定义共享。在这种情况下,应该把子SWF的定义放到新的系统域的子域下。

Child SWF Domain as Child of System Domain
系统域下的不同子应用程序域

由于父SWF和子SWF的定义之间没有继承关系,所以这时候即使存在相同的定义也不会引起冲突,因为二者属于不同的沙箱。

举个例子:比如你有个培训程序,通过加载外部SWF来代表不同的培训模块。这个程序已经有些年头了,许多开发者开发了成百上千个培训模块。这些模块,甚至培训主程序自身都是基于不同版本的基础代码库进行开发。所以主程序要保证自己使用的基础代码库不会对其他模块造成不兼容的情况。这就必须把这些培训模块加载到他们独立的系统域下的子域,而不是把他们加载到主应用程序域的子域下面。

trainingapplication.swf:

var moduleLoader:Loader = new Loader();
addChild(moduleLoader);

// 把模块加载到系统域的子域下,与当前的应用程序域区分开
function loadModule(url:String):void {
	var context:LoaderContext = new LoaderContext();
	context.applicationDomain = new ApplicationDomain();

	moduleLoader.load(new URLRequest(url), context);
}

不足的是,这种定义的划分方式还不是完全隔离的。由于在同一个安全域下的内容都处于一个相同的系统域下,任何对系统域内定义的修改都将影响同一个安全域下的所有应用程序域。即使是将子SWF加载到一个单独的系统域的子域下,父SWF对系统域的更改还是会对其造成影响。

我们可以通过改动XML.prettyIndent属性来验证这一点:不管处于应用程序域层级的哪个SWF对系统域里的定义作出改变,都会影响到相同安全域下的所有文件。

parent.swf:

trace(XML.prettyIndent); // 2
XML.prettyIndent = 5;
trace(XML.prettyIndent); // 5

var loader:Loader = new Loader();

var context:LoaderContext = new LoaderContext();
// 新建一个独立的应用程序域
context.applicationDomain = new ApplicationDomain();

var url:String = "child.swf";
loader.load(new URLRequest(url), context);

child.swf:

trace(XML.prettyIndent); // 5

所以最佳实践是对定义做的改动应该在使用后及时还原,这样可以避免对其他文件的影响。

var originalPrettyIndent:int = XML.prettyIndent;
XML.prettyIndent = 5;
trace(myXML.toXMLString());
XML.prettyIndent = originalPrettyIndent;

同样的,你也必须留心类似这样的值有可能在你的程序之外被人所改动。

Same Domain: Runtime Shared Libraries 相同的域:运行时共享库

把新增的定义增加到现有的应用程序域下可能是应用程序域最大的用处。因为继承只能把父域内的定义对子域共享,而合并定义到相同的应用程序域内则可以对所有使用这个域的SWF共享,包括父级和子级。

Parent Domain Adds Child Definitions
父应用程序域包括了子SWF的定义

运行时共享库(RSLs)正是运用了这种机制。RSLs是可以在运行时被加载的独立的代码库。通过RSLs,其他SWF可以共用其中的代码而不需要编译到自身,从而排除了冗余,减小了文件量,也让代码更容易维护。我们在主应用程序域中加载RSL,从而可以在整个程序中共享定义。

使用RSLs之前需要做些准备工作。首先,ActionScript编译器需要在发布SWF文件的时候知道哪些定义不需要被编译。

原生的Flash Player API定义就不需要编译。虽然每个SWF都需要用到原生的定义(Array,XML,Sprite等),但是这些定义只存在于Flash Player的可执行文件中,不需要也不会被编译到SWF文件中。编译器使用一个叫做playerglobal.swc的特殊SWC(预先编译的SWF类库)来识别原生定义。它包含了原生定义的接口,包括定义的名字和数据类型等。编译器通过它来编译SWF,而且不会把这些定义编译到最终的SWF中。

编译器还可以引用其他类似playerglobal.swc一样的SWC库。这些库作为“外部”类库,其中包含的定义只是用于编译,不会包含到SWF内部。

这里不详细讨论在编辑工具中如何进行库链接的设置。不同版本的编辑器的设置有些不同,具体方法请参考Flash文档。

虽然我们用SWCs来编译SWF,但实际上他们本身就是SWF文件,和其他被加载的SWF内容类似。在进行库编译的时候,同时生成了SWF和SWC文件。SWF用于运行时加载,而SWC在编译时用做外部库。

Compiling a Shared Library
编译器使用SWCs共享库,SWF共享库在运行时加载

另一个准备工作需要编写代码。使用外部库的时候,发布的SWF中不包含库中的定义。如果Flash Player尝试运行其中代码,就会产生核查错误,整个SWF基本上就瘫痪了。

Flash Player会在类第一次使用的时候校验其定义。如果应用程序域中不包括该定义,那么校验错误就会产生。

实际上缺少定义产生的错误有两种。校验错误是两种之中最糟的,表示类无法正常工作的灾难性失败。另一种是引用错误,当某种数据类型被引用但是却不可用的情况下发生。虽然缺失定义也会造成引用错误,但这种错误只会在已经经过核查的类内部打断代码执行的正常过程。

var instance:DoesNotExist;
// VerifyError: Error #1014: Class DoesNotExist could not be found.
// 当Flash Player校验包含该定义的类时发生校验错误
var instance:Object = new DoesNotExist();
// ReferenceError: Error #1065: Variable DoesNotExist is not defined.
// 当代码执行到这一行的时候发生引用错误

主要的区别在于校验错误与类定义有关,而引用错误与代码执行相关。在类内部的代码要执行之前,必须要先通过校验。上面的例子中instance对象声明为Object类型,校验可以正常通过(只是在执行的时候就会遇到引用错误)。

Note: Strict Mode 注意:严格模式

外部库是引用定义而不需将其编译到SWF中的一种方法。另一种方法是关闭严格模式,这将大大放宽了对变量使用的检查。对于类的使用来说,你可以引用一个不存在的类而不会引起编译器报错。你不能直接把不存在的类用作变量类型(这样做会在运行时产生校验错误),但是你可以像上面的“引用错误”例子中那样去引用。在非严格模式下,编译器也许会检测不到一些可能发生的错误,所以通常不建议用这种模式。

使用了RSLs的SWF文件必须保证先加载好RSLs,才能使用这些外部定义。我们应该在主应用程序开始执行之前用一个预加载器来加载RSLs。

下面演示了一个SWF加载包含Doughnut类的外部RSL的例子。虽然在SWF中直接引用了这个类,但是它却是编译在外部库中,并通过SWC的方式来引用的。RSL在Doughnut类第一次使用之前就被加载进来,所以不会造成校验错误。

Doughnut.as (编译为 doughnutLibrary.swc 和 doughnutLibrary.swf):

package {
	import flash.display.Sprite;

	public class Doughnut extends Sprite {
		public function Doughnut(){

			// draw a doughnut shape
			graphics.beginFill(0xFF99AA);
			graphics.drawCircle(0, 0, 50);
			graphics.drawCircle(0, 0, 25);
		}
	}
}

ShapesMain.as (Shapes.swf的主类):

package {
	import flash.display.Sprite;

	public class ShapesMain extends Sprite {
		public function ShapesMain(){

			// 虽然并没有编译到Shapes.swf中,
			// 但是我们通过doughnutLibrary.swc外部库
			// 可以获得对Doughnut类的引用
			var donut:Doughnut = new Doughnut();
			donut.x = 100;
			donut.y = 100;
			addChild(donut);
		}
	}
}

Shapes.swf (RSL loader):

var rslLoader:Loader = new Loader();
rslLoader.contentLoaderInfo.addEventListener(Event.INIT, rslInit);

// 把RSL中的定义加载到当前应用程序域中
var context:LoaderContext = new LoaderContext();
context.applicationDomain = ApplicationDomain.currentDomain;

var url:String = "doughnutLibrary.swf";
rslLoader.load(new URLRequest(url), context);

function rslInit(event:Event):void {
	// 只有当RSL中的定义导入到当前应用程序域以后
	// 我们才能用其中的Doughnut定义通过ShapesMain类的校验
	addChild(new ShapesMain());
}

在这个例子中,Shapes.swf是主程序,当RSL加载完毕后实例化主类ShapesMain。如果没有导入RSL中的定义,创建ShapesMain实例的时候就会因为在应用程序域中找不到对应的类而发生校验错误。

注意:Flex中的RSL

这里讨论的方法是最底层的方法,不应该用于Flex开发。Flex框架中有自己的一套RSLs处理机制,更多关于RSL在Flex中的应用,请参考Flex Runtime Shared Libraries (Flex 4)

Getting Definitions Dynamically 动态获取定义

我们可以用Application.getDefinition方法获取不在应用程序域内的定义,或者被父域覆盖的定义。这个方法返回应用程序域及其任意父域内的定义引用。在当前应用程序域内使用getDefinition方法的效果等同于全局函数getDefinitionByName

我们也可以通过SWF的LoaderInfo.applicationDomain来获得在ApplicationDomain.currentDomain以外的应用程序域。在下面的例子中我们用Loader加载了一个SWF文件,然后在加载的那个应用程序域中提取com.example.Box类的定义。

try {
	var domain:ApplicationDomain = loader.contentLoaderInfo.applicationDomain;
	var boxClass:Class = domain.getDefinition("com.example.Box") as Class;
	var boxInstance:Object = new boxClass();
}catch(err:Error){
	trace(err.message);
}

以上的例子中包含了两个知识点。首先,getDefinition方法的返回值被显式的转换为Class类型,这是因为getDefinition默认返回的是Object类型,有可能代表了除了类类型以外的其他类型(函数,命名空间,接口)。其次,这个操作应该要放在try-catch函数体内,因为如果getDefinition查找定义失败将会抛出错误。或者你也可以在使用getDefinition之前用ApplicationDomain.hasDefinition方法检测是否能够成功找到某个定义。

用动态方式去获取的定义,而不是那些在当前应用程序域(及继承的程序域内)的定义,是不能用作变量类型的。就像RSL一样,在应用程序域内找不到的类定义会在校验的时候报错。所以上面的例子中boxInstance变量声明为Object类型而不是Box类型,就是因为Box类的定义在应用程序域内不存在。

Same-definition Collisions 相同定义的冲突

有些时候可能会发生你引用的定义匹配到另外的应用程序域里的定义的交叉情况。这种情况将会产生如下强制转换类型错误:

TypeError: Error #1034: Type Coercion failed: cannot convert
	com.example::MyClass@51e1101 to com.example.MyClass.

你可以看到在不同内存空间里的定义用@符号进行了区分。虽然它们内部的代码可能是完全相同的(或不同),但是由于它们存在不同的应用程序域(或安全域)内,所以它们是两个不同的定义。

只有像Object那样的原生Flash Player定义才可以将位于不同域(甚至是跨安全域的)的定义关联起来。实际上,大多数时候声明一个跨域的变量类型的时候都需要用Object类型。

虽然我们可以用Object这种通用类型来解决定义冲突错误,实际上我们更应该合理安排应用程序域的位置来消除这种不匹配的情况。

Conclusion 总结

这篇教程包含了很多方面的信息。前半部分讨论了什么是安全域,以及它如何影响来自不同域的内容。Flash Player用这种安全沙箱机制保护用户的数据。Flash开发者应该了解并合理利用这种限制。

第二部分讨论了应用程序域——另一种用于在安全沙箱内划分ActionScript定义的沙箱类型。应用程序域的层级机制提供了在不同的SWF直接共享和重用定义的方法。

在安全域和应用程序域的概念上有很多容易犯的错误。希望这篇教程能够帮你对此有所准备。你不仅应当了解他们的运作方式,还要知道如何正确运用它们以达成你想要的效果。

分类: YangDan 标签:

谈谈ActionScript垃圾回收(下)

2011年11月12日 没有评论

前文我们介绍了GC的工作机制和帮助GC更好工作的最佳实践。其实只要我们遵守谁创建谁清理的原则来管理对象,就能基本上避免回收失败,也就是我们通常说的内存泄漏问题。但是在实际项目中我们还会看到各种原因引起的内存泄漏,接下来就让我们一起来找出病因。

首先我们需要观察症状,也就是内存的使用曲线。排查的方法是反复执行一些创建和删除对象的方法、反复加载和卸载子文件。如果内存曲线一路飙升、或者是居高不下,都表明发生了内存泄漏问题。观察内存占用可以直接求助于操作系统的资源管理器,也可以用Hi-ReS-Stats这个类。

第二个需要观察的地方,是Player输出的load和unload信息。加载和卸载外部文件,是内存泄漏问题的重灾区。在调试阶段,我一般会在主文件加一个执行System.gc()语句的按钮。一旦卸载了一个子文件,就手动触发若干次GC。如果没有输出子文件的卸载信息,那么就说明出现泄漏了。

第三个可以帮助我们排查问题的地方是Profiler工具,当你删除了对象引用,并手动触发GC以后,可以观察这个对象是否还存在内存中。Profiler可以说是排查内存泄漏问题的终极工具,唯一的问题就是会拖慢整体的运行速度,比较慢。

观察到问题现象以后我们得顺藤摸瓜,找出到底是那个对象占着内存不放,然后对症下药。下面我们就来分析几个内存泄漏的疑难杂症。

病例一:小心loaderContext和applicationDomain

ActionScript 3的Loader对象远没有我们想象中那么简单,内存泄漏问题有很大一部分是由于不当的加载和卸载操作引起的。我在研究Gaia框架的内存泄漏问题的时候发现了一处由于没有删除LoaderContext的引用而造成的卸载失败问题,其实就是没有释放应用程序域所造成的。应用程序域是一个需要被重视的对象,它对加载和卸载的影响有如下两点:

  • 如果子SWF文件是加载到主应用程序域里的,那么这个文件是不能卸载的(前提是子SWF文件内的类定义没有被主应用程序域里定义所覆盖)。
  • 如果子SWF文件是加载到子应用程序域内(Loader的默认方式),那么这个文件是一定能够被卸载的。

关于应用程序域的知识可以看我以前翻译的文章。根据类定义在主应用程序域里的向下覆盖原则,我们还可以考虑以下情况:如果再次加载相同的子SWF文件到主应用程序域,子文件里所包含的类定义将全部忽略,并不会注册到主应用程序域中。这次加载的SWF文件则是可以被卸载的。换句话说,一旦类定义被加入到主应用程序域里就不能够被删除。而没有加载到主应用程序域内的对象如果不能卸载就肯定是内存泄漏。

实际开发中除了一些确实不需要卸载的模块代码需要加载到主应用程序域中,一般我们还是将对象加载到子应用程序域中去的。

病例二:小心静态类

症状还是某个子文件加载后不能卸载,但是当我们再次加载这个子文件的时候,能从log看到之前的子文件被释放了。这是一个轻度内存泄漏的例子,一般不会引起内存飙升直到引起crash等强烈后果,但是我们也不能掉以轻心。

根据之前的经验:不能卸载一定是某个对象被占住了,后续再次加载又能卸载之前的实例,说明前面文件中被占住的资源又被释放了。我们先通过Profiler查看到底是那个对象被占住,然而分析下来看到居然是子文件中创建的所有实例都已经释放了。那么,到底是什么原因呢?

既然实例都已经被释放了,那么只有可能是类定义被占住了。我在这个子文件中用到了Greensock类库的ImageLoader。通过研究它的源码发现这个加载类库采用了与TweenMax类似的插件机制。当我第一次引用ImageLoader定义的时候,它会自动向LoaderMax类注册。也就是说LoaderMax类的静态成员持有ImageLoader定义的引用。

如果这两个类定义都在子应用程序域中,那么随着子文件的卸载,这两个静态类也会被销毁了。但是我在主文件中也包含了LoaderMax类,这个定义会覆盖掉我在子文件中的定义。于是造成的情况就是:一个主应用程序域中的LoaderMax类持有子应用程序域中的ImageLoader类的引用。这就是子文件无法卸载的原因!

解决方法很简单:要么在主文件中也包含ImageLoader类的定义,要么在主文件中删去LoaderMax类。这样我们就解决了一个由于跨域的静态类引用造成的内存泄漏问题。

从这个例子我们还可以总结一下在ActionScript中静态类、静态变量及其衍生的单例的注意事项,这也是和其他编程语言不同的地方:

  • 只要静态类的定义是在子应用程序域里的,那么是可以被卸载的。
  • 静态类、单例的只能保证在同一个应用程序域里的唯一性。也就是说有可能单例不单。
  • 真正保证静态类和单例的唯一性的方法是把它们的定义加入到主应用程序域。

这种静态类之间引用的问题也是唯一让Profiler束手无策的情况,如果以后能在Profiler中直接看到类定义来自哪个应用程序域就更好了。

除此之外还要小心的是静态类的方法可能造成的对象引用问题,比如:Flash组件的FocusManager.setFocus(),以及Flex框架中的StyleManager的样式注册等等。这篇文章详细讨论了Flex模块的卸载问题。

病例三:延时删除

这个无法卸载的问题来自于我的一个使用Robotlegs模块插件开发的子文件。为了让所有mediator执行自己的onRemove()方法,我在ShutdownCommand中将所有视图从contextView上移除,此外还进行了model和service自己的清理工作。这通常运行良好,能够正确的将模块卸载。但是我却遇到了一个问题,严格来说,这并不是一个GC的问题。因为我通过trace发现mediator的onRemove()方法并没有执行!

没有执行清理当然就有可能造成内存泄漏,那么到底是什么原因,让我从contextView上移除视图的时候没有触发对应mediator的onRemove()方法呢?

答案是Robotlegs的延时机制。为了兼容Flex框架,mediator的onRemove方法并不是在视图的REMOVED_FROM_STAGE事件监听里执行的,而是延迟了一帧(查看代码)。这样在真正的移除代码执行以前我的视图就已经从stage上移除了,也就过不了330行那个检查。

于是我就只好迁就一下Robotlegs,把子文件从显示列表上移除的时间也延迟了一帧,这样问题就解决了。

从这几个例子我们可以看出,内存泄漏的病因可能千奇百怪,但归根结底肯定都是某种引用没有被释放的问题。在实际项目中,建议大家一边开发一边就要测试内存泄漏。不要到了项目的最后阶段再来排查,那样复杂度太高。此外,在引入第三方类库的时候,也要特别注意是否会引起内存泄漏。

本文总结了排查内存泄漏的方法,分析了若干可能引起内存泄漏的代码问题。希望对大家有所帮助。如果同学们在自己的项目中也遇到过一些疑难杂症,欢迎留言一起探讨。

分类: YangDan 标签:

谈谈ActionScript垃圾回收(上)

2011年11月12日 没有评论

最近在一些项目过程中我又对这方面有了更多的理解,在此希望能够分享给大家。首先让我们来回顾一下关于垃圾回收(Garbage Collection,下文简称GC)的一些知识。要阅读本文,你需要对GC机制有些基本认识。

在ActionScript中,我们没有API可以直接删除一个对象,也不能控制Player进行GC。但是GC的行为是可以预估的,作为开发者,我们需要了解的是GC执行的时机是发生在需要向操作系统请求分配内存的时候。

从上面的模拟图我们可以看到:

  • Player以块的方式请求和释放内存。GC的结果不一定就是更少的内存占用,也有可能是从操作系统获得更多的可用内存。
  • Player会在某些GC过程中把内存中未使用部分组合成可以释放的块还给操作系统。
  • 此外还要注意的是Player为了避免占用太多的CPU资源,会将一些GC操作分到不同的时间片中运行,所以一次GC过程并不一定清理完所有可回收资源。

一次GC过程(GC Pass)分为以下两个步骤:

Reference Counting

统计所有对象的引用计数,如果某个对象没有任何引用,就标记为可回收。

这个操作很好理解,需要强调的是weak reference(弱引用)是不参与计算的。引用计数是一个相对省CPU的操作,能够筛选出大部分可回收资源,但是对一些循环引用的情况就无能为力了。在下图中,标记为绿色的对象每个的引用数都为1,但它们明显是应该被回收的。

所以GC需要进行第二个步骤:

Mark Sweeping

这个步骤是从根对象(Root)开始轮询对象的引用。所谓的根对象包括:

  • Stage对象
  • 静态变量
  • 局部变量

这种方式足够精确,能够成功筛选出上图中绿色标记的对象,而它的代价就是较大的计算开销。

为了帮助GC过程更高效的执行,最好是能在第一步引用计数中就把需要回收的对象都标记出来。具体的做法就是把所有不需要的对象引用全部清空,包括:

  • 删除成员变量的引用
  • 从可视对象列表上移除对象
  • 移除事件监听

难点:事件监听是否会造成对象不能回收?这个问题要具体分析,有些情况可以,有些情况却不可以。归根结底还是引用关系的问题。来看下面这个例子:

Foo.as:

public class Foo extends Sprite
{
	private var bar:Sprite;
	public function Foo()
	{
		bar = new Sprite();
		//...

		//监听bar发出的事件。可以看作是bar引用了foo,因为foo的引用被保存在bar的监听者数组里。
		bar.addEventListener(MouseEvent.CLICK, clickHandler);
		addChild(bar);
	}

	private function clickHandler(event:MouseEvent):void
	{
		...
	}
}
Main.as:

// 创建foo实例
var foo:Foo = new Foo();
addChild(foo);

// do something with foo...

// 清除foo的引用
removeChild(foo);
foo = null;

我们看到foo引用了bar,而bar又通过事件监听的联系引用了foo,这就构成了一个循环引用。根据前文对GC步骤的分析,这两个对象都必须到第二步mark and sweep才能被标记出来。

如果我们对事件监听用弱引用的方式:

bar.addEventListener(MouseEvent.CLICK, clickHandler, false, 0, true);

由于弱引用不计入引用计数,所以现在foo的引用数为0。GC在第一步操作中就能把foo标记出来,从而减少了一些运算开销。这也是为什么Grant Skinner呼吁把弱引用作为事件监听的默认方式的原因。

但是作为最佳实践,我们还是提倡要手动移除事件监听。以下代码添加一个destroy()方法(也有习惯命名为dispose()或kill()等)到Foo对象中:

public function destroy():void
{
	bar.removeEventListener(MouseEvent.CLICK, clickHandler);
	removeChild(bar);
	bar = null;
}

在清除foo的引用之前执行destroy()方法:

foo.destroy();
removeChild(foo);
foo = null;

经过我们如此处理,两个对象的引用数全都为0。GC只需第一步处理就完成任务,我们节约了更多的运算开销!

从上例可以看出在一般情况下,监听子对象的事件不会影响GC。不管是弱引用方式的监听,还是显式移除监听,都只是帮助GC更高效运行的手段,而不会影响GC的结果。但是如果事件监听造成的是对象以外的引用关系,情况就不同了,并且很有可能造成回收失败。一个常见的错误例子是监听Stage对象的RESIZE事件,如果没有显式移除这个监听或者是没有采用弱引用方式,那么这个对象就不会被GC回收的。所以我建议大家还是要尽可能的显式移除监听,切断引用关系。

我们现在已经用对GC最友好的方式做好了清理准备,但是对象还没有从内存中删除。在等待GC执行的这段时间,对象内的代码还在继续执行。比如加在对象上的ENTER_FRAME事件监听处理还在继续执行,对象内的MovieClip或Sound都还在继续播放。我们一定要避免这种情况的发生,所以在切断引用之前,我们还要在destroy()方法中做些清理工作。我们要做的工作包括:

  • clearInterval(),clearTimeout()
  • timer.stop()
  • loader.unload()/loader.unloadAndStop()
  • movieclip.stop() 如果有子MC的,也要停止播放
  • bitmapData.dispose()
  • 关闭LocalConnection,NetConnection,NetStream
  • 停止音频和视频的播放
  • 删除Camera和Microphone对象的引用
  • 调用子对象的destroy()方法,如果有的话

其实这些都是在开发中管理资源的基本常识,归结为一句话就是:谁创建了对象,谁就要负责清理该对象。

下面就以一些我在实际项目中开发的destroy()方法为例,看代码说话:

public function destroy():void
{
	// List是一个继承自Sprite的自定义子类

	// 移除list的事件监听
	list.removeEventListener.remove(MouseEvent.CLICK, clickHandler);

	// 我们创建了list实例,也要负责清理
	// 调用list对象自己的destroy()方法
	list.destroy();

	// 将list从显示列表移除
	// 这一步并非必须的步骤,原因是list的父对象会被移除,这样list和stage就没有联系了。
	// 但是在我的代码中,list还有可能会被添加到外部的容器中(比如stage),那么这一步就是必须的了。
	list.parent.removeChild(list);
	list = null;

	// --------------------
	// loader是一个来自tweenmax类库中的ImageLoader实例

	// 如果loader已经创建
	if(loader)
	{
		// 调用loader的dispose()方法,优秀的第三方类库都应该有良好的资源管理机制。
		// 参数true表示把加载的内容从显示列表上移除,帮我们节约了代码。
		loader.dispose(true);
		loader = null;
	}

	// --------------------
	// lightbox实例变量保存了一个外部引用
	// 根据谁创建谁清理的原则,我们在这里不需要负责该对象的清理,只要删除引用就可以了。
	lightbox = null;
}

另一个示例的destroy()方法演示了对数组中对象的处理方法:

public function destroy():void
{
	// cells是一个数组,包含了一组子对象
	var i : int, n : int;
	n = cells.length;
	for(i = 0; i &lt; n; i++)
	{
		// 执行每一个子对象的destroy()方法
		cells[i].destroy();
		// 删除子对象的引用
		cells[i] = null;
	}

	// 删除数组自身的引用
	cells = null;

	// 如果不需要得到每一个子对象的引用,我们也可以简单的用以下代码来清理数组:
	//cells.length = 0;
	//cells = null;
}

在结构更复杂的项目里,我们还可以抽象出一个IDestroyable接口,让需要执行清理的自定义对象实现这个接口。这样我们的清理代码可以写为:

var n:int = this.numChildren;
for(var i:int = n-1; i &gt;= 0; i--)
{
	if(this.getChildAt(i) is IDestroyable)
	{
		IDestroyable(this.getChildAt(i)).destroy();
		this.removeChildAt(i);
	}
}

总结:GC好比是ActionScript城市的环卫工人,我们的每个类都是从事劳动生产的市民。优秀的市民会把生产垃圾分类安放到回收点,而不文明的市民则把垃圾丢得到处都是。你说那种做法让城市的清扫工作变得更加高效?所以请大家谨记“谁创建谁清理”的原则,做一位ActionScript好市民。

本文回顾了GC的一些基本知识和最佳实践。在下一篇中我将结合一些具体问题,为大家把脉GC的疑难杂症。

分类: ActionSprite3.0 标签:

教程:深入理解Flash的沙箱 – Security Domains

2011年11月12日 没有评论

教程:深入理解Flash的沙箱 – Security Domains | Kevin Caos Blog.

 

今天终于有时间把senocular上关于安全域和应用程序域的教程好好看了一遍。觉得人家老外就是专业:内容非常有条理且完整,图文并茂,举例也非常实用,真是教程中的精品。刚好我最近也在整理这方面的知识,于是决定把这篇翻译出来,方便国内的读者。对想要进阶理解Flash的运行机制的朋友,本文是不可多得的好材料。

原文地址:http://www.senocular.com/flash/tutorials/contentdomains/

简介

如果你还没有与复杂的的安全域(security domain)和应用程序域(application domain)问题打过交道,那么你真是个幸运的家伙。当你在加载外部内容(然后他们开始播放)的时候,默认的设置工作的很好,你甚至不知道他们的存在。
但是某些时候你可能需要控制默认设置以外的更多行为和功能,这样你就会遇到前面所说的问题。你也许会困扰于Security.allowDomain和crossdomain.xml文件的区别,又或者你想要深究关于安全性的最佳实践。如果是这样,那么这篇文章就是你所需要的了。
以下的教程将会讨论什么是安全域和应用程序域,以及他们在Flash Player中应该如何使用。

安全域

  • Introduction 简介
  • Sandboxing 沙箱
  • Security Domains 安全域
  • Trust 信任授权
  • Non-executable Trust 不可执行文件的信任机制
  • Non-executable Content Without Trust 非受信的不可执行文件
  • SWF Communication Without Trust 在非受信的SWF之间通讯
  • Merging Security Domains 合并安全域
  • Stage Owner and Access 场景的拥有者和获取权限
  • Local Security Domains 本地安全域
Sandboxing 沙箱

沙箱是用于区分不同的数据和程序执行。沙箱对于安全性尤其重要。如果没有恰当的信任授权,两个位于不同沙箱内的内容应该没有任何交互。Flash Player的安全模型使用称为安全域的沙箱来分离内容。
虽然安全性是沙箱的主要用途,但这并不是唯一使用沙箱的原因。另外一种可能的情形是使用沙箱来避免命名冲突,这种区分代码的沙箱方式在Flash Player中被称为应用域。

Security Domains 安全域

安全域在Flash中是顶级的沙箱。安全域链接到内容的来源域名,或者是被加载的内容(如SWF文件)的来源域名。比如在senocular.com下的SWF文件包含一个链接到senocular.com的安全域,而在example.com下的SWF文件则有一个链接到example.com的安全域。不同的安全域使得SWF文件在Flash Player中播放时运行在自身的沙箱下。

security domains
Flash Player中的安全域沙箱

注意:在本教程的例子中,你将看到我用统一顶级域名下的不同子域来代表不同域名,这是因为在Flash中,不同子域和不同顶级域一样,都被视为不同的域。示例中代码也被简化过了,在Flash编辑环境下用时间线代码也可以进行测试。

不可执行的内容(非SWF文件),比如图片或者文本文件,也被划分到安全域中,同样与他们所处的域名相关联。实际上,正是域名决定了这些内容是否能够被某个SWF文件加载。更多这方面的内容将在不可执行文件的信任机制章节中进行讨论。

回到SWF上来,安全域划分了数据和可执行代码。如果两个SWF处于不同的安全域下,某个SWF中的数据(比如某个变量)是不可以被其他SWF获取的,当然,代码也不能执行。如果尝试获取其他域中SWF文件的数据将会产生一个安全错误。

下面的代码展示了一个SWF文件企图加载另外一个SWF,并获取其文档类的实例(也就是主时间线)。

http://same.example.com/parent.swf:

var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.INIT, init);

var url:String = "http://diff.example.com/child.swf";
loader.load(new URLRequest(url)); 

function init(event:Event):void {
	trace(loader.content);
	// SecurityError: Error #2121: Security sandbox violation:
	// Loader.content: http://same.example.com/parent.swf
	// cannot access http://diff.example.com/child.swf.
	// This may be worked around by calling Security.allowDomain.
}

任意想要获取被加载的SWF文件的内容的尝试,甚至包括trace Loader的content属性。都会引起安全错误,因为他们两者处于不同的安全域内。

安全域的划分也适用于Flash Player所使用的原生ActionScript类。Flash Player在每个安全域中都创建了独立的原生类。举例来说,一个安全域内的XML类与另外一个安全域内的XML类是不相同的,改变其中一个XML的静态属性XML.prettyIndent并不会影响到另一个安全域。

下面这个SWF文件加载了两个子SWF,一个来自自身的域,另一个从另外的域加载。我们改变了主文件的prettyIndent属性,来看看这两个子文件的属性输出。

http://same.example.com/parent.swf:

trace(XML.prettyIndent); // 2
XML.prettyIndent = 5;
trace(XML.prettyIndent); // 5

// Same domain:
var sameLoader:Loader = new Loader();
var sameURL:String = "http://same.example.com/child.swf";
sameLoader.load(new URLRequest(sameURL)); 

// Different domain:
var diffLoader:Loader = new Loader();
var diffURL:String = "http://diff.example.com/child.swf";
diffLoader.load(new URLRequest(diffURL));

http://same.example.com/child.swf:

trace("same: " + XML.prettyIndent); // same: 5

http://diff.example.com/child.swf:

trace("diff: " + XML.prettyIndent); // diff: 2

可以看到,第二个子文件的属性并没有被改变。这就说明不同的安全域具有不同的原生ActionScript类定义。

Trust 信任授权

尽管安全域只允许相同域下的通讯,但是我们可以通过信任授权来让处于两个不同安全域内的SWF文件进行通讯。通过授权,某个安全域内的文件可以获取另一个域内文件的的数据,或者调用其方法,就像是处于相同的安全域下一样。

在ActionScript中,SWF的信任授权是通过Security.allowDomain(或者类似的Security.allowInsecureDomain)来设置的。在被信任的安全域内的代码可以调用这个方法来授权信任给另一个或者一组在其他安全域内的SWF文件。这种信任是单向的,发起allowDomain的SWF文件不能去访问被授权信任的文件,除非对方也做了信任授权。

Security domains with trust
建立信任关系的安全域

在下面的例子中,一个子SWF文件调用allowDomain来允许父SWF的访问:

http://home.example.com/parent.swf:

var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.INIT, init);

var url:String = "http://away.example.com/child.swf";
loader.load(new URLRequest(url));

function init(event:Event):void {
	// (子文件执行了allowDomain)
	trace(loader.content); <em>// [object DocumentClass]</em>
}

http://away.example.com/child.swf:

Security.allowDomain("home.example.com");

如果没有授信,就像前文说到的,还是会引发安全错误。但是一旦被加载的子SWF调用了allowDomain以后,父SWF文件就可以自由的访问子SWF文件中的内容。要注意的是在这个例子中,由于父SWF文件没有授权away.example.com的信任,所以子SWF仍然无法访问loader的content属性。

信任是非常重要的安全概念,绝对不能掉以轻心。我们经常看见使用通配符的授权:

// 小心哦!
Security.allowDomain("*");

这么做将允许所有SWF文件,不仅仅只是你加载的或是加载你的,可以通过ActionScript来访问你文件中的数据。就算你没有在文件中包含敏感数据,但是如果你在文件中提供了某些方法去获取这种数据,那也有可能被其他SWF调用。使用allowDomain来授权的信任就像给了其他SWF文件相等的权利:你能做什么,我就能做什么。在接下来的合并安全域章节中你将看到这意味着什么。

如果你只是想让SWF之间能够通信,除了信任授权的方法以外我们还可以使用sharedEvents对象来实现,我们将在在非受信的SWF之间通讯章节中讨论。

Non-executable Trust 不可执行文件的信任机制

由于不可执行文件(也就是非SWF文件)不能调用allowDomain代码,所以这类文件的信任机制在Flash Player中有不一样的处理方法。这就是跨域(cross-domain)策略文件派上用场的地方。

跨域策略文件是一个放在网站的根域名下的命名为crossdomain.xml的XML文件。和allowDomain类似,定义了一组可以被Flash Player加载的安全网站域名。一个简单的跨域策略文件的例子如下:

http://example.com/crossdomain.xml:

<?xml version="1.0"?>
<cross-domain-policy>
	<site-control permitted-cross-domain-policies="by-content-type"/>
	<allow-access-from domain="*.example.com"/>
	<allow-access-from domain="www.example-partner.com"/>
</cross-domain-policy>

你可以从Cross-domain policy file specification (adobe.com)获得详细的文件格式信息。

和allowDomain不同的是,跨域策略文件只提供了包含所有文件(通常是一个域下的所有文件)的用法。上面的例子表示允许来自example.com的任意子域或www.example-partner.com的SWF文件加载example.com下的文件。

由于存在allowDomain机制,跨域策略文件通常不用于授权SWF文件的访问。跨域加载SWF的时候不会请求跨域策略文件。只有当要把一个跨域的SWF合并到当前的安全域的时候,才需要提供跨域策略文件。这个主题将在合并安全域中进行讨论。

不管是标准的位于域名根目录下的跨域策略文件还是用Security.loadPolicyfile指定的跨域策略文件,都只有在需要的时候才会被加载:当内容被加载的同时,跨域策略文件也被一起加载进来。

加载完成后,Flash Player分析跨越策略文件并判断该域是否为信任SWF所处的域。如果答案是肯定的话,文件正常加载,就像处于和SWF文件相同的域一样。反之则有可能有两种情况:

  • 文件不被加载
  • 文件加载成功,但是其数据不能被SWF文件直接访问

如果文件本身就是数据(文本文件,XML文件,二进制数据等等),那么文件就不会被加载。

Policy file for loading

需要跨域策略文件来加载仅包含数据的文件

如果文件除了数据以外还有其余用途(图像文件,声音文件等),那么文件还是能够被加载到用户可见(可听)的环境中。比如说图像文件,就算没有跨域策略文件,还是可以在Loader对象中显示给用户看。但是类似BitmapData.draw等直接访问图像数据的方法就不能运行。

Policy file for trusting

需要跨域策略文件来获取其他文件的数据的引用

对此可能大家都有点疑惑。实际上用户是可以访问这些数据的,但是SWF文件不行。Flash Player是在保护用户的数据不被潜藏有危险代码的SWF文件获取。用户不需要关心跨域策略文件也能正常的浏览网页内容。

但这并不是说跨域策略文件可以被忽视,类似于下面这种过分纵容的跨域策略文件有很多潜在的危险:

<?xml version="1.0"?>
<cross-domain-policy>
	<!-- 小心哦! -->
	<allow-access-from domain="*"/>
</cross-domain-policy>

警告:使用通配符(*)允许所有域的访问等同于:用户可能可以接触到的所有处于该域下的数据都有可能被任意SWF文件获取。

客户端的Flash Player运行在当前用户的认证下,这就表示用户的数据可能就是Flash Player的数据。而且Flash Player的数据可能被任意在里面运行的SWF获取。Flash Player默认只允许相同域名下的SWF的安全数据,并限制跨域SWF的运行。如果没有这层限制,SWF可以获取任意当前用户可以获取的数据。

举个例子:某用户使用他的认证来登录网页的邮件客户端收取邮件,然后用户打开了一个包含有恶意程序的SWF的页面。如果没有跨域限制的话,这个SWF可以用他现有的认证偷偷地加载用户的邮件页面。用户可以访问的内网也不例外,只要用户能去的地方,SWF就能去。幸好Flash Player阻止了这种获取数据的行为,除非该域通过跨域策略文件给予SWF授权。

记住一个原则,永远不要对包含敏感数据的域开发跨域授权,即使需要上面的信息来进行用户认证。把SWF可以访问的数据划分到不同的域或者子域下面。

Domain Description Policy file
login.example.com Hosts user data None
feed.example.com Hosts public data Includes:<allow-access-from domain="*" />

这将使得敏感数据不可被访问,但是仍然可以对其他域下的SWF文件公开你的其他数据。

Non-executable Content Without Trust 非受信的不可执行文件

如果没有跨域策略文件的信任授权,Flash Player禁止非SWF文件的获取。特别是像文本那样的仅包含数据的文件,甚至不会加载。如果你需要从一个没有跨域授权的域中获取数据,还是有一个变通的办法。

跨域策略文件用于保护数据,特别是保护用户数据,或者说是用户能够接触到的数据。因为Flash Player是运行在客户端的,但是服务器端的代码没有这种限制。服务器完全是另外一台机器,所以用户请求和服务器请求是完全无关的。

由于服务器没有用户的限制,服务器端的代码可以从任意公开的网络服务获取数据。也就是说包含SWF的服务器可以用于访问外部域的数据,然后作为相同域的数据返回给Flash Player。由于处在相同的域下,Flash Player就不需要有跨域策略文件了。

下面的代码演示了一个服务器端的php脚本加载外部数据的例子:

http://home.example.com/loader.swf:

var urlLoader:URLLoader = new URLLoader();

var urlVariables:URLVariables = new URLVariables();
// 服务器端获取外部数据的地址
urlVariables.externalURL = "http://away.example.com/content.txt";

// 服务器端的脚本获取外部数据并传给SWF
var serverPage:String = "http://home.example.com/read.php";

var request:URLRequest = new URLRequest(serverPage);
request.data = urlVariables;
urlLoader.load(request);

这种解决方案也有一定的问题。首先你必须能够在服务器端部署代码,某些小项目也许根本不需要服务器环境。

另一个可能更重要的原因是这种方式加倍了网络流量。首先必须从外部域加载到你自己的域下,然后才被下载到你的SWF客户端。同时这也加重了你的服务器的负载。使用跨域策略文件的话就不会有这种问题。

这可以作为一种解决方案,但最好还是能用跨域策略文件来解决。

SWF Communication Without Trust 在非受信的SWF之间通讯

在某些情况下有可能要与其他来源不那么可靠的域中的SWF通讯,你并不希望完全信任该域,放开全部授权。对此LoaderInfo对象的sharedEvents属性提供了另一种机制。sharedEvents对象是唯一的一个可以在不同安全域中发送共享事件的对象。加载者和被加载者都可以通过这个对象来向对方发送事件。

通过sharedEvents对象发送的事件在两个域中都是完全受信的,这就使得在两个安全域中传递的任意数据都无需考虑安全问题。

警告:当心!通过sharedEvents对象传递了错误的数据仍然有可能把你的SWF中的数据暴露出去。比如你的事件包含了一个复杂对象,特别是显示列表上的对象,那么你的整个SWF都将暴露。

所以通过sharedEvents发送的事件应该限制为包含简单数据的事件类型,避免一些被包含后门的SWF程序加以利用。如果你要传递复杂的事件,那要在传递之前先做一下清理。

使用sharedEvents进行通讯的两个SWF需要确保发送和接收的是一致的事件类型。父SWF可以发出一种事件并监听另一种。子SWF可以监听父SWF发出的事件并发出父SWF中正在监听的事件。这些事件的名字可以是随意的。

下面的例子演示了在不同安全域中的父子SWF使用sharedEvents来通讯简单的文本信息的情况。父SWF发出“fromParent”事件,而子SWF发出“fromChild”事件。

http://safe.example.com/parent.swf:

var loader:Loader = new Loader();
var shared:EventDispatcher = loader.contentLoaderInfo.sharedEvents;
shared.addEventListener("fromChild", fromChild);

var url:String = "http://untrusted.example.com/child.swf";
loader.load(new URLRequest(url));

function fromChild(event:TextEvent):void {
	trace(event.text); // Good day

	var replyMessage:TextEvent = new TextEvent("fromParent");
	replyMessage.text = "Same to you";
	shared.dispatchEvent(replyMessage);
}

http://untrusted.example.com/child.swf:

var shared:EventDispatcher = loaderInfo.sharedEvents;
shared.addEventListener("fromParent", fromParent);

var firstMessage:TextEvent = new TextEvent("fromChild");
firstMessage.text = "Good Day";
shared.dispatchEvent(firstMessage);

function fromParent(event:TextEvent):void {
	trace(event.text); // Same to you
}

任意的事件类都可以像这样用于传递信息,也包括自定义事件。再次强调,要当心不要把包含引用的数据(特别是显示列表上的对象)随着事件一起发送出去。这种情况的例子在场景的拥有者和获取权限章节中可以找到。

Merging Security Domains 合并安全域

如果两个域之间建立了信任关系,一个SWF就能把另外一个SWF加到自己的安全域内,就像是在相同的域下一样。

在这种情况下信任授权的处理有少许不同。首先,包含父SWF的域不需要指定什么,只要执行加载另一个SWF到当前的安全域就表示完全信任这个SWF。

其次,因为子SWF是立即被加载到父SWF的安全域中,并没有机会通过allowDomain进行信任授权声明。当子SWF可以执行allowDomain声明的时候,已经被加载并实例化到另外的域中。所以这种情况下,跨域策略文件将派上用场。实际上这也是跨域策略文件唯一适用于对SWF进行授权的情况。

Loading into another security domain

需要跨域策略文件把跨域的SWF加载到相同的安全域

要加载某个SWF到自己的安全域内,需要给Loader.load方法指定一个LoaderContext对象。LoaderContext对象的securityDomain属性设置为当前的安全域(SecurityDomain.currentDomain)。通过这样的加载方式,父SWF授信给子SWF,而子SWF的授信则需要通过跨域策略文件。

http://host.example.com/parent.swf:

trace(new LocalConnection().domain); // host.example.com

var loader:Loader = new Loader();

// 创建一个LoaderContext对象把子SWF加载到当前的安全域
var context:LoaderContext = new LoaderContext(true);
context.securityDomain = SecurityDomain.currentDomain;

var url:String = "http://trusting.example.com/child.swf";
loader.load(new URLRequest(url), context);

http://trusting.example.com/crossdomain.xml:

<?xml version="1.0"?>
<cross-domain-policy>
	<allow-access-from domain="host.example.com"/>
</cross-domain-policy>

http://trusting.example.com/child.swf:

trace(new LocalConnection().domain); // host.example.com

我们可以通过LocalConnection对象的domain属性来检查每个SWF所处的安全域。虽然子SWF原先所处的域是trusting.example.com,但是由于它被加载到父SWF所处的域中,所以子SWF最终所处的安全域是host.example.com。

用这个方式加载的SWF文件权力比用allowDomain授权的更加大。使用allowDomain授权,等同于说你能做什么,我就能做什么。而把SWF加载到同一个安全域,则等同于我能做任何事。在前一种情况下,子SWF只能调用父SWF下的代码,还是受限于父SWF中的定义。但是通过加载到相同的安全域,这些子SWF就可以在你的域下面做任意操作,这包括:

  • 获取父SWF中的任意引用
  • 读取主域中的所有文件
  • 读取其他授信给主域的所有域下的文件
  • 读取主域下的共享对象
  • 获取通过主域建立的共享连接通讯
  • 获取授信给主域的socket连接

所以在引入跨域SWF文件到你当前的安全域下的时候,你要确保这种权力不会被滥用。

使用包含安全域的LoaderContext对象的load方法不是能够引入跨域SWF到你的安全域的唯一方法。Loader类的另一个方法loadBytes也可以做到。和load不同的是,它不是用URL来加载外部内容,而是直接加载以ByteArray的形式加载对象。

由于ByteArray与域名之间没有关联,所以用loadBytes方法加载的对象将直接进入当前安全域内。因为你在加载包含这些字节对象之前往往都要经过某种信任授权,所以这通常是安全的。

http://host.example.com/parent.swf:

trace(new LocalConnection().domain); // host.example.com

var loader:Loader = new Loader();

var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener(Event.COMPLETE, bytesLoaded);

// cross-domain policy file required to load data
var url:String = "http://trusting.example.com/childbytes.swf";
urlLoader.load(new URLRequest(url));

function bytesLoaded(event:Event):void {
	loader.loadBytes(urlLoader.data);
}

http://trusting.example.com/crossdomain.xml:

<?xml version="1.0"?>
<cross-domain-policy>
	<allow-access-from domain="host.example.com"/>
</cross-domain-policy>

http://trusting.example.com/childbytes.swf:

trace(new LocalConnection().domain); // host.example.com

就和前面看到的例子一样,通过检查子SWF文件的LocalConnection.domain属性,使用loadBytes方法加载的子SWF也显示为相同的安全域。

警告:loadBytes方法有个小小的安全问题:可以把授信过的跨域SWF和加载到当前安全域下的SWF两者间的不同扯平。我们知道虽然这两者都是被信任的,但是就像上面的列表中提到的,后者比前者的权力更大。“你能做什么,我就能做什么”与“我能做任何事”之间的差别,结果可以变成没有差别。

这是因为授信过的跨域SWF文件可以访问父SWF的任何对象,包括父SWF对象的Loader实例,一旦拥有了对loadBytes方法的引用,这就意味着可以把某些字节对象加载到当前的安全域。

下面的这个例子展示了这种可能性:

http://good.example.com/parent.swf:

// 授权 "你能做什么,我就能做什么"
Security.allowDomain("evil.example.com");

// 应当受保护的数据
var so:SharedObject = SharedObject.getLocal("foo", "/");
so.data.foo = "bar";
so.flush();

var loader:Loader = new Loader();
var url:String = "http://evil.example.com/child.swf";
loader.load(new URLRequest(url));

http://evil.example.com/child.swf:

var so:SharedObject = SharedObject.getLocal("foo", "/");
trace("trust only: " + so.data.foo); // trust only: undefined

var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener(Event.COMPLETE, bytesLoaded);

var url:String = "http://evil.example.com/childbytes.swf";
urlLoader.load(new URLRequest(url));

function bytesLoadedEvent):void {
	// 威胁!loadBytes加载了SWF数据到父SWF的安全域
	loaderInfo.loader.loadBytes(urlLoader.data);
}

http://evil.example.com/childbytes.swf:

var so:SharedObject = SharedObject.getLocal("foo", "/");
trace("same domain: " + so.data.foo); // same domain: ba

将来版本的Flash Player可能会改变这种行为,所以在程序中不要使用这种方法。我们应该关注的是加载授信过的SWF文件会带来的潜在威胁:暴露你的域下的所有数据。

Stage Owner and Access 场景的拥有者和获取权限

当第一个SWF文件被加载到Flash Player中的时候,它被加到显示列表的根上,也就是我们所说的stage对象。这也是Flash Player自己的显示对象的根。每个SWF都有代表自己主文档类或者主时间线的根(叫做root)。第一个被创建的SWF实例的根被放置于场景上,其他子SWF使用Loader对象的实例来加载。

场景的特别之处在于它本身就位于显示列表上,所有处于显示列表上的子SWF都可以取得它的引用,但是它只有一个拥有者:就是第一个被实例化的那个SWF。场景的拥有者决定了场景所连接的安全域。其他的SWF想对场景进行特殊操作的行为都必须获得场景所有者的信任授权。

你可能有注意到在过去的有些程序或者是组件中,被加载到不同的域(未授信)里的时候报错。这正是因为没有取得对场景对象进行操作的授权。因为场景对象是可以被引用的,但是诸如场景的addEventListener方法等却不可用,所以这很容易引起误解。

下面这个表格列出了场景对象限制非安全域对象访问的成员。可能不是100%精确,主要用于参考。

addChild addChildAt removeChild
removeChildAt getChildIndex setChildIndex
getChildAt getObjectsUnderPoint swapChildren
swapChildrenAt numChildren tabChildren
mouseChildren width stageWidth
fullScreenWidth height stageHeight
fullScreenHeight quality align
scaleMode displayState fullScreenSourceRect
stageFocusRect showDefaultContextMenu colorCorrection
addEventListener dispatchEvent hasEventListener
willTrigger

在下面的例子中看看场景是如何可以被子SWF访问,但是却不能调用stage.addEventListener方法。

http://first.example.com/parent.swf:

var loader:Loader = new Loader();
addChild(loader);

var url:String = "http://<samp>second</samp>.example.com/child.swf";
loader.load(new URLRequest(url));

http://second.example.com/child.swf:

// Works
trace(stage); // [object Stage]

// Does not work
stage.addEventListener(MouseEvent.CLICK, stageClick);
// SecurityError: Error #2070: Security sandbox violation:
// caller http://second.example.com/child.swf cannot access
// Stage owned by http://first.example.com/parent.swf.

场景的这种所有者关系非常操蛋,因为我们经常需要对场景对象监听鼠标或者键盘事件,比如检测键盘按下或者检测鼠标在物体外部释放点击。在这种情况下,单靠子SWF自身是没办法完成的。还好,场景拥有者的父SWF可以通过sharedEvents传递场景事件而不必授信给子SWF。通过这种方式,可以在保护主域的前提下配合完成这种工作。

警告:以下这个使用范例演示了sharedEvents是如何处理安全性问题的。一些鼠标事件的relatedObject属性持有对时间线上的对象的引用。如果不经过清理,这些对象就会暴露给没有经过授信的域。所以通过sharedEvents发送事件时,要把这些引用清除。比如MouseEvent,我们可以新建一个仅包含必须数据的MouseEvent对象。

下面的示例展示了如何通过sahredEvents发送场景事件。示例中仅仅转发了MOUSE_OUT事件,当然也可以扩展于其他的事件类型。注意如何创建一个代理事件并保护父SWF中的对象。

http://stageowner.example.com/parent.swf:

var combination:String = "1-2-3-4-5"; // 隐私数据

var loader:Loader = new Loader();
var shared:EventDispatcher = loader.contentLoaderInfo.sharedEvents;

var url:String = "http://untrusted.example.com/child.swf";
loader.load(new URLRequest(url));

stage.addEventListener(MouseEvent.MOUSE_OUT, forwardMouseEvent);

function forwardMouseEvent(event:MouseEvent):void {
	// 威胁!这种做法暴露了relatedObject,也使得其他数据被暴露
	//shared.dispatchEvent(event);

	// Safer: 创建一个清理过的代理事件来切断relatedObject的引用
	var safeEvent:MouseEvent = new MouseEvent(event.type);
	safeEvent.altKey = event.altKey;
	safeEvent.buttonDown = event.buttonDown;
	safeEvent.ctrlKey = event.ctrlKey;
	safeEvent.delta = event.delta;
	safeEvent.localX = event.localX;
	safeEvent.localY = event.localY;
	safeEvent.shiftKey = event.shiftKey;

	shared.dispatchEvent(safeEvent);
}

http://untrusted.example.com/child.swf:

var shared:EventDispatcher;

// 如果场景事件不能引用,那就通过sharedEvents监听
if (loaderInfo.parentAllowsChild){
	stage.addEventListener(MouseEvent.MOUSE_OUT, stageMouseOut);
}else{
	shared = loaderInfo.sharedEvents;
	shared.addEventListener(MouseEvent.MOUSE_OUT, stageMouseOut);
}

function stageMouseOut(event:MouseEvent):void {
	// -- stage mouse out actions here --

	// 如果sharedEvents传递了原始的鼠标事件,那么父域中的数据就暴露了!
	//trace(Object(event.relatedObject).root.combination); // 1-2-3-4-5
}

幸好有safeEvent这个MouseEvent的实例,原本的MouseEvent对象的relatedObject指向的引用被屏蔽了。实际上,所有通过sharedEvents对象发送的事件都应该用这种方式清理一遍。

Local Security Domains 本地安全域

在硬盘上运行的SWF文件同样有自己的安全域。本地安全域有自己独特的行为,共分为4种安全沙箱类型:local-with-file, local-with-network, local-trusted, and application for AIR(本文不详细讨论AIR)。再加上网络上的SWF,一共有5种安全沙箱。在ActionScript中,你可以用Security.sandboxType来获得当前的安全沙箱类型。

  • local-with-file (Security.LOCAL_WITH_FILE)—本地不受信任的文件,只可以访问本地数据,不能与网络通信。
  • local-with-network (Security.LOCAL_WITH_NETWORK)—本地不受信任的文件,只可以访问网络,但是不能读取本地数据。
  • local-trusted (Security.LOCAL_TRUSTED)—本地受信的文件,通过Flash Player设置管理器或者FlashPlayerTrust文件授权。可以访问本地和网络。
  • application (Security.APPLICATION)—随着AIR包而安装,运行在AIR程序中。默认在这种沙箱类型下的文件可以调用任意域下的文件(外部域可能不允许)。而且默认能从任意其他域下加载数据。
  • remote (Security.REMOTE)—来自网络的文件,遵循安全域的沙箱规则。

由于有可能从用户的硬盘上获取敏感数据,所以本地文件在安全方面有着严格的规则。一旦有机会,恶意的SWF将可以从电脑上读取数据并上传到网络上的服务器。所以为了防止这种情况,本地的SWF只允许一种类型的通讯,要么就是本地,要么就是网络。而且,不同安全沙箱类型的SWF不能相互加载,避免能同时访问网络和访问本地的情况出现。

由于我们不能判断本地文件的域名,所以判断本地SWF文件的安全沙箱用的是另外一种形式。只允许本地和只允许网络这两种情况是通过SWF文件内的标识来区分的,所有的SWF在发布的时候都带有这种标识,当SWF在本地运行的时候,Flash Player就用它来检测安全沙箱类型。

对于本地的信任文件,相同的问题是我们没有地方来保存跨域策略文件,所以我们通过Flash Player的设置管理器里的全局安全设置面板来设置。通过下图中所示的下拉菜单把本地SWF的路径添加到本地的安全文件列表里。

Settings Manager

Flash Player 设置管理器

另一种方式是通过配置文件的方式。配置文件不像设置管理器那样需要联网来使用。要把SWF或者包含SWF的文件夹添加到信任位置的话只需要添加路径到Flash Player的#Security\FlashPlayerTrust下的.cfg文件就可以了。在Mac和Windows上,这个路径如下:

  • Windows 95-XP:
    C:\Documents and Settings\[username]\Application Data\Macromedia\Flash Player\#Security\FlashPlayerTrust
  • Windows Vista-7:
    C:\Users\[username]\AppData\Roaming\Macromedia\Flash Player\#Security\FlashPlayerTrust
  • Mac OS X:
    /Users/[username]/Library/Preferences/Macromedia/Flash Player/#Security/FlashPlayerTrust

把[username] 替换为你的用户名。

两种方法都是把信任授权信息写入你的硬盘(全局安全设置面板也一样,保存在Flash Player的特定目录下)。这些文件就像你本地的跨域策略文件一样。当Flash Player读取的时候,信任授权给SWF,并覆盖SWF文件中关于本地访问权限的标识,从而允许受信的SWF能够同时访问本地和网络。

Local sandbox types
本地安全域

关于本地信任机制还有一点需要注意的是即使是本地受信的文件,仍然不能把处于外部沙箱的内容加载入本地沙箱。

大多数Flash内容都是为网络创建的,所以通常不需要完全理解本地的安全沙箱机制。但是开发和测试是一个例外。Flash编辑器在测试的时候就是把SWF放在一个本地受信的安全沙箱里面。这有可能会造成与真正发布的SWF情况不同的结果,因为两者的安全沙箱不同。比如你测试的时候可以从没有授信的外部域读取内容,而真正发布到网站上的SWF却无法加载。所以当你测试的时候要注意,你所看到的结果和最终发布的版本有可能不一样。

你可以到Flash Player Developer Center(adobe.com)的Security section查看更详细的安全相关信息。

分类: ActionSprite3.0 标签:

FLASH 调试工具

2011年11月12日 没有评论

http://kevincao.com/2010/05/better-debugging-flash-website/

Flash网站开发中常常遇到loading功能、deeplinking、js与flash交互等问题,而这些问题只有在在真实的网络环境下才能进行有效的测试。还在为上传—测试—再上传—测试的重复劳动而烦恼吗?本文告诉你一个更简单有效的debug解决方案。

1.开发Flash网站就应该用网页(而不是swf文件)来进行测试。只需要在FDT的Run Configrations里面按照下图进行设置,就能在项目编译好之后自动打开浏览器来测试(请设置为Firefox,flash插件为debug版本,后面你就知道为什么了)。 image

2.如果是用debug模式运行,FDT的控制台应该就能接收到Flash传递出来的各种信息了。这里有使用FDT来debug的详细教程,我就不复述了。但即使不用debug模式,我们也可以得到trace信息的,只要我们祭出这两件两件法宝:FirebugFlashbug。Firebug是网页开发者必备的插件,功能非常强大。而Flashbug是Firebug的扩展,专门负责输出Flash文件的trace信息。我们来看看这两个插件工作中的截图:

firebug flashbug

3.除了简单的输出信息以外,我们还有更专业的调试工具De MonsterDebugger。如果你和我们一样使用Gaia Framework来构建网站的话,其实你已经用上这个方便的工具了。只要把代码中的trace()换成GaiaDebug.log(),你就能在De MonsterDebugger的窗口中看到更为详尽的信息。

monsterdebug

除此之外,De MonsterDebugger还有个非常棒的资源监视器:帧频、内存占用情况一目了然,你还能看到用户操作对整个程序执行时间造成的影响。我们在开发Mercedes-Benz Autoshow 2010 Web Special的过程中就是通过它来排查内存泄漏的问题。

4.接下来让我们更进一步:是否怀念Flash IDE中提供的模拟下载功能?以前我们测试loading都靠要它。现在用FDT作为主要开发工具,我们有了更好的替代方法。首先需要搭建一个本地的服务器环境。我用的是xampp,很简单易用的一个组合程序包。装好之后你的电脑就有Apache、MySql等服务器环境(如下图)。开启Apache服务后,就可以访问http://localhost或者http://127.0.0.1

image

如果一切正常,那么接下来你就可以复制项目文件去xampp安装目录下的htdocs准备测试了。

5.虽然现在你有了一个服务器环境,但是访问本地网站的速度实在太快,所以在正式开始前,你还需要安装一个Firefox插件:Firefox Throttle。这是一个限速插件,通过它你可以任意选择想要模拟的带宽速度:

image

还要在第二个选项页勾选限制本地连接的速度:

image

6.到此我们的Flash Website Debug环境就要大功告成了。最后还要记得每次测试以后都要清空缓存。推荐装一个Cache Status扩展,能够进一步简化这个操作。

image

总结:在真实的网络测试环境下,我们可以去分析Firebug中输出的各项信息,这对批量加载、队列加载等复杂loading逻辑的debug非常有用。此外我们还可以很方便地测试浏览器的前进后退等deeplinking相关功能。当然,测试与js之间的交互传值控制也完全没有问题。

如果你也有调试方面的好方法,欢迎留言探讨。下载文中提到的软件/插件:

分类: ActionSprite3.0 标签:

ZSWF 1.1 发布,让你的SWF文件减少40%,支持Flash10

2011年11月12日 没有评论

http://bbs.9ria.com/thread-103658-1-1.html

Flash11新增加了SWF对LZMA压缩的支持
这种压缩算法比以前的压缩率要高不少
著名压缩工具7zip默认就是使用这种压缩算法

ZSWF是一个替换SWF压缩方法和版本号的工具
使用方法很简单
swf拖到命令行工具里就可以了
或者根据命令提示操作

经测试一般能减少30%-40%
但由于jpg等图片本身已经有很高的压缩率
所以图片居多的时候压缩率不高
这是我用aswing2做的测试
未命名.PNG 

另外因为FP11才能解码LZMA压缩过的SWF
所以我用AS3写了一个精简的解码器来解码LZMA
以及LZMA压缩过的swf(ZWS)

比较特别的是
我发现就算主文件是用10发布的
只要运行时的flash播放器为11就能加载11的swf
所以在解码swf的时候
会根据运行时的播放器来判断是否需要使用as3来解码
注意:
因为SWF13(SWF Version)以上才支持ZWS格式解码
所以在编码时我把低于13的SWF强制改成了13并记录了下来
不过这并没有太大的影响,只是在使用内置解码时
FP会把它当成高版本去加载,请考虑兼容性的问题
AS3解码则没有影响
 zswf.rar (601.33 KB) 

lzma.exe是7z官方提供的lzma编解码文件的工具
能压缩文件但不直接支持对SWF的压缩
AS3版本的解码器也提供对它的解码支持
 lzma.exe (71.5 KB) 

改下排版,顺便祝大家光棍节打飞X快乐 
什么html5统一天下,flash毁灭的简直弱爆了

分类: YangDan 标签:

关于flash全屏禁用所有键盘输入的解决方法

2011年11月5日 没有评论

关于flash全屏禁用所有键盘输入的解决方法
<param name=”allowFullScreen” value=”true” />
< embed src=”example.swf” allowFullScreen=”true” … >
AC_FL_RunContent( … “allowFullScreen”, “true”, … )
stage.displayState=StageDisplayState.FULL_SCREEN
全屏模式是在响应用户单击鼠标或按键时初始化的;在没有用户输入的情况下,影片不能更改 Stage.displayState。 当 Flash Player 处于全屏模式时,将禁用所有键盘输入(可使用户退出全屏模式的键盘快捷键除外)。 当用户进入全屏模式时,将在影片前面显示一个 Flash Player 对话框,告诉用户已经进入了全屏模式,并可以按 Esc 键退出全屏模式
很多时候我们需要输入文本,可是FLASH处于安全的考虑还是把这个功能屏蔽了, 
但是我们可以使用Javascript代码来实现全屏
<script language=”Javascript”> 
  <!– 
  window.open(“index.swf”,”",”fullscreen=1,menubar=no,width=800,height=600″) 
  //–> 
< /script>

分类: YangDan 标签: