前段时间按照公司要求做了一个Flex多图片压缩上传组件,使网站实现了图片的快速上传与分享,提高了用户体验。
使用Flex4开发,Flex好像没有自带的多线程功能,因此借用了 开源项目async-threading实现多线程并发上传。
google code地址:http://code.google.com/p/async-threading/
要在flex4 sdk环境下使用,要先修改一下源代码,打开com.symantec.premiumServices.asyncThreading.handlers.FPSObserverHandler
将import mx.core.Application; 修改为import spark.components.Application;
将private var _appRef:Application = Application.applicationas Application;修改为private var _appRef:Application = FlexGlobals.topLevelApplication as Application;
同时导入import mx.core.FlexGlobals;
这个api要求自定义的线程继承AbstractAsyncThread然后实现IAsyncThreadResponder接口。
首先自定义一个 CommendThread.as
package common { import com.symantec.premiumServices.asyncThreading.abstract.AbstractAsyncThread; import com.symantec.premiumServices.asyncThreading.interfaces.IAsyncThreadResponder; public class CommendThread extends AbstractAsyncThread implements IAsyncThreadResponder { private var _f:Function; public function CommendThread(f:Function) { this._f = f; } public function execute():void { _f.call(); this.kill(); } } }
定义图片组件 MyImage.mxml
<?xml version="1.0" encoding="utf-8"?> <s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" autoDrawBackground="true"> <fx:Script> <![CDATA[ import mx.controls.Alert; ]]> </fx:Script> <s:Image width="100" height="100"> <s:creationComplete> <![CDATA[ var img:Image = event.target as Image; var file:FileReference = data as FileReference; img.source = file.data; ]]> </s:creationComplete> </s:Image> <s:Image x="84" y="0" width="16" height="15" source="@Embed('close_btn.png')"> <s:click> <![CDATA[ parentDocument.valiUpload(); var file:FileReference = data as FileReference; parentDocument.removeImageArray(file); ]]> </s:click> <s:mouseOver> <![CDATA[ Mouse.cursor = MouseCursor.BUTTON; ]]> </s:mouseOver> <s:mouseOut> <![CDATA[ Mouse.cursor = MouseCursor.ARROW; ]]> </s:mouseOut> </s:Image> </s:ItemRenderer>
主文件 flexMultiUpload.mxml
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="700" height="450" minWidth="955" minHeight="600" initialize="init()"> <fx:Style> @namespace s "library://ns.adobe.com/flex/spark"; @namespace mx "library://ns.adobe.com/flex/mx"; .back { backgroundColor:#E7E7E7; borderAlpha:0; } </fx:Style> <fx:Script> <![CDATA[ import common.CommendThread; import mx.collections.ArrayCollection; import mx.controls.Alert; import mx.graphics.codec.JPEGEncoder; import mx.utils.StringUtil; import spark.components.Image; private var fileRef:FileReferenceList = new FileReferenceList(); [Bindable] private var imageArray:ArrayCollection = new ArrayCollection;//图片数组 private var imageNameArray:ArrayCollection = new ArrayCollection;//上传图片名称数组 private var uploadURL:String = "http://www.leku.com/flex/flexuploadimage?"; private var header:URLRequestHeader = new URLRequestHeader("Content-type", "application/octet-stream"); private var isUploading:Boolean = false; //初始化 public function init():void { //初始化图片上传的url var url:String = this.parameters.url; if(url != null && url.length>0) { uploadURL = decodeURI(this.parameters.url); } //初始化文件选择事件 fileRef.addEventListener(Event.SELECT,selectFileHandler); fileRef.addEventListener(Event.COMPLETE,completeFileHandler); } public function valiUpload():void { if(isUploading) { Alert.show("正在上传中,请稍后操作。"); throw new Error("正在上传中,请稍后操作。"); } } //打开图片选择框 private function showSelectDialog():void { valiUpload(); fileRef.browse([ new FileFilter("Images (*.jpg, *.jpeg, *.gif, *.png)", "*.jpg;*.jpeg;*.gif;*.png") ]); } //图片选择完成后 private function selectFileHandler(e:Event):void { for each (var file:FileReference in fileRef.fileList) { if(imageNameArray.contains(file.name)) { continue; } file.load(); file.addEventListener(Event.COMPLETE,completeFileHandler); } uploadButton.visible = true; } //图片加载完成后 private function completeFileHandler(e:Event):void { var file:FileReference = e.target as FileReference; insertImageArray(file); } //图片数组增加元素 private function insertImageArray(file:FileReference):void { imageArray.addItem(file); imageNameArray.addItem(file.name); } //删除图片数组元素 public function removeImageArray(file:FileReference):void { var index:Number = imageArray.getItemIndex(file); if(index >= 0) { imageArray.removeItemAt(index); } index = imageNameArray.getItemIndex(file.name); if(index >= 0) { imageNameArray.removeItemAt(index); } } private var value:Number = 0; private var total:Number = 0; private var index:Number = 0; private var maxSize:Number = 1024;//设置图片压缩尺寸 //上传图片 private function upload():void { if(imageArray.length <= 0) { Alert.show("请选择图片"); return; } valiUpload(); isUploading = true; value = 0; total = imageArray.length; progressBar.visible = true; progressBar.setProgress(value,total); for each (var file:FileReference in imageArray) { uploading(file); } } private function uploading(file:FileReference):void { new CommendThread(function():void { var fileName:String = file.name; var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function():void { var bm:Bitmap=loader.content as Bitmap; var bmd:BitmapData; if((bm.width>=bm.height)&&bm.width>maxSize) { var rateW:Number=maxSize/bm.width; var h:Number=rateW*bm.height; bmd=new BitmapData(maxSize,h,true,0); bmd.draw(bm.bitmapData,new Matrix(rateW,0,0,rateW),null,null,null,true); bm=new Bitmap(bmd,PixelSnapping.ALWAYS,true); bmd = bm.bitmapData; } else if(bm.height>bm.width&&bm.height>maxSize) { var rateH:Number=maxSize/bm.height; var w:Number=rateH*bm.width; bmd=new BitmapData(w,maxSize,true,0); bmd.draw(bm.bitmapData,new Matrix(rateH,0,0,rateH),null,null,null,true); bm=new Bitmap(bmd,PixelSnapping.ALWAYS,true); bmd = bm.bitmapData; } else { bmd = bm.bitmapData; } var urlLoader:URLLoader = new URLLoader(); var jpeg:JPEGEncoder = new JPEGEncoder(80); var b:ByteArray = jpeg.encode(bmd); var subRequest:URLRequest = new URLRequest(); subRequest.method = URLRequestMethod.POST; subRequest.requestHeaders.push(header); subRequest.url = uploadURL+"&fileName="+encodeURI(fileName==null?"":fileName);//url编码,不然汉字会乱码 subRequest.data = b; urlLoader.load(subRequest); urlLoader.addEventListener(Event.COMPLETE,function(e:Event):void{ progressBar.setProgress(++value,total); ExternalInterface.call("flexCallBack",urlLoader.data); if(value == total) { imageArray.removeAll(); imageNameArray.removeAll(); value = 0; total = 0; progressBar.visible = false; ExternalInterface.call("flexCompleteCallBack"); isUploading = false; } // System.gc(); }); }); loader.loadBytes(file.data); }).start(); } ]]> </fx:Script> <s:VGroup x="0" y="0" width="700" height="450" gap="0"> <s:HGroup width="700" height="400" gap="0"> <s:Scroller> <s:DataGroup width="684" height="400" dataProvider="{imageArray}" focusColor="#EE7091" itemRenderer="mycomponent.MyImage"> <s:layout> <s:TileLayout useVirtualLayout="true" requestedColumnCount="6"> </s:TileLayout> </s:layout> </s:DataGroup> </s:Scroller> </s:HGroup> <s:BorderContainer width="700" height="50" styleName="back"> <s:Button x="210" y="8" width="116" height="35" label="选择图片" click="showSelectDialog()"> <s:mouseOver> <![CDATA[ Mouse.cursor = MouseCursor.BUTTON; ]]> </s:mouseOver> <s:mouseOut> <![CDATA[ Mouse.cursor = MouseCursor.ARROW; ]]> </s:mouseOut> </s:Button> <s:Button x="370" y="8" width="116" height="35" label="图片上传" click="upload()" id="uploadButton" visible="false"> <s:mouseOver> <![CDATA[ Mouse.cursor = MouseCursor.BUTTON; ]]> </s:mouseOver> <s:mouseOut> <![CDATA[ Mouse.cursor = MouseCursor.ARROW; ]]> </s:mouseOut> </s:Button> </s:BorderContainer> </s:VGroup> <mx:ProgressBar x="150" y="200" width="400" id="progressBar" mode="manual" labelPlacement="center" label="%1 of %2,总进度:%3%%" visible="false"> </mx:ProgressBar> </s:Application>
以下是工程结构
这是close_btn.png的图片:
接下来就是要导出swf文件了,如果方式不对的话,会导致文件很大,从何造成网页组件加载很慢。
我是用的是flash builder开发,流程如下
右键点击工程-->选择Properties -->选择 Flex Build Path-->选择RSL选择,这样编译的时候只会加载使用到的类库,大大
降低swf文件大小-->点击OK
点击Export Release Build
选择工程,然后点击finish,完成后在bin-release目录下找到flexMultiUpload.swf
可以使用浏览器直接打开,或者flash客户端(尽量把flash更新到最新版本)
服务端使用java struts2
import java.io.File; import java.io.InputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.FileUtils; import org.apache.struts2.ServletActionContext; import org.apache.struts2.convention.annotation.Action; import com.opensymphony.xwork2.ActionSupport; public class FlexAction extends ActionSupport{ /** * */ private static final long serialVersionUID = -3543364166243030722L; private HttpServletRequest request = ServletActionContext.getRequest(); private HttpServletResponse response = ServletActionContext.getResponse(); public static final Integer MAX_SIZE = 1024*1024*2;//允许图片最大尺寸 private String fileName; @Action(value = "flexuploadimage") public String upload() throws Exception{ InputStream inputStream = request.getInputStream(); int formlength = request.getContentLength(); //如果图片大于允许的值 if(formlength > MAX_SIZE){ //返回异常信息 response.getOutputStream().println("自定义返回信息,或者跳转到异常页面"); return null; } byte[] formcontent = new byte[formlength]; int totalread = 0; int nowread = 0; while (totalread < formlength) { nowread = inputStream.read(formcontent, totalread, formlength); totalread += nowread; } //将图片写入本地或者图片服务器 File file = new File("D:/flex", fileName); FileUtils.writeByteArrayToFile(file, formcontent); return null; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } }
jsp页面调用
<%@page import="java.net.URLEncoder"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <% request.setAttribute("url", URLEncoder.encode("上传路径", "utf-8")); %> <div style="height:600px;width:800px;display:table-cell;vertical-align:middle;text-align:center;"> <embed src="flexMultiUpload.swf" style="height:450px;width:700px;border: #C9DDEA solid 1px;vertical-align: middle;" flashVars="url=${url }" allowScriptAccess="always"> <br/> <div style="margin-left: -450px;margin-top: 10px;"> <span>如果不能正常使用,请将Flash升级到最新版本。</span> </div> </div>
组件缺陷(由于本文作者偷懒的结果,哈哈):
每一个图片新开一个线程并发上传,如果用户一次性上传的图片过多,可能会造成客户端挂掉的现象。
解决办法:(1)限制每次上传的图片数量
(2)做一个上传队列,几个线程并发执行队列中的任务(可以参考QQ空间相册普通上传功能,注意:不是极速上传)。
(3)flex的多线程是借助于第三方的