`
Lagunarock
  • 浏览: 20171 次
  • 性别: Icon_minigender_1
  • 来自: 成都
文章分类
社区版块
存档分类
最新评论

移动终端上传文件若干问题的研究

阅读更多

最初,我是想实现一个可看见上传进度的效果。
在网络上查找了资料,资料虽少,不过稍加研究,还是实现了效果。
在此,先向前辈们表达敬意。
我将相关代码和思路进行一些整理,一并发出,方便大家参考。

1,需要依赖的包

apache-mime4j-0.6.jar;httpmime-4.0.2.jar

这两个包我一并上传。

 

2,核心类

ProcessEntity

继承自MultipartEntity。

为了获取上传进度,需要加入一个监听器以及自定义输出流。

package org.ashtray.single;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;

import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.ashtray.single.listener.ProgressListner;
import org.ashtray.single.stream.CountingOutputStream;

/**
 * 自定义文件上传实体类
 * 
 * @author Ashtray
 * @createtime 2012-9-29 上午11:36:05 最后修改时间 : 更新记录:
 */
public class ProcessEntity extends MultipartEntity {

	/**上传进度监听器*/
	private ProgressListner listener;

	public ProcessEntity(ProgressListner listener) {
		super();
		this.listener = listener;
	}

	public ProcessEntity(HttpMultipartMode mode, ProgressListner listener) {
		super(mode);
		this.listener = listener;
	}

	public ProcessEntity(HttpMultipartMode mode,String boundary,Charset charset,ProgressListner listener) {
		super(mode, boundary, charset);
		this.listener = listener;
	}

	@Override
	public void writeTo(OutputStream out) throws IOException {
		super.writeTo(new CountingOutputStream(out,listener));
	}
}

 

 ProgressListner

上传进度监听器,上传文件时回调,获取当前上传字节数,更新UI进度条。

package org.ashtray.single.listener;

/**
 * 监听器,上传文件时回调,获取当前上传字节数。
 * 
 * @author Ashtray
 * @createtime 2012-9-29 下午01:45:47
 * 最后修改时间 : 
 * 更新记录:
 */
public interface ProgressListner {

	/**
	* 文件上传过程中被回调,
	* 获取当前上传字节数。
	* 
	* @param count 当前上传字节数
	* @author Ashtray
	* @createtime 2012-10-11 上午11:16:09
	* 最后修改时间 : 
	* 更新记录:
	*/
	void transferred(long count);
}

 CountingOutputStream

自定义输出流,继承自FilterOutputStream。

用于记录已上传的字节数,并告知监听器。

package org.ashtray.single.stream;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.ashtray.single.listener.ProgressListner;

/**
 * 自定义输出流,
 * 在文件上传时记录已上传的字节数,
 * 并回调监听器。
 * 
 * @author Ashtray
 * @createtime 2012-9-29 下午01:58:07
 * 最后修改时间 : 
 * 更新记录:
 */
public class CountingOutputStream extends FilterOutputStream {
	
	/**上传进度监听器*/
	private ProgressListner listener;
	
	/**已上传字节数*/
	private long transferred;

	public CountingOutputStream(OutputStream out,ProgressListner listener) {
		super(out);
		this.listener = listener;
	}

	@Override
	public void write(byte [] buffer,int offset,int count) throws IOException {
		out.write(buffer, offset, count);
		//记录已上传的字节数
		transferred += count;
		//回调监听器方法
		listener.transferred(transferred);
	}
	
	@Override
	public void write(int oneByte) throws IOException {
		out.write(oneByte);
		transferred++;
		listener.transferred(transferred);
	}
}

 

HttpMultipartPost

文件上传异步任务,用于读取本地文件,创建并发送HTTP请求。

上传过程中更新UI。 

package org.ashtray.single.async;

import java.io.File;
import java.io.IOException;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.ashtray.single.Const;
import org.ashtray.single.ProcessEntity;
import org.ashtray.single.activity.ImageUpload;
import org.ashtray.single.http.HttpCenter;
import org.ashtray.single.listener.ProgressListner;
import org.ashtray.single.util.Debug;

import android.app.ProgressDialog;
import android.os.AsyncTask;

/**
 * 上传文件异步任务,
 * 便于实时更新UI中的进度条。
 * 
 * @author Ashtray
 * @createtime 2012-9-29 下午02:15:51
 * 最后修改时间 : 
 * 更新记录:
 */
public class HttpMultipartPost extends AsyncTask<Void, Integer, HttpResponse> {
	
	/**上下文*/
	private ImageUpload activity;
	
	/**为简便,使用系统自带的进度条对话框*/
	private ProgressDialog dialog;
	
	/**上传文件大小(实际上并非完全是待上传文件的大小,而是在文件基础上加入了部分描述信息)*/
	private long size;
	
	public HttpMultipartPost(ImageUpload activity){
		this.activity = activity;
	}
	
	@Override
	protected void onPreExecute() {
		//初始化进度对话框,并显示
		dialog = new ProgressDialog(activity);
		dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
		dialog.setMessage("Uploading Image");
		dialog.setCancelable(true);
		dialog.show();
	}

	@Override
	protected HttpResponse doInBackground(Void... params) {
		//创建HTTP请求
		HttpClient client = new DefaultHttpClient(HttpCenter.getParams());
		HttpPost post = new HttpPost(Const.URL_BYTE);  
		
		//构建上传文件实体,注意匿名的监听器
		ProcessEntity entity = new ProcessEntity(new ProgressListner() {
			@Override
			public void transferred(long count) {
				//此处必须这样,否则不能得出正确的计算结果
				int progress = (int) ((count / (float) size) * 100); 
				//更新UI
				publishProgress(progress);
			}
		});
		try {
			//读取本地文件,并加入请求实体
			File file = new File(Const.PATH);
			entity.addPart("file", new FileBody(file));
			size = entity.getContentLength();
			//注意此处的差值
			Debug.log("文件:" + file.length() + "byte,请求:" + size + ",多余:" + (size - file.length()));
			//发送POST请求进行上传
			post.setEntity(entity);
			HttpResponse resp = client.execute(post);
			return resp;
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	@Override
	protected void onProgressUpdate(Integer... values) {
		//更新进度
		dialog.setProgress(values[0]);
	}
	
	@Override
	protected void onPostExecute(HttpResponse result) {
		dialog.dismiss();
	}
}

 

 Activity略,只需调用new HttpMultipartPost(this).execute();即可。

 

3,服务端

服务端接收文件时会有一个问题,网络上的资料都没有提及。我找到了原因和一个不算很好的解决方法。

详见后述。

服务端即基本的Servlet即可。

package com.th.server.web.servlet;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 接收上传的文件,并保存在本地
 * 
 * @author Ashtray
 * @createtime 2012-9-25 下午05:02:53
 * 最后修改时间 : 
 * 更新记录:
 */
public class Upload extends HttpServlet {

	private static final long serialVersionUID = 2478185387706416494L;
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		resp.setCharacterEncoding("UTF-8");
		System.out.println(req.getContentType());
		try {
			InputStream is = req.getInputStream();
			OutputStream os = new FileOutputStream("D:/" + UUID.randomUUID() + ".jpg");
			byte [] buffer = new byte [1024];
			int length = -1;
			
			//以MultipartEntity的方式接收上传文件,流中会包含描述信息,
			//去掉第一次读取的内容,即可除去,保证只接收文件内容
			boolean first = true;
			while ((length = is.read(buffer)) != -1) {
				if (!first) {
					os.write(buffer, 0, length);
				}
				first = false;
			}
			os.flush();
			is.close();
			os.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doPost(req, resp);
	}
}

 

 

如前文所述,采用MultipartEntity上传文件会产生一个问题。

会在请求中加入描述信息,导致Servlet中输出的文件会多余一部分内容,即前文中提到的一个差值。

用记事本将接收到的文件打开,会看到如下内容。

 

--aW3AQg4cMF7JA21OHAMMR9JaNlrj7nTAP
Content-Disposition: form-data; name="file"; filename="fe5bc4e1-8ecb-464c-9266-1794345a4a75"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary

 

 ??5Exif...以下均为二进制内容

    --aW3AQg4cMF7JA21OHAMMR9JaNlrj7nTAP--

 

--aW3AQg4cMF7JA21OHAMMR9JaNlrj7nTAP称为boundary,在服务端可通过req.getContentType()得到。

 

问题的关键就在于,如何去掉上传文件头部的那部分多余的内容。

本来希望由客户端提供多余内容的长度,服务端舍弃这一部分内容。

但是在实际测试中发现,并不需要如此精确,将第一次读取到的内容舍去即可。

如以上代码所示。

这样就能保证接收到文件正确,实际上还是有点小问题,文件尾部的boundary没有去掉,但是不会影响整个文件的正确性。

这个解决办法比较山寨,如有更好的办法,望不吝赐教。

 

分享到:
评论
2 楼 abcmsnet 2014-01-14  
你这个方法接收后需要手动分离头和尾的描述信息,请使用import org.apache.commons.fileupload.disk.DiskFileItemFactory;解决
1 楼 zengyan2012 2013-02-27  
  发现用你的这个工具类,会报错。。。。
能给个能正确运行的demo?  bxxasn@126.com

谢谢。。。

相关推荐

    PHP精仿百度网盘文件分享dzzoffice网盘系统源码.zip

    如果大文件中途停止上传,今后再不上传的话,系统会产生若干垃圾切片文件,本系统会自动监测,如7天后会自动删除切片垃圾文件,释放服务器空间。 4.支持ssl功能,可直接用https://形式访问。 5.强大的定时任务。本...

    文件加密软件——绿盾

    2、文件外发方案:如有内部文件需要外发,可把这些文件发送到只解密不加密的绿盾终端(通过设置登入终端的帐户类型实现),通过这些终端电脑将这些文件另存,另存后的文件即以明文的形式存在并可根据实际需要发送给...

    PHP仿百度网盘文件分享dzzoffice网盘系统源码.zip

    如果大文件中途停止上传,今后再不上传的话,系统会产生若干垃圾切片文件,本系统会自动监测,如7天后会自动删除切片垃圾文件,释放服务器空间。 4.支持ssl功能,可直接用https://形式访问。 5.强大的定时任务。...

    微动力CMS 手机PC微信三合一网站源码

    首先将下载到的安装包解压缩,然后将所有文件上传到对应网站空间根目录,接下来在可以通过浏览器输入安装向导网址开始进行微动力的安装。 以下目录需要写权限: /Runtime /App/Common/Conf /App/User/Conf /App/...

    微动力CMS(手机PC微信三合一) v1.0.2.zip

    首先将下载到的安装包解压缩,然后将所有文件上传到对应网站空间根目录,接下来在可以通过浏览器输入安装向导网址开始进行微动力的安装。   以下目录需要写权限: /Runtime /App/Common/Conf /App/User/Conf /...

    新版Android开发教程.rar

    将移动终端的评价标准从硬件向软件转变,极大的激发了软件开发者的热情。 � Android 的源代码遵循 Apache V2 软件许可,而不是通常的 GPL v2 许可。有利于商业开发。 � 具有强大的 Linux 社区的支持。 Android ...

    大米CMS v5.5.3.rar

    9:跨**台支持移动终端访问,自适应,不变形!可做手机APP等应用开发,内置JSON数据API接口 10:国内CMS首创MSYQL表万能管理模型,输入MYSQL表名即可生成一个有增删改功能的管理模型,可快速在此基础上开发出自己想要的...

    操作系统实验报告

    1、熟悉windows的编程接口,使用系统调用编程实现将参数1对应文件1.txt和参数2对应文件2.txt的内容合并到参数3对应文件zong.txt中(上传文件名为学号后5位ex0701.c)。 2、使用windows提供的命令将文件1.txt和文件2....

    IIS6.0 IIS,互联网信息服务

    四、在Vista系统中安装IIS7.0相对于早先的版本,IIS 7.0 带来了许多引人注目的新特色新功能,比如基于 Microsoft .NET Framework 的全局配置文件,可简单地通过文本编辑器或 Microsoft Visual Studio 编辑;...

Global site tag (gtag.js) - Google Analytics