最近在写上传图片和参数到后台服务器的功能,网上的各种代码五花八门,大部分使用的还是原生的传输方式,此处我总结了使用Okhttp框架来进行传输的方式。实现了上传单张图片以及字符参数到服务器的功能,本博客还包括服务器部分的实现。
首先我在自己写的一个HttpUtil工具类中定义了网络请求上传图片到服务器的函数。此函数接收六个参数,如下:
public class HttpUtil {
/**
* @param address 服务器的地址
* @param key 获取图片的key
* @param filename 图片的文件名称
* @param file 图片文件
* @param params 字符参数
* @param stringCallback 回调接口
*/
public static void upLoadImageToServer(String address, String key, String filename, File file,
Map<String, String> params, StringCallback stringCallback) {
OkHttpUtils.post()
.addFile(key, filename, file)
.params(params)
.url(address)
.build()
.execute(stringCallback);
}
}
其中使用到的OkHttpUtils库是鸿洋大神实现的,github地址
我们使用只需在gradle文件中引入:
implementation 'com.zhy:okhttputils:2.6.2'
接着,在我们的具体业务中,使用如下函数,具体去实现上传图片到服务器的整个完整流程:
private void upLoadImageToServer() {
Map<String, String> params = new HashMap<>();
params.put("u_id", user.getU_id());
HttpUtil.upLoadImageToServer(Constant.EDITPHOTO_URL, "u_photo", photoFile.getName(),
photoFile, params, new StringCallback() {
@Override
public void onError(Call call, Exception e, int id) {
ImageUtils.deletePhotoFromStorage(user.getU_id());
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(EditPhotoActivity.this, "头像上传失败", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(String response, int id) {
final BaseMsg msg;
try {
String responseText = URLDecoder.decode(response, "utf-8");
msg = Utility.handleBaseMsgResponse(responseText);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return;
}
if (msg == null) {
return;
}
if (msg.getCode().equals("1")) {
String photo = msg.getMsg();
user.setU_photo(photo);
user.save();
} else if (msg.getCode().equals("0")) {
ImageUtils.deletePhotoFromStorage(user.getU_id());
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(EditPhotoActivity.this, msg.getMsg(), Toast.LENGTH_SHORT).show();
}
});
}
}
});
}
可以看到,此处我仅想传递一个u_id的参数到服务器,把这个参数写入一个Map中,然后利用刚才写好的HttpUtil.upLoadImageToServer()函数,向服务器发起网络请求,这里定义了自己的回调接口,用于处理服务器的响应结果。这样,android的部分就成功实现了。
补上代码中用到的ImageUtils类、Utility类和BaseMsg类的实现:
ImageUtils类:
public class ImageUtils {
// 从sd卡中获取用户头像
public static Bitmap getPhotoFromStorage(String u_id) {
String photoPath = Environment.getExternalStorageDirectory() + "/demo/photo/" + u_id + ".jpg";
return getBitmapFromPath(photoPath, 80, 80);
}
// 从sd卡中删除用户头像
public static void deletePhotoFromStorage(String u_id) {
String photoPath = Environment.getExternalStorageDirectory() + "/demo/photo/";
File file = new File(photoPath + u_id + ".jpg");
if (file.exists()) {
file.delete();
}
}
}
Utility类:
public class Utility {
/**
* 解析和处理服务器返回的基本消息数据
*/
public static BaseMsg handleBaseMsgResponse(String response) {
if (!TextUtils.isEmpty(response)) {
return new Gson().fromJson(response, BaseMsg.class);
}
return null;
}
}
BaseMsg类:
public class BaseMsg {
protected String code;
protected String msg;
public BaseMsg() {
}
public BaseMsg(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "RegistMsg{" +
"code='" + code + '\'' +
", msg='" + msg + '\'' +
'}';
}
}
服务器部分我使用的是ssm框架,参数接收一个request来取出客户端传递的数据。代码每部分的具体功能已在代码注释中给出,可放心食用:
// 修改头像
@RequestMapping("/editPhoto")
public @ResponseBody String editPhoto(HttpServletRequest request) {
String content; // 返回给客户端的内容
try {
request.setCharacterEncoding("utf-8"); //设置编码
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//创建工厂
DiskFileItemFactory factory = new DiskFileItemFactory(10*1024*1024, new File("E:/temp"));//设置缓存大小和临时目录
//得到解析器
ServletFileUpload sfu = new ServletFileUpload(factory);
//设置单个文件最大值为10*1024*1024
sfu.setFileSizeMax(10*1024*1024);
// 设置保存路径
String str = request.getServletContext().getRealPath("/");
int loc = str.indexOf("target");
String savepath = str.substring(0, loc) + "user_photo";
//使用sfu去解析request对象,得到List
try {
List<FileItem> fileItemList = sfu.parseRequest(new ServletRequestContext(request));
int photoIndex = -1;
Map<String, String> map = new HashMap<>();
for (int i = 0; i < fileItemList.size(); i++) {
if (fileItemList.get(i).isFormField()) { // 判断是否是普通表单项
map.put(fileItemList.get(i).getFieldName(), fileItemList.get(i).getString("utf-8"));
} else { // 图片类型
photoIndex = i;
}
}
// 得到用户id
String u_id = map.get("u_id");
//设置图片名称:u_id + 随机16位uuid + 图片后缀
int begin = fileItemList.get(photoIndex).getName().indexOf(".");
String suffix = fileItemList.get(1).getName().substring(begin); //截取图片后缀
String filename = u_id + CommonUtils.uuid(16) + suffix;
if (!(filename.toLowerCase().endsWith("png") || filename.toLowerCase().endsWith("jpg") || filename.toLowerCase().endsWith("jpeg"))) {
// 图片格式错误
content = "{'code':'0', 'msg':'图片格式错误,上传失败'}";
try {
return URLEncoder.encode(content, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return content;
}
//使用目录和文件名称创建目标文件
File destFile = new File(savepath, filename);
//保存上传文件到目标文件位置
fileItemList.get(photoIndex).write(destFile);
// 删除原头像
String oldPhoto = userService.getUserInfo(u_id).getU_photo();
File file = new File(savepath, oldPhoto);
if (file.exists()) {
file.delete();
}
// 保存到服务器
userService.savePhoto(u_id, filename);
// 返回成功信息和图片名给客户端
content = "{'code':'1', 'msg':'" + filename + "'}";
try {
return URLEncoder.encode(content, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return content;
} catch (Exception e){
if(e instanceof FileUploadBase.FileSizeLimitExceededException) {
// 图片尺寸过大
content = "{'code':'0', 'msg':'图片尺寸超过10MB,上传失败'}";
try {
return URLEncoder.encode(content, "utf-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
return content;
}
}
return null;
}
值得注意的是,这里有一个坑,可以看到,我定义了解析工厂,并使用解析器来完成数据的解析。在fileItemList中,如果发现它的size为0,那么说明你在springmvc的配置文件中配置了如下的文件上传的解析器,由于springmvc已经解析过数据了,因此这里二次解析将获取不到数据,只需要将springmvc中的这部分代码注释掉即可:
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize">
<value>5242880value>
property>
bean>