Android 上传图片到JavaWeb服务器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuehuayous/article/details/51453077
前言:在上一篇博客《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

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

    断点调试图如下:

    

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

2. 获取上传字段


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

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

3. 更改文件名称为唯一


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

// 更改文件名为唯一的
String filename = fileItem.getName();
if (filename != null) {
    filename = IdGenertor.generateGUID() + "." + FilenameUtils.getExtension(filename);
}
这里的generateGUID()来产生随机的32位16进置值:
/**
 * 生成UUID
 * 
 * @return UUID
 */
public static String generateGUID() {
    return new BigInteger(165, new Random()).toString(36).toUpperCase();
}

4. 生成存储路径


我们可以通过

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

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

// 计算文件的存放目录
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. 存储文件

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

6. 响应文件存储地址

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

response.getWriter().write(filePath);


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

四、客户端OkHttp上传文件

 

1、监听裁剪结果

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

// 设置裁剪图片结果监听
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上传图片文件

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

// 设置裁剪图片结果监听
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来进行子线程到主线程的链接。

/**
 * 访问网络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来进行文件的上传:

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加载图片

/**
 * 访问网络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的字段就可以了

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


整体代码如下:

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. 服务器端接收

// 修改用户的图片
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服务器》,相信朋友们对于裁剪图片并上传服务器有了大致的了解,当然实现方式只是一种,主要是理清思路并灵活运用。

 

猜你喜欢

转载自blog.csdn.net/xuehuayous/article/details/51453077