本文代码详见:https://github.com/honghailiang/RetrofitUpLoadImage
一、再次膜拜下Retrofit
Retrofit不管从性能还是使用方便性上都非常屌!!!
,本文不去介绍其运作原理(尽管非常想搞明确)。后面会出专题文章解析Retrofit的内部原理;本文仅仅是从使用上解析Retrofit实现多图片/文件、图文上传的功能。文件上传相关可參考Multipart/form-data文件上传简单介绍 及Apache FileUpload文件上传功能 。
二、概念介绍
1)注解@Multipart
从字面上理解就是与多媒体文件相关的,没错,图片、文件等的上传都要用到该注解,当中每一个部分须要使用@Part来注解。
。看其凝视
/**
* Denotes that the request body is multi-part. Parts should be declared as parameters and
* annotated with {@link Part @Part}.
*/
2)注解@PartMap
当然能够理解为使用@PartMap凝视。传递多个Part,以实现多文件上传。
凝视
/**
* Denotes name and value parts of a multi-part request.
*
* Values of the map on which this annotation exists will be processed in one of two ways:
*
* - If the type is {@link okhttp3.RequestBody RequestBody} the value will be used
* directly with its content type.
* - Other object types will be converted to an appropriate representation by using
* {@linkplain Converter a converter}.
*
*
*
* @Multipart
* @POST("/upload")
* Call upload(
* @Part("file") RequestBody file,
* @PartMap Map params);
*
*
* A {@code null} value for the map, as a key, or as a value is not allowed.
*
* @see Multipart
* @see Part
*/
3)RequestBody
从上面凝视中就能够看到參数类型是RequestBody,其就是请求体。
文件上传就须要參数为RequestBody。官方使用说明例如以下http://square.github.io/retrofit/
Multipart parts use one of Retrofit's converters or they can implement RequestBody to handle their own serialization.
四、基本实现
了解了以上概念,以下就一一实现
1)接口定义
public interface IHttpService {
@Multipart
@POST("file/upLoad.do")
Call upLoadAgree(@PartMap Mapparams);
}
BaseBean是依据服务端返回数据进行定义的。这个使用时能够依据自有Server定义。
2)Retrofit实现
/**
* Created by DELL on 2017/3/16.
* 上传文件用(包括图片)
*/
public class RetrofitHttpUpLoad {
/**
* 超时时间60s
*/
private static final long DEFAULT_TIMEOUT = 60;
private volatile static RetrofitHttpUpLoad mInstance;
public Retrofit mRetrofit;
public IHttpService mHttpService;
private static Map params;
private RetrofitHttpUpLoad() {
mRetrofit = new Retrofit.Builder()
.baseUrl(UrlConfig.ROOT_URL)
.client(genericClient())
.addConverterFactory(GsonConverterFactory.create())
.build();
mHttpService = mRetrofit.create(IHttpService.class);
}
public static RetrofitHttpUpLoad getInstance() {
if (mInstance == null) {
synchronized (RetrofitHttpUpLoad.class) {
if (mInstance == null)
mInstance = new RetrofitHttpUpLoad();
params = new HashMap();
}
}
return mInstance;
}
/**
* 加入统一超时时间,http日志打印
*
* @return
*/
private OkHttpClient genericClient() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(logging)
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.build();
return httpClient;
}
/**
* 将call加入队列并实现回调
*
* @param call 调入的call
* @param retrofitCallBack 回调
* @param method 调用方法标志。回调用
* @param 泛型參数
*/
public void addToEnqueue(Call call, final RetrofitCallBack retrofitCallBack, final int method) {
final Context context = MyApplication.getContext();
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
LogUtil.d("retrofit back code ====" + response.code());
if (null != response.body()) {
if (response.code() == 200) {
LogUtil.d("retrofit back body ====" + new Gson().toJson(response.body()));
retrofitCallBack.onResponse(response, method);
} else {
LogUtil.d("toEnqueue, onResponse Fail:" + response.code());
ToastUtil.makeShortText(context, "网络连接错误" + response.code());
retrofitCallBack.onFailure(response, method);
}
} else {
LogUtil.d("toEnqueue, onResponse Fail m:" + response.message());
ToastUtil.makeShortText(context, "网络连接错误" + response.message());
retrofitCallBack.onFailure(response, method);
}
}
@Override
public void onFailure(Call call, Throwable t) {
LogUtil.d("toEnqueue, onResponse Fail unKnown:" + t.getMessage());
t.printStackTrace();
ToastUtil.makeShortText(context, "网络连接错误" + t.getMessage());
retrofitCallBack.onFailure(null, method);
}
});
}
/**
* 加入參数
* 依据传进来的Object对象来推断是String还是File类型的參数
*/
public RetrofitHttpUpLoad addParameter(String key, Object o) {
if (o instanceof String) {
RequestBody body = RequestBody.create(MediaType.parse("text/plain;charset=UTF-8"), (String) o);
params.put(key, body);
} else if (o instanceof File) {
RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data;charset=UTF-8"), (File) o);
params.put(key + "\"; filename=\"" + ((File) o).getName() + "", body);
}
return this;
}
/**
* 构建RequestBody
*/
public Map bulider() {
return params;
}
public void clear(){
params.clear();
}
}
当中定义了Retrofit实例、还用拦截器定义了统一的超时时间和日志打印;将call加入队列并实现回调。
最重要的就是加入參数:
/**
* 加入參数
* 依据传进来的Object对象来推断是String还是File类型的參数
*/
public RetrofitHttpUpLoad addParameter(String key, Object o) {
if (o instanceof String) {
RequestBody body = RequestBody.create(MediaType.parse("text/plain;charset=UTF-8"), (String) o);
params.put(key, body);
} else if (o instanceof File) {
RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data;charset=UTF-8"), (File) o);
params.put(key + "\"; filename=\"" + ((File) o).getName() + "", body);
}
return this;
}
这里就是依据传入的參数,返回不同的RequestBody, 注意文件的key值。
3)使用
private void upLoadAgree() {
showWaitDialog();
RetrofitHttpUpLoad retrofitHttpUpLoad = RetrofitHttpUpLoad.getInstance();
retrofitHttpUpLoad.clear();
if (!StringUtil.isEmpty(pathImage[0])){
retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter("pic1",new File(pathImage[0]));
}
if (!StringUtil.isEmpty(pathImage[1])){
retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter("pic2", new File(pathImage[1]));
}
if (!StringUtil.isEmpty(pathImage[2])){
retrofitHttpUpLoad = retrofitHttpUpLoad.addParameter("zip", new File(pathImage[2]));
}
Map params = retrofitHttpUpLoad
.addParameter("status", "4")
.addParameter("pickupId", tv_orderquality_pid.getText().toString())
.addParameter("cause", reason)
.addParameter("connectname", et_orderquality_lxrname.getText().toString())
.addParameter("connectphone", et_orderquality_lxrphone.getText().toString())
.addParameter("details", et_orderquality_xqms.getText().toString())
.bulider();
retrofitHttpUpLoad.addToEnqueue(retrofitHttpUpLoad.mHttpService.upLoadAgree(params),
this, HttpStaticApi.HTTP_UPLOADAGREE);
}
须要注意的是要对图片及文件路径进行判空操作,否则会报异常W/System.err: java.io.FileNotFoundException: /: open failed: EISDIR (Is a directory)
五、报文日志
当中图片报文有省略
D/OkHttp: --> POST http://192.168.xxx.xxx:8880/xxx/nocheck/file/agree.do http/1.1
D/OkHttp: Content-Type: multipart/form-data; boundary=f3e7369a-ead9-46e2-9ddd-448442fd5108
D/OkHttp: Content-Length: 300580
D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108
D/OkHttp: Content-Disposition: form-data; name="pic2"; filename="90079.jpg"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data;charset=UTF-8
D/OkHttp: Content-Length: 149456
D/OkHttp: ?
??
???JFIF??
??H?
?
H?
????
???C?? D/OkHttp: "##! %*5-%'2( .?/279<<<$-BFA:F5;<9??
?
?
C D/OkHttp: 9& &99999999999999999999999999999999999999999999999999???
?
?
8"??
?
?
??
??
???
?
?????
???
?
???
????
??
???
?
?
??
??
?
?
?
??
??
?????
?
???
????
?????????
|??
?
/n?s??y?]8ug?7?R?
??
???h?tΘa?T?
T
?
?z?
?
?+3C?
w??tdf??=
??fN??s??x??hhzd??X~?
X??
i?{~/?
<^??~=zX??\??4?U?ɡ)I?
?
?
?
???
?
???
????
?
?
?
?$??@?
??
iM?"J?
R 2?
f?MK5x#w?????r?I?3?Y?
?l??
?
V?Bj??>{?
?
t?u???
?
]?
?
??
g>?o?
?o??
?
dM?U?
??
???
J?R??
+?;?
???
?
?
????
OG>?
?
??
=?5?L?9?&???_/\?yu??
~|*,???
My?
r?????
?
?
?='?d?=?t*?
?*?
Y?
?
(????
?
?
?
??
?
?
?
?
?
?? YB i?
Jlv??d?
"l?
???Y?
?
4??X?
7?
??
;??
sY?
\κ+?N?
?;?
L?
?&?(?
MJ?
??
@w~~??
?
a?qs?
?m7??y?
?
?Ns?
\?C?
g??>?
??N%??N?
?gs?Q??c???
?Z?t?
?
?x??
{??
^_}s?
s??
gO?
?
???
N?|}?;?
?y?y?
ǎ|?v??
N?
l??????
?????*k5?(???????
?
?
1$?B?
?j?+,?l?
??
hN??
U?
S??;;c?,? ?7?0Z?J?I? r??X?9? t? '\? 1W+ D/OkHttp: [?? ????=/X? ? ?n??T*4? u? ?
??? ?s? q?? ?? ??c???\?6? YV?p??? ?oB%??|? s?? ?? ? ? ?? ??{?? g??k?}?t??d{]^W???v?WB? x???|z9? >V?{ǒ>?o?? Y? ???xk? k7? ?? {w????b?!JQjRQ%RH?%8? H??Q? ?Ys? {??? u??(?`?b\?? k? cC:u#???d?C?? &? ?W.CXd?e?? N? ?n? ? ? ?.?%v?,.zW?>??&??+??r??S'? .n? [ V? ?q??oGL? ,:?S?? ? ???/?o
?? ? ^[?#Lm?? 7.Q? 6sONz??fwN? D/OkHttp: ?? ?,? \???? D/OkHttp: ? ??U
1?Z?? ?=?? pn?~q?? [-?P?? =? j?va? R? ?? 4X*??? nv?H? ?j?j?p? ? `h#-???qiA?U????? x? &v?b? R?? o?.??H[M5??Y??5?>%Y?j???? x? 2.m??=??GG??? D/OkHttp: \? D? ?(?JK9 ? ?JE?jl??pW D/OkHttp: ??}? ?i?6??R? ?:!J??FT?!e??? D/OkHttp: ??? ?:??5?? ??%? ? `? |? ??;z?G? ?[?P? ??N=???T??X? -?okNe?? ?Y??f8?`:?P? ??x? 1?I?g ?0? )? fe*P?qU? ~?jSY%?? gɡR?(?$A? |y?}??s?2?
/? ? 4? s??@-?,?? AZ?az?,?? bl?.?? WD? ??? ?? q? X?u}?+G? z?h8=?w[`?j??g&q?c? ???????<|??|? 1???? q^? ? ? D/OkHttp: 5? ?)x???:*dK? ?|?KPz5v?4?+?>eO?4??i?P2F?&\9? ?? ? -V?esf~&F?Q?? S?\??? 8{? ? *B?1qVO?? ??-S?? !????? ? ? ? ?*6?? D/OkHttp: 3? 5W?r? x??+? \? r? ? ? 6? ?C? ?Ms,H?AE??=q?? ????(??f?=,?? ? ?Z??+????L??
m|? ?? ?6??L? ??=?4? Y?{? W? ?9=?? \AW?? ? ~?? {@!?^ Z6??,?k>|? ?C D/OkHttp: aZ? ? -?ы? ?R?K? ? ?1?6`'?? F%/4wZ?Cn? ??? [?]?el?? U&[?? ?1db-4???? ?? ??~er!?4??>ji? ]??AV?[v??e??`θo??? 帏!(??Pk?XCI?? Glk-p??p ? B?b???ae??? d? ]-|"? ?*? ?`??l?? Tmn`? ?? D/OkHttp: R? G??h? DETp???i? ??^? ? ? ?u?E??1?wW%?
??3e? ?V?? ?? **m??9V??O?R??f? b? D/OkHttp: ? ?j%)^? $?? g?\?Qm^`? ? D/OkHttp: ? ?[*?\?@/T@,?|@\$F? ????v_??uA?? ? :?9>%B? ??? 1 =?D]{? "? ? *^?? q1? ? i?? B?bs?L_?? ? ? e? ?W?2??pDR?Q??M?? ?{?? ? ?7S? ?? ?? Dzcb\? ??? ??0??? ? ? u? h?? ?e?? 7X? ? )? s{??DIqf???QdM? ?V?? ? ? ?r?? MTk?=?? +? >b0?b?? ?i?\? lI??H?P? ? ,?Pn[]??.? `.X? =A?I?P? @?<~??Px??.??? 9?(? ?:=? ? 5E?n? !l??? 5???ee_??'[???? p? d??1? )g?s
?kop?вd? 19m?ft??ab??? ? ???? j?5/pT?M?xBb??8???z?? ? ??? wX??V??|~x???????? c?Rsx? ?? D???ixH??ud?50??MΘ7? ??
^I???i?`?????f?A? ?? ?? ? ?? ? ;?U? H? ?? ?a~?W臸?@O?'u\-???? ?? ? CN,? ? ??-? ??@?+"n? :y???G |S??C?F5?? ? ??Ix??? ??)?????b 22???jRj??? ?j?,K?K"¥
W? ? ? ?sn??L?? ??n? n? ???? k??? ? "? *? ?~?9? ?<4?,c?d>?EG??iB? ?0+??i? Y??D?? ?p?? ???? ? S|.?2???# &?? "v?Y?? P?? O?#EK? ? ? ,J? 6U? >a???;?-rM??@? ?? ^b??@??K? ???? PI??4? qM|? ?V? ? h[Ld? ?R????or? U?M??)_?J?^S? 41n}?@n|?? D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108 D/OkHttp: Content-Disposition: form-data; name="cause" D/OkHttp: Content-Transfer-Encoding: binary D/OkHttp: Content-Type: text/plain;charset=UTF-8 D/OkHttp: Content-Length: 33 D/OkHttp: 对货物数量、质量有异议 D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108 D/OkHttp: Content-Disposition: form-data; name="details" D/OkHttp: Content-Transfer-Encoding: binary D/OkHttp: Content-Type: text/plain;charset=UTF-8 D/OkHttp: Content-Length: 9 D/OkHttp: 哈哈哈 D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108 D/OkHttp: Content-Disposition: form-data; name="status" D/OkHttp: Content-Transfer-Encoding: binary D/OkHttp: Content-Type: text/plain;charset=UTF-8 D/OkHttp: Content-Length: 1 D/OkHttp: 4 D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108 D/OkHttp: Content-Disposition: form-data; name="pickupId" D/OkHttp: Content-Transfer-Encoding: binary D/OkHttp: Content-Type: text/plain;charset=UTF-8 D/OkHttp: Content-Length: 6 D/OkHttp: 105329 D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108 D/OkHttp: Content-Disposition: form-data; name="connectphone" D/OkHttp: Content-Transfer-Encoding: binary D/OkHttp: Content-Type: text/plain;charset=UTF-8 D/OkHttp: Content-Length: 11 D/OkHttp: 13xxxxxxxxx D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108 D/OkHttp: Content-Disposition: form-data; name="connectname" D/OkHttp: Content-Transfer-Encoding: binary D/OkHttp: Content-Type: text/plain;charset=UTF-8 D/OkHttp: Content-Length: 3 D/OkHttp: 111 D/OkHttp: --f3e7369a-ead9-46e2-9ddd-448442fd5108-- D/OkHttp: --> END POST (300580-byte body) https://github.com/honghailiang/RetrofitUpLoadImage六、代码托管
七、效果图:
八、服务端代码
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
File directory = null;
List