1、前言
作为初学者来说,想必对上传的数据格式肯定比较模糊,一般传递参数给后台,常用的是使用基本数据类型拼装成json格式。头像上传也可以使用String类型进行上传(极客学院有相关的视频,可以自行了解),本篇文章所讲的是使用文件的格式进行上传,也就是File类型。主要是实际的项目开发中,为了与web端的头像上传格式一致,所以一般使用File类型的还是比较多。之后会比较多的提到文件上传,这里就是指图片的上传,只是采用的是文件上传的格式。
2、效果图
3、实现步骤
1、首先我们得获取相册中或者拍照所得到的照片,这个可以通过系统中的Intent获得
2、 将得到的图片进行剪裁、压缩,然后根据图片路径转换成File文件
3、 将File文件(这里指需要上传的图片)上传到服务器后台
<span style="background-color: rgb(153, 255, 153);"><span style="font-family: Arial, Helvetica, sans-serif;">注:这里上传到服务器的代码会比较复杂,需要拼接相应的Http协议。做过j2</span><span style="font-family: Arial, Helvetica, sans-serif;">ee的童鞋都知道,在jsp或html中上传文件非常简单,</span><span style="font-size: 14px; font-family: Arial, Helvetica, sans-serif;">一个form表单就可以搞定:</span></span>
<span style="white-space:pre"> </span><form action="/web/ManageServlet" method="post" enctype="multipart/form-data">
文件标题:<input name="title" type="text"><br/>
文件: <input type="file" name="fileName"/>
<input type="submit" value=" 提 交 "/>
</form>
之所以这么简便是因为浏览器会帮助我们处理相应的http协议,而在Android客户端中则需要我们自己去实现。下面我们先看一段浏览器向web服务器发送的http协议:
之后我们就会面向这段Htpp协议进行文件(图片)的上传
4、代码实现:
打开相机或者打开手机相册:
<span style="white-space:pre"> </span>switch (position) {
case 0:
// 照相机
intent = new Intent("android.media.action.IMAGE_CAPTURE");
// 判断存储卡是否可以用,可用进行存储
if (hasSdcard()) {
intent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.fromFile(new File(Environment
.getExternalStorageDirectory(), PHOTO_FILE_NAME)));
}
startActivityForResult(intent, PHOTO_REQUEST_CAMERA);
break;
case 1:
// 图库选择
// 激活系统图库,选择一张图片
intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, PHOTO_REQUEST_GALLERY);
break;
case 2:
//取消
break;
}
重写onActivityResult方法:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PHOTO_REQUEST_GALLERY) {//相册
if (data != null) {
// 得到图片的全路径
Uri uri = data.getData();
crop(uri);//剪切图片
}
} else if (requestCode == PHOTO_REQUEST_CAMERA) {//相机
if (hasSdcard()) {
tempFile = new File(Environment.getExternalStorageDirectory(),
PHOTO_FILE_NAME);
crop(Uri.fromFile(tempFile));//剪切图片
} else {
showToast("未找到存储卡,无法存储照片!");
}
super.onActivityResult(requestCode, resultCode, data);
}
}
使用系统剪裁图片的方法:
/**
* 剪切图片
*
*/
private void crop(Uri uri) {
// 裁剪图片意图
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
intent.putExtra("crop", "true");
// 裁剪框的比例,1:1
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// 裁剪后输出图片的尺寸大小
intent.putExtra("outputX", 250);
intent.putExtra("outputY", 250);
// 图片格式
intent.putExtra("outputFormat", "PNG");
intent.putExtra("noFaceDetection", true);// 取消人脸识别
intent.putExtra("return-data", true);// true:不返回uri,false:返回uri
startActivityForResult(intent, PHOTO_REQUEST_CUT);//同样的在onActivityResult中处理剪裁好的图片
}
图片剪裁完成之后就可以把图片显示到图片控件中,并且在剪裁完成的回调中将图片上传到服务器
完整的onActivityResult:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//修改昵称返回
if (requestCode == REQ_CODE_UPDATE_NAME && resultCode == RESULT_OK) {
tvUserName.setText(data.getStringExtra("name"));
}
if (requestCode == PHOTO_REQUEST_GALLERY) {//相册
if (data != null) {
// 得到图片的全路径
Uri uri = data.getData();
crop(uri);//剪切图片
}
} else if (requestCode == PHOTO_REQUEST_CAMERA) {//相机
if (hasSdcard()) {
tempFile = new File(Environment.getExternalStorageDirectory(),
PHOTO_FILE_NAME);
crop(Uri.fromFile(tempFile));//剪切图片
} else {
showToast("未找到存储卡,无法存储照片!");
}
} else if (requestCode == PHOTO_REQUEST_CUT) {//图片剪裁完成
try {
bitmap = data.getParcelableExtra("data");
sriUserPhoto.setImageBitmap(bitmap);//确定图片之后将显示在圆形控件上
//pictureCutUtil是一个图片压缩的工具类
String filePath = pictureCutUtil.cutPictureQuality(bitmap, Constant.PACKAGE_PICTURE);
final File file = new File(filePath);
//此处是需要传递到后台的参数
int uid = getPreferenceHelper().getInt(Constant.DUOBAO_USER_UID, -1);
final Map<String, String> map = new HashMap<String, String>();
map.put("uid", uid + "");
//开启子线程
new Thread(new Runnable() {
@Override
public void run() {
try {
//文件上传, 参数解释见具体方法
uploadForm(map, "Filedata", file, null, HttpConstant.CLIENT_HOME_USERPHOTOUP);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} catch (Exception e) {
e.printStackTrace();
}
}
super.onActivityResult(requestCode, resultCode, data);
}
图片压缩工具类:
/**
* 图片压缩
*
* @author lihaotian
*
*/
public class PictureCutUtil {
private Context context;
/**
* 构造器
*
* @param context
*/
public PictureCutUtil(Context context) {
this.context = context;
}
/**
* 计算图片的缩放值
*
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
private int cutBySize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
/**
* 根据路径获得图片并压缩返回bitmap
*
* @param filePath
* @return
*/
public Bitmap cutPictureSize(String filePath) {
Display mDisplay = ((Activity) context).getWindowManager().getDefaultDisplay();
int width = mDisplay.getWidth();
int height = mDisplay.getHeight();
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
options.inSampleSize = cutBySize(options, width, height);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filePath, options);
}
/**
* 存储图片
*
* @param bitmap
* @param savePackage
* @return
*/
public String cutPictureQuality(Bitmap bitmap, String savePackage) {
String fileName = UUID.randomUUID().toString().replace("-","") + ".png";
String filePath = Environment.getExternalStorageDirectory() + File.separator + savePackage;
// 判断文件夹存在
File file = new File(filePath);
if (file != null && !file.exists()) {
file.mkdirs();
}
try {
// 第一次压缩
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
FileOutputStream fos = new FileOutputStream(new File(filePath, fileName));
int options = 100;
// 如果大于150kb则再次压缩,最多压缩三次
while (baos.toByteArray().length / 1024 > 150 && options != 10) {
// 清空baos
baos.reset();
// 这里压缩options%,把压缩后的数据存放到baos中
bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);
options -= 30;
}
fos.write(baos.toByteArray());
fos.close();
baos.close();
} catch (Exception e) {
}
return filePath + File.separator + fileName;
}
}
具体的图片上传(关键),本篇介绍两种上传的方法,第一种是HttpURLConnection,上面上传文件调用的就是此方法:
/**
*android上传文件到服务器
* @param params:请求参数
* @param fileFormName:文件名称
* @param uploadFile:上传的文件
* @param newFileName:可不写
* @param urlStr:后台地址
* @throws IOException
*/
public void uploadForm(Map<String, String> params, String fileFormName,
File uploadFile, String newFileName, String urlStr)
throws IOException {
if (newFileName == null || newFileName.trim().equals("")) {
newFileName = uploadFile.getName();
}
StringBuilder sb = new StringBuilder();
/**
* 普通的表单数据
*/
if (params != null)
for (String key : params.keySet()) {
sb.append("--" + BOUNDARY + "\r\n");
sb.append("Content-Disposition: form-data; name=\"" + key
+ "\"" + "\r\n");
sb.append("\r\n");
sb.append(params.get(key) + "\r\n");
}
/**
* 上传文件的头
*/
sb.append("--" + BOUNDARY + "\r\n");
sb.append("Content-Disposition: form-data; name=\"" + fileFormName
+ "\"; filename=\"" + newFileName + "\"" + "\r\n");
sb.append("Content-Type: image/png" + "\r\n");// 如果服务器端有文件类型的校验,必须明确指定ContentType
sb.append("\r\n");
byte[] headerInfo = sb.toString().getBytes("UTF-8");
byte[] endInfo = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8");
System.out.println(sb.toString());
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY);
conn.setRequestProperty("Content-Length", String
.valueOf(headerInfo.length + uploadFile.length()
+ endInfo.length));
conn.setDoOutput(true);
OutputStream out = conn.getOutputStream();
InputStream in = new FileInputStream(uploadFile);
out.write(headerInfo);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) != -1)
out.write(buf, 0, len);
out.write(endInfo);
in.close();
out.close();
if (conn.getResponseCode() == 200) {
System.out.println("上传成功");
}
}
第二种使用socket:(可作为了解) 网上有人说HttpURLConnection有一定缓存,容易造成内存溢出,具体的没有深究,就当我扯淡~~~
public class SocketHttpRequester {
/**
* 直接通过HTTP协议提交数据到服务器,实现如下面表单提交功能:
* <FORM METHOD=POST ACTION="http://192.168.0.200:8080/ssi/fileload/test.do" enctype="multipart/form-data">
<INPUT TYPE="text" NAME="name">
<INPUT TYPE="text" NAME="id">
<input type="file" name="imagefile"/>
<input type="file" name="zip"/>
</FORM>
* @param path 上传路径(注:避免使用localhost或127.0.0.1这样的路径测试,因为它会指向手机模拟器,你可以使用http://www.itcast.cn或http://192.168.1.10:8080这样的路径测试)
* @param params 请求参数 key为参数名,value为参数值
* @param files 上传文件
*/
public static boolean post(String path, Map<String, String> params, FormFile[] files) throws Exception{
final String BOUNDARY = "---------------------------7da2137580612"; //数据分隔线
final String endline = "--" + BOUNDARY + "--\r\n";//数据结束标志
int fileDataLength = 0;
for(FormFile uploadFile : files){//得到文件类型数据的总长度
StringBuilder fileExplain = new StringBuilder();
fileExplain.append("--");
fileExplain.append(BOUNDARY);
fileExplain.append("\r\n");
fileExplain.append("Content-Disposition: form-data;name=\""+ uploadFile.getParameterName()+"\";filename=\""+ uploadFile.getFilname() + "\"\r\n");
fileExplain.append("Content-Type: "+ uploadFile.getContentType()+"\r\n\r\n");
fileDataLength += fileExplain.length();
if(uploadFile.getInStream()!=null){
fileDataLength += uploadFile.getFile().length();
}else{
fileDataLength += uploadFile.getData().length;
}
fileDataLength += "\r\n".length();
}
StringBuilder textEntity = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {//构造文本类型参数的实体数据
textEntity.append("--");
textEntity.append(BOUNDARY);
textEntity.append("\r\n");
textEntity.append("Content-Disposition: form-data; name=\""+ entry.getKey() + "\"\r\n\r\n");
textEntity.append(entry.getValue());
textEntity.append("\r\n");
}
//计算传输给服务器的实体数据总长度
int dataLength = textEntity.toString().getBytes().length + fileDataLength + endline.getBytes().length;
URL url = new URL(path);
int port = url.getPort()==-1 ? 80 : url.getPort();
Socket socket = new Socket(InetAddress.getByName(url.getHost()), port);
OutputStream outStream = socket.getOutputStream();
//下面完成HTTP请求头的发送
String requestmethod = "POST "+ url.getPath()+" HTTP/1.1\r\n";
outStream.write(requestmethod.getBytes());
String accept = "Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*\r\n";
outStream.write(accept.getBytes());
String language = "Accept-Language: zh-CN\r\n";
outStream.write(language.getBytes());
String contenttype = "Content-Type: multipart/form-data; boundary="+ BOUNDARY+ "\r\n";
outStream.write(contenttype.getBytes());
String contentlength = "Content-Length: "+ dataLength + "\r\n";
outStream.write(contentlength.getBytes());
String alive = "Connection: Keep-Alive\r\n";
outStream.write(alive.getBytes());
String host = "Host: "+ url.getHost() +":"+ port +"\r\n";
outStream.write(host.getBytes());
//写完HTTP请求头后根据HTTP协议再写一个回车换行
outStream.write("\r\n".getBytes());
//把所有文本类型的实体数据发送出来
outStream.write(textEntity.toString().getBytes());
//把所有文件类型的实体数据发送出来
for(FormFile uploadFile : files){
StringBuilder fileEntity = new StringBuilder();
fileEntity.append("--");
fileEntity.append(BOUNDARY);
fileEntity.append("\r\n");
fileEntity.append("Content-Disposition: form-data;name=\""+ uploadFile.getParameterName()+"\";filename=\""+ uploadFile.getFilname() + "\"\r\n");
fileEntity.append("Content-Type: "+ uploadFile.getContentType()+"\r\n\r\n");
outStream.write(fileEntity.toString().getBytes());
if(uploadFile.getInStream()!=null){
byte[] buffer = new byte[1024];
int len = 0;
while((len = uploadFile.getInStream().read(buffer, 0, 1024))!=-1){
outStream.write(buffer, 0, len);
}
uploadFile.getInStream().close();
}else{
outStream.write(uploadFile.getData(), 0, uploadFile.getData().length);
}
outStream.write("\r\n".getBytes());
}
//下面发送数据结束标志,表示数据已经结束
outStream.write(endline.getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// JsonUtils.parseObject(convertStreamToString(socket.getInputStream()),);
if(reader.readLine().indexOf("200")==-1){//读取web服务器返回的数据,判断请求码是否为200,如果不是200,代表请求失败
return false;
}
outStream.flush();
outStream.close();
reader.close();
socket.close();
return true;
}
/**
* 临时
* @param is
* @return
*/
public static String convertStreamToString(InputStream is) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
/**
* 提交数据到服务器
* @param path 上传路径(注:避免使用localhost或127.0.0.1这样的路径测试,因为它会指向手机模拟器,你可以使用http://www.itcast.cn或http://192.168.1.10:8080这样的路径测试)
* @param params 请求参数 key为参数名,value为参数值
* @param file 上传文件
*/
public static boolean post(String path, Map<String, String> params, FormFile file) throws Exception{
return post(path, params, new FormFile[]{file});
}
/**
* 提交数据到服务器
* @param path 上传路径(注:避免使用localhost或127.0.0.1这样的路径测试,因为它会指向手机模拟器,你可以使用http://www.itcast.cn或http://192.168.1.10:8080这样的路径测试)
* @param params 请求参数 key为参数名,value为参数值
* @param encode 编码
*/
public static byte[] postFromHttpClient(String path, Map<String, String> params, String encode) throws Exception{
List<NameValuePair> formparams = new ArrayList<NameValuePair>();//用于存放请求参数
for(Map.Entry<String, String> entry : params.entrySet()){
formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, encode);
HttpPost httppost = new HttpPost(path);
httppost.setEntity(entity);
HttpClient httpclient = new DefaultHttpClient();//看作是浏览器
HttpResponse response = httpclient.execute(httppost);//发送post请求
return readStream(response.getEntity().getContent());
}
/**
* 发送请求
* @param path 请求路径
* @param params 请求参数 key为参数名称 value为参数值
* @param encode 请求参数的编码
*/
public static byte[] post(String path, Map<String, String> params, String encode) throws Exception{
//String params = "method=save&name="+ URLEncoder.encode("老毕", "UTF-8")+ "&age=28&";//需要发送的参数
StringBuilder parambuilder = new StringBuilder("");
if(params!=null && !params.isEmpty()){
for(Map.Entry<String, String> entry : params.entrySet()){
parambuilder.append(entry.getKey()).append("=")
.append(URLEncoder.encode(entry.getValue(), encode)).append("&");
}
parambuilder.deleteCharAt(parambuilder.length()-1);
}
byte[] data = parambuilder.toString().getBytes();
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setDoOutput(true);//允许对外发送请求参数
conn.setUseCaches(false);//不进行缓存
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("POST");
//下面设置http请求头
conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", String.valueOf(data.length));
conn.setRequestProperty("Connection", "Keep-Alive");
//发送参数
DataOutputStream outStream = new DataOutputStream(conn.getOutputStream());
outStream.write(data);//把参数发送出去
outStream.flush();
outStream.close();
if(conn.getResponseCode()==200){
return readStream(conn.getInputStream());
}
return null;
}
/**
* 读取流
* @param inStream
* @return 字节数组
* @throws Exception
*/
public static byte[] readStream(InputStream inStream) throws Exception{
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while( (len=inStream.read(buffer)) != -1){
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
return outSteam.toByteArray();
}
}
/**
* 上传文件
*/
public class FormFile {
/* 上传文件的数据 */
private byte[] data;
private InputStream inStream;
private File file;
/* 文件名称 */
private String filname;
/* 请求参数名称*/
private String parameterName;
/* 内容类型 */
private String contentType = "application/octet-stream";
public FormFile(String filname, byte[] data, String parameterName, String contentType) {
this.data = data;
this.filname = filname;
this.parameterName = parameterName;
if(contentType!=null) this.contentType = contentType;
}
public FormFile(File file, String parameterName, String contentType) {
this.filname = file.getName();
this.parameterName = parameterName;
this.file = file;
try {
this.inStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if(contentType!=null) this.contentType = contentType;
}
public File getFile() {
return file;
}
public InputStream getInStream() {
return inStream;
}
public byte[] getData() {
return data;
}
public String getFilname() {
return filname;
}
public void setFilname(String filname) {
this.filname = filname;
}
public String getParameterName() {
return parameterName;
}
public void setParameterName(String parameterName) {
this.parameterName = parameterName;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
}
其实就这么简单,完工。。