前言
因为前端使用的rxjava+retrofit+mvp的架构进行实现,因此考虑着图片上传的功能也直接用rxjava+retrofit去实现,结果在使用过程中,发现始终有问题,图片上不去;测试了几天,尝试更新成rxjava2+retrofit2进行测试,结果成功了;因此这里做一下记录;如果不知道rxjava+retrofit使用的朋友可以参考之前写的博客浅谈Android MVP设计模式(简单结合RxJava+Retrofit)
Android前段实现
资源引入
在build.gradle文件中添加rxjava2+retrofit2相关的引入;我这边是以下相关的配置
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
compile 'com.squareup.okhttp3:okhttp:3.8.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'
compile "io.reactivex.rxjava2:rxjava:2.1.1"
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
service的编写
@Multipart
@POST("/imgs/upload")
Observable<String> uploadImgs(@Part List<MultipartBody.Part> file, @Query("data") String data);
这里传递的是list,目的是方便传输多张图片,如果只有一张,往list里面塞一个值即可
基于Retrofit2自定义Subscriber
在retrofit中我们常用的Subscriber在retrofit2中竟然不见了,替换他的是Observer,为了减少代码的调整,我们自定义一个Subscriber,代码如下,里面异常处理相关的代码,可以根据自己项目情况酌情调整:
public abstract class Subscriber<T> implements Observer<T> {
@Override
public void onError(Throwable e) {
Throwable throwable = e;
//获取最根源的异常
while (throwable.getCause() != null) {
e = throwable;
throwable = throwable.getCause();
}
NetWorkException ex;
if (e instanceof HttpException) { //HTTP错误
HttpException httpException = (HttpException) e;
NetWorkCodeEnum netWorkCode = NetWorkCodeEnum.getNetWorkCode(httpException.code() + "");
if (null != netWorkCode)
ex = new NetWorkException(netWorkCode.getHttpCode() + "", netWorkCode.getMessage());
else
ex = new NetWorkException(NetWorkCodeEnum.UNKNOWN.getHttpCode() + "", NetWorkCodeEnum.UNKNOWN.getMessage());
} else if (e instanceof ConnectException) {
ex = new NetWorkException(NetWorkCodeEnum.CODE_503.getHttpCode() + "", NetWorkCodeEnum.CODE_503.getMessage());
} else if (e instanceof ResultException) { //服务器返回的错误
ResultException resultException = (ResultException) e;
ex = new NetWorkException(resultException.getmErrorCode(), resultException.getMessage());
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
ex = new NetWorkException(NetWorkCodeEnum.CODE_1001.getHttpCode() + "", NetWorkCodeEnum.CODE_1001.getMessage());//均视为解析错误
} else if (e instanceof NetWorkException) {
ex = (NetWorkException) e;
} else {
if (null != e.getMessage() && e.getMessage().length() > 0) {
ex = new NetWorkException(NetWorkCodeEnum.OTHER.getHttpCode() + "", e.getMessage());//未知错误
} else {
ex = new NetWorkException(NetWorkCodeEnum.UNKNOWN.getHttpCode() + "", NetWorkCodeEnum.UNKNOWN.getMessage());//未知错误
}
}
onError(ex);
}
/**
* 错误回调
*/
protected abstract void onError(BaseException e);
}
Model层编写
public void uploadImg(List<File> files) {
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM);//表单类型
for (int i = 0; i < files.size(); i++) {
File file = files.get(i);
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/jpg"), file);
builder.addFormDataPart("file", file.getName(), photoRequestBody);
}
List<MultipartBody.Part> parts = builder.build().parts();
managerService.uploadImgs(parts, "")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<String>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull String s) {
}
@Override
public void onComplete() {
}
@Override
protected void onError(BaseException e) {
}
});
}
Android调用相机拍照
android调用相机拍照有2种模式,一种是拍照后获取压缩图;一种是拍照之后将图片保存到指定路径,然后根据代码获取原图显示上传
private static int REQUEST_THUMBNAIL = 10;// 请求缩略图信号标识
private static int REQUEST_ORIGINAL = 9;// 请求原图信号标识
第一种方式
Intent intent1 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // 启动相机 startActivityForResult(intent1, REQUEST_THUMBNAIL);
第二种方式
//SD卡路径 String sdPath = Environment.getExternalStorageDirectory().getPath(); //照片保存的路径及名称 String picPath = sdPath + "/" + "test.png"; Intent intent2 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); Uri uri = null; if (Build.VERSION.SDK_INT <= 23) { //为拍摄的图片指定一个存储的路径 uri = Uri.fromFile(new File(picPath)); } else { ContentValues contentValues = new ContentValues(1); contentValues.put(MediaStore.Images.Media.DATA, picPath); uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); } if (null != uri) { intent2.putExtra(MediaStore.EXTRA_OUTPUT, uri); startActivityForResult(intent2, REQUEST_ORIGINAL); }
接受图片
/** * 返回应用时回调方法 */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { if (requestCode == REQUEST_THUMBNAIL) {//对应第一种方法 /** * 通过这种方法取出的拍摄会默认压缩,因为如果相机的像素比较高拍摄出来的图会比较高清, * 如果图太大会造成内存溢出(OOM),因此此种方法会默认给图片进行压缩 */ Bundle bundle = data.getExtras(); Bitmap bitmap = (Bitmap) bundle.get("data"); img.setImageBitmap(bitmap); //这里可以再将这个bitmap保存到sd卡中,然后得带一个file对象,即可进行上传;这里就不写了 } else if (requestCode == REQUEST_ORIGINAL) {//对应第二种方法 /** * 这种方法是通过内存卡的路径进行读取图片,所以的到的图片是拍摄的原图 */ FileInputStream fis = null; try { Log.e("sdPath2", picPath); //把图片转化为字节流 fis = new FileInputStream(picPath); //把流转化图片 Bitmap bitmap = BitmapFactory.decodeStream(fis); img.setImageBitmap(bitmap); //测试上传 File file = new File(picPath); List<File> files = new ArrayList<>(); //如果有多张如片,通过系统照片选择取到相应的路径,然后添加到list中即可 files.add(file); //这里为了方便测试,直接只用了model的代码进行测试了,实际开发的时候,这里需要调用presenter的代码测试 uploadImageModel.uploadImg(files); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { try { fis.close();//关闭流 } catch (IOException e) { e.printStackTrace(); } } } } }
后端实现(SpringMvc)
springmvc.xml配置
<!-- SpringMVC上传文件时,需要配置MultipartResolver处理器 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8" /> <!-- 指定所上传文件的总大小,单位字节。注意maxUploadSize属性的限制不是针对单个文件,而是所有文件的容量之和 20M,可根据情况调整 --> <property name="maxUploadSize" value="20971520" /> </bean>
controller层代码编写
@RequestMapping(value = "/upload", method = RequestMethod.POST) @ResponseBody public String imageUpload(@RequestParam("file") MultipartFile[] partFiles, String data) { try { if (null != partFiles && partFiles.length > 0) { for (int i = 0; i < partFiles.length; i++) { MultipartFile partFile = partFiles[i]; //服务器图片保存的路径 String imgPath = "E:/test/imgs/img" + i + ".png"; File imgFile = new File(imgPath); //将图片写到指定的文件下 partFile.transferTo(imgFile); } return "{\"status\":0,\"desc\":\"成功\"}"; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return "{\"status\":-1,\"desc\":\"失败\"}"; }
这里只是写了一个范例,后台如何接受的操作,但是实际开发过程中逻辑相关的代码是应该在service中去写;为了直观的达到效果;这里就直接写在controller里面了。
可能存在的问题
- 文件大小
如果说文件太大;可以在stringmvc.xml中将maxUploadSize属性调大一点 - nginx文件大小
nginx默认请求最大的文件包为2M,如果上传的图片过大,那么nginx就会报“413 Request Entity Too Large”错误;原因就是因为图片大于2M了,可以在ngnix.conf配置文件中的http{}下添加或者修改client_max_body_size 20m配置,即允许最大的包为20M,大小可根据情况调整;使用nginx -t测试配置是否符合要求;然后通过nginx -s reload重启nginx即可
到这里,单张图片和多张图片即可完成上传了。