首页 > 代码库 > 【Android实战】----基于Retrofit实现多图片/文件、图文上传
【Android实战】----基于Retrofit实现多图片/文件、图文上传
本文代码详见: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. * <p> * Values of the map on which this annotation exists will be processed in one of two ways: * <ul> * <li>If the type is {@link okhttp3.RequestBody RequestBody} the value will be used * directly with its content type.</li> * <li>Other object types will be converted to an appropriate representation by using * {@linkplain Converter a converter}.</li> * </ul> * <p> * <pre><code> * @Multipart * @POST("/upload") * Call<ResponseBody> upload( * @Part("file") RequestBody file, * @PartMap Map<String, RequestBody> params); * </code></pre> * <p> * 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<BaseBean> upLoadAgree(@PartMap Map<String, RequestBody>params); }
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<String, RequestBody> 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<String, RequestBody>(); } } 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 <T> 泛型參数 */ public <T> void addToEnqueue(Call<T> call, final RetrofitCallBack retrofitCallBack, final int method) { final Context context = MyApplication.getContext(); call.enqueue(new Callback<T>() { @Override public void onResponse(Call<T> call, Response<T> 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<T> 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<String, RequestBody> 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<String, RequestBody> 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<?U?
?
?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?
<sgb?g?x?
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<???,?B???;???
??
?
^[?#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<J?
?
?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??<??v_i-?
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"¥</?G?w/ *?
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<FileItem> items = new ArrayList<FileItem>(); InputStream is = null; FileOutputStream fos = null; try { items = upload.parseRequest(request); // 得到全部的文件 Iterator<FileItem> it = items.iterator(); while (it.hasNext()) { FileItem fItem = (FileItem) it.next(); String fName = ""; Object fValue = http://www.mamicode.com/null;>
??????
【Android实战】----基于Retrofit实现多图片/文件、图文上传