Retrofit2本身是支持文件上传的, 但是由于当时的版本(Retrofit2 beta2)有一个小的bug, 需要用一个Hack来解决, 因此我就将以前写的ImageUploader稍微封装了一下.
由于之前的ImageUploader职责有写多, 同时包含了网络请求代码, 描述网络请求信息, 还有一些工具类代码. 因此我将其拆分成三个类:
- ImageUploader
只负责发送网络请求
- ImageUploadHelper
将请求结果转换成相应的实体, 将请求包装成Observable, 以及一些工具方法
- ImageUploadRequest
拼接请求, 如Url, 参数, 要上传的文件, 转换的实体类型等.
虽然..类名叫ImageUploader, 但是也是可以用来上传文件的. 由于APP里绝大部分情况都是上传图片, 所以干脆就叫ImageUploader了. 接下来看看ImageUploader类, 此类只提供了一个公共方法, 一个接口, 以及一个异常类:
public class ImageUploader {
public void post(String actionUrl, Map<String, String> params,
Map<String, File> files, String fileKey, OnResponseListener listener);
public interface OnResponseListener {
void onResponse(String data);
void onError(Exception e);
}
class HttpException extends Exception {
private int httpCode;
private String message;
public HttpException(int httpCode, String message) {
this.httpCode = httpCode;
this.message = message;
}
public int getHttpCode() {
return httpCode;
}
public String getMessage() {
return message;
}
}
}
post的具体实现就不贴出来了, 代码比较长, 主要是使用HttpURLConnection来上传文件. 传入post方法的参数在这里说明一下:
String actionUrl 请求的URL地址
String fileKey 文件对应的键值, 如果此值为空, 则文件对应的键值默认为files的键, 如果不为空, 则键值全部为fileKey
OnResponseListener listener 上传结果的回调
OnResponseListener与HttpException都比较简单, 接下来看看ImageUploadRequest:
/**
* 文件上传请求封装类, 通过{@link Builder}
* 组装请求.
*/
public class ImageUploadRequest {
private String url;
private Map<String, String> params;
private Map<String, File> files;
private String fileKey;
private ImageUploader.OnResponseListener listener;
private Class outputClass; // 需要转换的实体类型, 如BaseData
private Class innerClass; // 如需要转换的实体类型带有泛型, 则需要设置innerClass, 如BaseData<User>
// get与set...
/**
* 标准的Builder类
*/
public static class Builder {
private ImageUploadRequest request;
public Builder() {
request = new ImageUploadRequest();
}
public Builder addParam(String key, String value) {
if (request.getParams() == null) request.setParams(new HashMap<String, String>());
request.getParams().put(key, value);
return this;
}
public Builder addFile(String key, File file) {
if (request.getFiles() == null) request.setFiles(new HashMap<String, File>());
request.getFiles().put(key, file);
return this;
}
public Builder setOutputClass(Class clz) {
request.setOutputClass(clz);
return this;
}
public Builder setInnerClass(Class clz) {
request.setInnerClass(clz);
return this;
}
public Builder setUrl(String url) {
request.setUrl(url);
return this;
}
public Builder setFileKey(String key) {
request.setFileKey(key);
return this;
}
public Builder setListener(ImageUploader.OnResponseListener listener) {
request.setListener(listener);
return this;
}
public ImageUploadRequest build() {
return request;
}
}
}
ImageUploadRequest类中包含有一个Builder类, 可以使用链式结构来拼接请求. 然后看看ImageUploadHelper
/**
* 通过{@link ImageUploader} 与 {@link ImageUploadRequest} 来上传图片.
* Created by Ryan on 2016/1/20.
*/
public class ImageUploadHelper<T> {
private ImageUploader uploader;
private Gson gson;
public ImageUploadHelper() {
uploader = new ImageUploader();
gson = new Gson();
}
private void doPost(String url, Map<String, String> params, Map<String, File> files,
String fileKey, ImageUploader.OnResponseListener listener) {
uploader.post(url, params, files, fileKey, listener);
}
public void doPost(ImageUploadRequest request) {
doPost(request.getUrl(), request.getParams(), request.getFiles(),
request.getFileKey(), request.getListener());
}
public Observable<T> doPostWithObservable(final ImageUploadRequest request) {
return Observable.create(new Observable.OnSubscribe<T>() {
@Override
public void call(final Subscriber<? super T> subscriber) {
uploader.post(request.getUrl(), request.getParams(), request.getFiles(),
request.getFileKey(), new ImageUploader.OnResponseListener() {
@Override @SuppressWarnings("unchecked")
public void onResponse(String data) {
subscriber.onNext((T) gson.fromJson(data,
ParameterizedTypeImpl.get(request.getOutputClass(), request.getInnerClasses())));
subscriber.onCompleted();
}
@Override
public void onError(Exception e) {
subscriber.onError(e);
}
});
}
});
}
// 压缩图片. 清除图片缓存等工具方法
}
此类使用ImageUploader发送请求, 使用ImageUploadRequest类来拼接请求, 也就是说, 我们应该使用ImageUploadHelper类来上传文件而不是ImageUploader类. doPostWithObservable方法还将上传请求转换为Observable, 以便我们使用. 此处要注意的是, ImageUploadHelper后传入的泛型的类型, 要与ImageUploadRequest中的outputClass和innerClass一致, 不然会报转换错误. 如最终要解析的类型为BaseData<User>
, 则泛型里需传入ImageUploadHelper<BaseData<User>>
, 并且将ImageUploadRequest中的outputClass设置为BaseData.class, innerClass设置为User.class.
最终使用方法:
首先可以新建一个Model层的类:
public class ImageUploadManager {
private ImageUploadHelper<BaseData> uploadHelper;
public ImageUploadManager() {
uploadHelper = new ImageUploadHelper<>();
}
public Observable<BaseData> uploadAvatar(long id, File avatar) {
ImageUploadRequest.Builder builder = new ImageUploadRequest.Builder();
ImageUploadRequest request = builder.addParam("id", id + "")
.addFile("file", avatar).setUrl(Urls.ROOT + Urls.IMAGE_UPLOAD)
.setOutputClass(BaseData.class).build();
return uploadHelper.doPostWithObservable(request);
}
}
然后就可以像一般的网络请求一样, 通过ResponseSubscriber来处理请求结果.