您的位置:首页 > 移动开发 > Android开发

Android 上传图片到JavaWeb服务器

2016-11-27 09:13 477 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[+]
一实现效果
二表单中enctype
    1 表单中enctype的作用是什么
    2 为什么上传文件要设置enctypemultipartform-data

三服务端fileupload接收文件
使用 fileupload 解析request
获取上传字段
更改文件名称为唯一
生成存储路径
存储文件
响应文件存储地址

四客户端OkHttp上传文件
使用OkHttp上传图片文件
上传图片Task编写
OkHttp上传文件编写
通过Glide加载图片

五客户端上传用户ID及图片
客户端添加参数
2 服务器端接收

六源码及示例
七测试提示
八结语

转载请标明出处:
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?





DiskFileItemFactory dff = new DiskFileItemFactory();  
ServletFileUpload sfu = new ServletFileUpload(dff);  
List<FileItem> items = sfu.parseRequest(request);  



DiskFileItemFactory dff = new DiskFileItemFactory();
ServletFileUpload sfu = new ServletFileUpload(dff);
List<FileItem> items = sfu.parseRequest(request);


    断点调试图如下:

    


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

2. 获取上传字段

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

[java]
view plain
copy

print?





// 获取上传字段  
FileItem fileItem = items.get(0);  



// 获取上传字段
FileItem fileItem = items.get(0);


3. 更改文件名称为唯一

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

[java]
view plain
copy

print?





// 更改文件名为唯一的  
String filename = fileItem.getName();  
if (filename != null) {  
    filename = IdGenertor.generateGUID() + "." + FilenameUtils.getExtension(filename);  
}  



// 更改文件名为唯一的
String filename = fileItem.getName();
if (filename != null) {
filename = IdGenertor.generateGUID() + "." + FilenameUtils.getExtension(filename);
}
这里的generateGUID()来产生随机的32位16进置值:

[java]
view plain
copy

print?





/** 
 * 生成UUID 
 *  
 * @return UUID 
 */  
public static String generateGUID() {  
    return new BigInteger(165, new Random()).toString(36).toUpperCase();  
}  



/**
* 生成UUID
*
* @return UUID
*/
public static String generateGUID() {
return new BigInteger(165, new Random()).toString(36).toUpperCase();
}

4. 生成存储路径

我们可以通过

[java]
view plain
copy

print?





String storeDirectory = getServletContext().getRealPath("/files/images");  



String storeDirectory = getServletContext().getRealPath("/files/images");

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

[java]
view plain
copy

print?





// 计算文件的存放目录  
private String genericPath(String filename, String storeDirectory) {  
    int hashCode = filename.hashCode();  
    int dir1 = hashCode&0xf;  
    int dir2 = (hashCode&0xf0)>>4;  
  
    String dir = "/"+dir1+"/"+dir2;  
  
    File file = new File(storeDirectory,dir);  
        if(!file.exists()){  
            file.mkdirs();  
        }  
    return dir;  
}  



// 计算文件的存放目录
private String genericPath(String filename, String storeDirectory) {
int hashCode = filename.hashCode();
int dir1 = hashCode&0xf;
int dir2 = (hashCode&0xf0)>>4;

String dir = "/"+dir1+"/"+dir2;

File file = new File(storeDirectory,dir);
if(!file.exists()){
file.mkdirs();
}
return dir;
}


5. 存储文件

[java]
view plain
copy

print?





fileItem.write(new File(storeDirectory + path, filename));  
String filePath = "/files/images" + path + "/" + filename;  



fileItem.write(new File(storeDirectory + path, filename));
String filePath = "/files/images" + path + "/" + filename;


6. 响应文件存储地址

[java]
view plain
copy

print?





String filePath = "/files/images" + path + "/" + filename;  
  
response.getWriter().write(filePath);  



String filePath = "/files/images" + path + "/" + filename;

response.getWriter().write(filePath);


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

四、客户端OkHttp上传文件

 
1、监听裁剪结果

 

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

[java]
view plain
copy

print?





// 设置裁剪图片结果监听  
setOnPictureSelectedListener(new OnPictureSelectedListener() {  
    @Override  
    public void onPictureSelected(Uri fileUri, Bitmap bitmap) {  
        mPictureIv.setImageBitmap(bitmap);  
  
        String filePath = fileUri.getEncodedPath();  
        String imagePath = Uri.decode(filePath);  
        Toast.makeText(mContext, "图片已经保存到:" + imagePath, Toast.LENGTH_LONG).show();  
    }  
});  



// 设置裁剪图片结果监听
setOnPictureSelectedListener(new OnPictureSelectedListener() {
@Override
public void onPictureSelected(Uri fileUri, Bitmap bitmap) {
mPictureIv.setImageBitmap(bitmap);

String filePath = fileUri.getEncodedPath();
String imagePath = Uri.decode(filePath);
Toast.makeText(mContext, "图片已经保存到:" + imagePath, Toast.LENGTH_LONG).show();
}
});


2. 使用OkHttp上传图片文件

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

[java]
view plain
copy

print?





// 设置裁剪图片结果监听  
setOnPictureSelectedListener(new OnPictureSelectedListener() {  
    @Override  
    public void onPictureSelected(Uri fileUri, Bitmap bitmap) {  
        // mPictureIv.setImageBitmap(bitmap);  
  
        String filePath = fileUri.getEncodedPath();  
        final String imagePath = Uri.decode(filePath);  
  
        uploadImage(imagePath);  
  
    }  
});  



// 设置裁剪图片结果监听
setOnPictureSelectedListener(new OnPictureSelectedListener() {
@Override
public void onPictureSelected(Uri fileUri, Bitmap bitmap) {
// mPictureIv.setImageBitmap(bitmap);

String filePath = fileUri.getEncodedPath();
final String imagePath = Uri.decode(filePath);

uploadImage(imagePath);

}
});


3. 上传图片Task编写

 

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

[java]
view plain
copy

print?





/** 
 * 访问网络AsyncTask,访问网络在子线程进行并返回主线程通知访问的结果 
 */  
class NetworkTask extends AsyncTask<String, Integer, String> {  
  
    @Override  
    protected void onPreExecute() {  
        super.onPreExecute();  
    }  
  
    @Override  
    protected String doInBackground(String... params) {  
        return doPost(params[0]);  
    }  
  
    @Override  
    protected void onPostExecute(String result) {  
        Log.i(TAG, "服务器响应" + result);  
    }  
}  



/**
* 访问网络AsyncTask,访问网络在子线程进行并返回主线程通知访问的结果
*/
class NetworkTask extends AsyncTask<String, Integer, String> {

@Override
protected void onPreExecute() {
super.onPreExecute();
}

@Override
protected String doInBackground(String... params) {
return doPost(params[0]);
}

@Override
protected void onPostExecute(String result) {
Log.i(TAG, "服务器响应" + result);
}
}

4. OkHttp上传文件编写

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

[java]
view plain
copy

print?





private String doPost(String imagePath) {  
    OkHttpClient mOkHttpClient = new OkHttpClient();  
  
    String result = "error";  
    MultipartBody.Builder builder = new MultipartBody.Builder();  
    builder.addFormDataPart("image", imagePath,  
            RequestBody.create(MediaType.parse("image/jpeg"), new File(imagePath)));  
    RequestBody requestBody = builder.build();  
    Request.Builder reqBuilder = new Request.Builder();  
    Request request = reqBuilder  
            .url(Constant.BASE_URL + "/uploadimage")  
            .post(requestBody)  
            .build();  
  
    Log.d(TAG, "请求地址 " + Constant.BASE_URL + "/uploadimage");  
    try{  
        Response response = mOkHttpClient.newCall(request).execute();  
        Log.d(TAG, "响应码 " + response.code());  
        if (response.isSuccessful()) {  
            String resultValue = response.body().string();  
            Log.d(TAG, "响应体 " + resultValue);  
            return resultValue;  
        }  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
    return result;  
}  



private String doPost(String imagePath) {
OkHttpClient mOkHttpClient = new OkHttpClient();

String result = "error";
MultipartBody.Builder builder = new MultipartBody.Builder();
builder.addFormDataPart("image", imagePath,
RequestBody.create(MediaType.parse("image/jpeg"), new File(imagePath)));
RequestBody requestBody = builder.build();
Request.Builder reqBuilder = new Request.Builder();
Request request = reqBuilder
.url(Constant.BASE_URL + "/uploadimage")
.post(requestBody)
.build();

Log.d(TAG, "请求地址 " + Constant.BASE_URL + "/uploadimage");
try{
Response response = mOkHttpClient.newCall(request).execute();
Log.d(TAG, "响应码 " + response.code());
if (response.isSuccessful()) {
String resultValue = response.body().string();
Log.d(TAG, "响应体 " + resultValue);
return resultValue;
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}


5. 通过Glide加载图片

[java]
view plain
copy

print?





/** 
 * 访问网络AsyncTask,访问网络在子线程进行并返回主线程通知访问的结果 
 */  
class NetworkTask extends AsyncTask<String, Integer, String> {  
  
    @Override  
    protected void onPreExecute() {  
        super.onPreExecute();  
    }  
  
    @Override  
    protected String doInBackground(String... params) {  
        return doPost(params[0]);  
    }  
  
    @Override  
    protected void onPostExecute(String result) {  
        if(!"error".equals(result)) {  
            Log.i(TAG, "图片地址 " + Constant.BASE_URL + result);  
            Glide.with(mContext)  
                    .load(Constant.BASE_URL + result)  
                    .into(mPictureIv);  
        }  
    }  
}  



/**
* 访问网络AsyncTask,访问网络在子线程进行并返回主线程通知访问的结果
*/
class NetworkTask extends AsyncTask<String, Integer, String> {

@Override
protected void onPreExecute() {
super.onPreExecute();
}

@Override
protected String doInBackground(String... params) {
return doPost(params[0]);
}

@Override
protected void onPostExecute(String result) {
if(!"error".equals(result)) {
Log.i(TAG, "图片地址 " + Constant.BASE_URL + result);
Glide.with(mContext)
.load(Constant.BASE_URL + result)
.into(mPictureIv);
}
}
}


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

    


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

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

1. 客户端添加参数

 

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

[java]
view plain
copy

print?





// 这里演示添加用户ID  
builder.addFormDataPart("userId", "20160519142605");  



// 这里演示添加用户ID
builder.addFormDataPart("userId", "20160519142605");


整体代码如下:

[java]
view plain
copy

print?





private String doPost(String imagePath) {  
    OkHttpClient mOkHttpClient = new OkHttpClient();  
  
    String result = "error";  
    MultipartBody.Builder builder = new MultipartBody.Builder();  
    // 这里演示添加用户ID  
    builder.addFormDataPart("userId", "20160519142605");  
    builder.addFormDataPart("image", imagePath,  
    RequestBody.create(MediaType.parse("image/jpeg"), new File(imagePath)));  
  
    RequestBody requestBody = builder.build();  
    Request.Builder reqBuilder = new Request.Builder();  
    Request request = reqBuilder  
            .url(Constant.BASE_URL + "/uploadimage")  
            .post(requestBody)  
            .build();  
  
    Log.d(TAG, "请求地址 " + Constant.BASE_URL + "/uploadimage");  
    try{  
        Response response = mOkHttpClient.newCall(request).execute();  
        Log.d(TAG, "响应码 " + response.code());  
        if (response.isSuccessful()) {  
            String resultValue = response.body().string();  
            Log.d(TAG, "响应体 " + resultValue);  
            return resultValue;  
        }  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
    return result;  
}  



private String doPost(String imagePath) {
OkHttpClient mOkHttpClient = new OkHttpClient();

String result = "error";
MultipartBody.Builder builder = new MultipartBody.Builder();
// 这里演示添加用户ID builder.addFormDataPart("userId", "20160519142605");
builder.addFormDataPart("image", imagePath,
RequestBody.create(MediaType.parse("image/jpeg"), new File(imagePath)));

RequestBody requestBody = builder.build();
Request.Builder reqBuilder = new Request.Builder();
Request request = reqBuilder
.url(Constant.BASE_URL + "/uploadimage")
.post(requestBody)
.build();

Log.d(TAG, "请求地址 " + Constant.BASE_URL + "/uploadimage");
try{
Response response = mOkHttpClient.newCall(request).execute();
Log.d(TAG, "响应码 " + response.code());
if (response.isSuccessful()) {
String resultValue = response.body().string();
Log.d(TAG, "响应体 " + resultValue);
return resultValue;
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}


2. 服务器端接收

[java]
view plain
copy

print?





// 修改用户的图片  
private void changeUserImage(HttpServletRequest request, HttpServletResponse response)   
        throws ServletException, IOException {  
    String message = "";  
    try{  
        DiskFileItemFactory dff = new DiskFileItemFactory();  
        ServletFileUpload sfu = new ServletFileUpload(dff);  
        List<FileItem> items = sfu.parseRequest(request);  
        for(FileItem item:items){  
            if(item.isFormField()){  
                //普通表单  
                String fieldName = item.getFieldName();  
                String fieldValue = item.getString();  
                System.out.println("name="+fieldName + ", value="+ fieldValue);  
            } else {// 获取上传字段  
                // 更改文件名为唯一的  
                String filename = item.getName();  
                if (filename != null) {  
                    filename = IdGenertor.generateGUID() + "." + FilenameUtils.getExtension(filename);  
                }  
                // 生成存储路径  
                String storeDirectory = getServletContext().getRealPath("/files/images");  
                File file = new File(storeDirectory);  
                if (!file.exists()) {  
                    file.mkdir();  
                }  
                String path = genericPath(filename, storeDirectory);  
                // 处理文件的上传  
                try {  
                    item.write(new File(storeDirectory + path, filename));  
  
                    String filePath = "/files/images" + path + "/" + filename;  
                    System.out.println("filePath="+filePath);  
                    message = filePath;  
                } catch (Exception e) {  
                    message = "上传图片失败";  
                }  
            }  
        }  
    } catch (Exception e) {  
        e.printStackTrace();  
        message = "上传图片失败";  
    } finally {  
        response.getWriter().write(message);  
    }  
}  



// 修改用户的图片
private void changeUserImage(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String message = "";
try{
DiskFileItemFactory dff = new DiskFileItemFactory(); ServletFileUpload sfu = new ServletFileUpload(dff); List<FileItem> items = sfu.parseRequest(request);
for(FileItem item:items){
if(item.isFormField()){
//普通表单
String fieldName = item.getFieldName();
String fieldValue = item.getString();
System.out.println("name="+fieldName + ", value="+ fieldValue);
} else {// 获取上传字段
// 更改文件名为唯一的
String filename = item.getName();
if (filename != null) {
filename = IdGenertor.generateGUID() + "." + FilenameUtils.getExtension(filename);
}
// 生成存储路径
String storeDirectory = getServletContext().getRealPath("/files/images");
File file = new File(storeDirectory);
if (!file.exists()) {
file.mkdir();
}
String path = genericPath(filename, storeDirectory);
// 处理文件的上传
try {
item.write(new File(storeDirectory + path, filename));

String filePath = "/files/images" + path + "/" + filename;
System.out.println("filePath="+filePath);
message = filePath;
} catch (Exception e) {
message = "上传图片失败";
}
}
}
} catch (Exception e) {
e.printStackTrace();
message = "上传图片失败";
} finally {
response.getWriter().write(message);
}
}


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

 

    


六、源码及示例

   ImageUpload Android端 + 服务器端代码
 

七、测试提示

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


八、结语

 
    通过上一篇博客《Android
拍照、选择图片并裁剪》以及该篇《Android 上传图片到JavaWeb服务器》,相信朋友们对于裁剪图片并上传服务器有了大致的了解,当然实现方式只是一种,主要是理清思路并灵活运用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: