一、实现效果
二、表单中enctype
1. 表单中enctype的作用是什么?
2. 为什么上传文件要设置enctype="multipart/form-data"?
三、服务端fileupload接收文件
1. 使用 fileupload 解析request
DiskFileItemFactory dff = new DiskFileItemFactory();
ServletFileUpload sfu = new ServletFileUpload(dff);
List<FileItem> items = sfu.parseRequest(request);
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上传文件
在上一篇《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);
}
}
}
五、客户端上传用户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以及图片,具体的业务要具体实现。控制台打印结果如下:
六、源码及示例
七、测试提示
在自己测试的时候,可以选用本地的tomcat,然后电脑和手机在同一网段内就可以了(连接同一个wifi),ip地址查看方法:
八、结语
通过上一篇博客《Android 拍照、选择图片并裁剪》以及该篇《Android 上传图片到JavaWeb服务器》,相信朋友们对于裁剪图片并上传服务器有了大致的了解,当然实现方式只是一种,主要是理清思路并灵活运用。