Android 上传图片到JavaWeb服务器

来源:互联网 发布:魔兽世界网络问题 编辑:程序博客网 时间:2024/06/10 03:34

目录(?)[+]

  1. 一实现效果
  2. 二表单中enctype
    1.     1 表单中enctype的作用是什么
    2.     2 为什么上传文件要设置enctypemultipartform-data
  3. 三服务端fileupload接收文件
    1. 使用 fileupload 解析request
    2. 获取上传字段
    3. 更改文件名称为唯一
    4. 生成存储路径
    5. 存储文件
    6. 响应文件存储地址
  4. 四客户端OkHttp上传文件
    1. 使用OkHttp上传图片文件
    2. 上传图片Task编写
    3. OkHttp上传文件编写
    4. 通过Glide加载图片
  5. 五客户端上传用户ID及图片
    1. 客户端添加参数
    2. 2 服务器端接收
  6. 六源码及示例
  7. 七测试提示
  8. 八结语

转载请标明出处:
http://blog.csdn.net/xuehuayous/article/details/51453077
本文出自:【Kevin.zhou的博客】

前言:在上一篇博客《Android 拍照、选择图片并裁剪》中主要说明了在Android中图片裁剪的一种方式,当然我们裁剪图片的最终目的是为了上传服务器,最常用的是设置用户头像。即用户在客户端拍照或者选择图片后上传服务器,服务器返回图片在服务器的地址,然后再携带用户信息与头像地址发送请求到服务器修改用户信息。或者上传图片到服务器的时候即携带用户信息,这样一次网略请求就可以搞定了。要根据不同的需求灵活选择合适的方案。在上一篇中有朋友反映写的太复杂,其实只是进行了一些基类的抽取与封装、接口回调的解耦等,是为了使用更简洁方便,该篇博客中会尽量拆分开,方便大家二次封装。

一、实现效果


    按照之前博客风格,首先看下实现效果。

    

    有朋友可能说和上篇博客的效果一样嘛,那我岂不是忽悠大家了。不是的,这里裁剪完之后就进行了上传服务器,然后在将服务器的图片下载后设置给ImageView。

二、表单中enctype


    1. 表单中enctype的作用是什么?

 
    表单中enctype的作用是设置表单的MIME编码。默认情况,这个编码格式是 application/x-www-form-urlencoded,如果在服务器端要通过Request对象来获取相应表单域的值,则应该将enctype属性设置为application/x-www-form-urlencoded值(即默认值,可以不显示设置)。
enctype="multipart/form-data"是上传二进制数据过去,默认的application/x-www-form-urlencoded,不能用于文件上传;只有使用了multipart/form-data,才能完整的传递文件数据。 

    2. 为什么上传文件要设置enctype="multipart/form-data"?

 
    因为:设置enctype为multipart/form-data值后,不对字符编码,则数据通过二进制的形式传送到服务器端,这时如果用request是无法直接获取到相应表单的值的,而应该通过stream流对象,将传到服务器端的二进制数据解码,从而读取数据。如果要上传文件的话,是一定要将encotype设置为multipart/form-data的。

    所以,如果是在jsp中应这样来写文件上传的表单:
    
 

三、服务端fileupload接收文件

 

1. 使用 fileupload 解析request

 
[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. DiskFileItemFactory dff = new DiskFileItemFactory();  
  2. ServletFileUpload sfu = new ServletFileUpload(dff);  
  3. List<FileItem> items = sfu.parseRequest(request);  

    断点调试图如下:

    

通过断点调试图,可以看到上传文件封装到FileItem的属性,我们最关心的是fieldName、fileName、以及临时文件tempFile。

2. 获取上传字段


由于只包含一个上传文件的字段,所以可以通过以下获取上传字段:

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. // 获取上传字段  
  2. FileItem fileItem = items.get(0);  

3. 更改文件名称为唯一


由于多次上传的文件名称可能相同,为了避免上传的文件被覆盖,需要将上传的文件名做唯一处理:

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. // 更改文件名为唯一的  
  2. String filename = fileItem.getName();  
  3. if (filename != null) {  
  4.     filename = IdGenertor.generateGUID() + "." + FilenameUtils.getExtension(filename);  
  5. }  
这里的generateGUID()来产生随机的32位16进置值:
[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 生成UUID 
  3.  *  
  4.  * @return UUID 
  5.  */  
  6. public static String generateGUID() {  
  7.     return new BigInteger(165new Random()).toString(36).toUpperCase();  
  8. }  

4. 生成存储路径


我们可以通过

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. String storeDirectory = getServletContext().getRealPath("/files/images");  

来获取项目下的files/images文件夹,但是如果上传的文件非常多时以后检索该文件夹就比较费时,这里通过文件的哈希来进行二级目录划分,每个目录16个文件夹,这样最多划分出256个文件夹。

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. // 计算文件的存放目录  
  2. private String genericPath(String filename, String storeDirectory) {  
  3.     int hashCode = filename.hashCode();  
  4.     int dir1 = hashCode&0xf;  
  5.     int dir2 = (hashCode&0xf0)>>4;  
  6.   
  7.     String dir = "/"+dir1+"/"+dir2;  
  8.   
  9.     File file = new File(storeDirectory,dir);  
  10.         if(!file.exists()){  
  11.             file.mkdirs();  
  12.         }  
  13.     return dir;  
  14. }  

5. 存储文件

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. fileItem.write(new File(storeDirectory + path, filename));  
  2. String filePath = "/files/images" + path + "/" + filename;  

6. 响应文件存储地址

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. String filePath = "/files/images" + path + "/" + filename;  
  2.   
  3. response.getWriter().write(filePath);  


通过以上步骤,就将上传的文件存储到了服务器,并将文件的存储相对项目地址返回。
 

四、客户端OkHttp上传文件

 

1、监听裁剪结果

 
在上一篇Android 拍照、选择图片并裁剪中,我们通过设置裁剪图片的监听获取到裁剪后的图片:

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. // 设置裁剪图片结果监听  
  2. setOnPictureSelectedListener(new OnPictureSelectedListener() {  
  3.     @Override  
  4.     public void onPictureSelected(Uri fileUri, Bitmap bitmap) {  
  5.         mPictureIv.setImageBitmap(bitmap);  
  6.   
  7.         String filePath = fileUri.getEncodedPath();  
  8.         String imagePath = Uri.decode(filePath);  
  9.         Toast.makeText(mContext, "图片已经保存到:" + imagePath, Toast.LENGTH_LONG).show();  
  10.     }  
  11. });  

2. 使用OkHttp上传图片文件

 
我们将裁剪图片结果的监听回调修改如下,即裁剪完成后就上传图片到服务器

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. // 设置裁剪图片结果监听  
  2. setOnPictureSelectedListener(new OnPictureSelectedListener() {  
  3.     @Override  
  4.     public void onPictureSelected(Uri fileUri, Bitmap bitmap) {  
  5.         // mPictureIv.setImageBitmap(bitmap);  
  6.   
  7.         String filePath = fileUri.getEncodedPath();  
  8.         final String imagePath = Uri.decode(filePath);  
  9.   
  10.         uploadImage(imagePath);  
  11.   
  12.     }  
  13. });  

3. 上传图片Task编写

 
由于OkHttp虽然有异步网络访问,但是回调还是处在子线程不能修改界面,这里编写一个NetworkTask来进行子线程到主线程的链接。

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 访问网络AsyncTask,访问网络在子线程进行并返回主线程通知访问的结果 
  3.  */  
  4. class NetworkTask extends AsyncTask<String, Integer, String> {  
  5.   
  6.     @Override  
  7.     protected void onPreExecute() {  
  8.         super.onPreExecute();  
  9.     }  
  10.   
  11.     @Override  
  12.     protected String doInBackground(String... params) {  
  13.         return doPost(params[0]);  
  14.     }  
  15.   
  16.     @Override  
  17.     protected void onPostExecute(String result) {  
  18.         Log.i(TAG, "服务器响应" + result);  
  19.     }  
  20. }  

4. OkHttp上传文件编写

 
在NetworkTask中,我们可以看到OkHttp上传的时候只有给它一个要上传文件的路径就可以了,然后返回上传后服务器返回的路径。
OkHttp提供了MultipartBody来进行文件的上传:

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. private String doPost(String imagePath) {  
  2.     OkHttpClient mOkHttpClient = new OkHttpClient();  
  3.   
  4.     String result = "error";  
  5.     MultipartBody.Builder builder = new MultipartBody.Builder();  
  6.     builder.addFormDataPart("image", imagePath,  
  7.             RequestBody.create(MediaType.parse("image/jpeg"), new File(imagePath)));  
  8.     RequestBody requestBody = builder.build();  
  9.     Request.Builder reqBuilder = new Request.Builder();  
  10.     Request request = reqBuilder  
  11.             .url(Constant.BASE_URL + "/uploadimage")  
  12.             .post(requestBody)  
  13.             .build();  
  14.   
  15.     Log.d(TAG, "请求地址 " + Constant.BASE_URL + "/uploadimage");  
  16.     try{  
  17.         Response response = mOkHttpClient.newCall(request).execute();  
  18.         Log.d(TAG, "响应码 " + response.code());  
  19.         if (response.isSuccessful()) {  
  20.             String resultValue = response.body().string();  
  21.             Log.d(TAG, "响应体 " + resultValue);  
  22.             return resultValue;  
  23.         }  
  24.     } catch (Exception e) {  
  25.         e.printStackTrace();  
  26.     }  
  27.     return result;  
  28. }  

5. 通过Glide加载图片

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 访问网络AsyncTask,访问网络在子线程进行并返回主线程通知访问的结果 
  3.  */  
  4. class NetworkTask extends AsyncTask<String, Integer, String> {  
  5.   
  6.     @Override  
  7.     protected void onPreExecute() {  
  8.         super.onPreExecute();  
  9.     }  
  10.   
  11.     @Override  
  12.     protected String doInBackground(String... params) {  
  13.         return doPost(params[0]);  
  14.     }  
  15.   
  16.     @Override  
  17.     protected void onPostExecute(String result) {  
  18.         if(!"error".equals(result)) {  
  19.             Log.i(TAG, "图片地址 " + Constant.BASE_URL + result);  
  20.             Glide.with(mContext)  
  21.                     .load(Constant.BASE_URL + result)  
  22.                     .into(mPictureIv);  
  23.         }  
  24.     }  
  25. }  

    OK,图片加载出来了,来看下我们设置的Log日志:

    


五、客户端上传用户ID及图片


    以下作为一个demo,演示客户端上传图片的时候并携带用户的ID,这样传递到服务器之后就可以根据用户的ID去修改用户头像了,服务器端一般是处理用户数据库的业务,这里只是简单的打印处理。

 

1. 客户端添加参数

 
客户端只需要添加一个用户Id的字段就可以了

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. // 这里演示添加用户ID  
  2. builder.addFormDataPart("userId""20160519142605");  


整体代码如下:

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. private String doPost(String imagePath) {  
  2.     OkHttpClient mOkHttpClient = new OkHttpClient();  
  3.   
  4.     String result = "error";  
  5.     MultipartBody.Builder builder = new MultipartBody.Builder();  
  6.     // 这里演示添加用户ID  
  7.     builder.addFormDataPart("userId""20160519142605");  
  8.     builder.addFormDataPart("image", imagePath,  
  9.     RequestBody.create(MediaType.parse("image/jpeg"), new File(imagePath)));  
  10.   
  11.     RequestBody requestBody = builder.build();  
  12.     Request.Builder reqBuilder = new Request.Builder();  
  13.     Request request = reqBuilder  
  14.             .url(Constant.BASE_URL + "/uploadimage")  
  15.             .post(requestBody)  
  16.             .build();  
  17.   
  18.     Log.d(TAG, "请求地址 " + Constant.BASE_URL + "/uploadimage");  
  19.     try{  
  20.         Response response = mOkHttpClient.newCall(request).execute();  
  21.         Log.d(TAG, "响应码 " + response.code());  
  22.         if (response.isSuccessful()) {  
  23.             String resultValue = response.body().string();  
  24.             Log.d(TAG, "响应体 " + resultValue);  
  25.             return resultValue;  
  26.         }  
  27.     } catch (Exception e) {  
  28.         e.printStackTrace();  
  29.     }  
  30.     return result;  
  31. }  


2. 服务器端接收

[java] view plain copy
print?在CODE上查看代码片派生到我的代码片
  1. // 修改用户的图片  
  2. private void changeUserImage(HttpServletRequest request, HttpServletResponse response)   
  3.         throws ServletException, IOException {  
  4.     String message = "";  
  5.     try{  
  6.         DiskFileItemFactory dff = new DiskFileItemFactory();  
  7.         ServletFileUpload sfu = new ServletFileUpload(dff);  
  8.         List<FileItem> items = sfu.parseRequest(request);  
  9.         for(FileItem item:items){  
  10.             if(item.isFormField()){  
  11.                 //普通表单  
  12.                 String fieldName = item.getFieldName();  
  13.                 String fieldValue = item.getString();  
  14.                 System.out.println("name="+fieldName + ", value="+ fieldValue);  
  15.             } else {// 获取上传字段  
  16.                 // 更改文件名为唯一的  
  17.                 String filename = item.getName();  
  18.                 if (filename != null) {  
  19.                     filename = IdGenertor.generateGUID() + "." + FilenameUtils.getExtension(filename);  
  20.                 }  
  21.                 // 生成存储路径  
  22.                 String storeDirectory = getServletContext().getRealPath("/files/images");  
  23.                 File file = new File(storeDirectory);  
  24.                 if (!file.exists()) {  
  25.                     file.mkdir();  
  26.                 }  
  27.                 String path = genericPath(filename, storeDirectory);  
  28.                 // 处理文件的上传  
  29.                 try {  
  30.                     item.write(new File(storeDirectory + path, filename));  
  31.   
  32.                     String filePath = "/files/images" + path + "/" + filename;  
  33.                     System.out.println("filePath="+filePath);  
  34.                     message = filePath;  
  35.                 } catch (Exception e) {  
  36.                     message = "上传图片失败";  
  37.                 }  
  38.             }  
  39.         }  
  40.     } catch (Exception e) {  
  41.         e.printStackTrace();  
  42.         message = "上传图片失败";  
  43.     } finally {  
  44.         response.getWriter().write(message);  
  45.     }  
  46. }  


    当然这里只是演示如何接收用户ID以及图片,具体的业务要具体实现。控制台打印结果如下:

 

    


六、源码及示例


   ImageUpload Android端 + 服务器端代码

 

七、测试提示


    在自己测试的时候,可以选用本地的tomcat,然后电脑和手机在同一网段内就可以了(连接同一个wifi),ip地址查看方法:

 

    


八、结语

 

    通过上一篇博客《Android 拍照、选择图片并裁剪》以及该篇《Android 上传图片到JavaWeb服务器》,相信朋友们对于裁剪图片并上传服务器有了大致的了解,当然实现方式只是一种,主要是理清思路并灵活运用。

0 0
原创粉丝点击