您的位置:首页 > 移动开发 > Android开发

【Android实战】----基于Retrofit实现多图片/文件、图文上传

2017-05-26 15:37 706 查看
本文代码详见:https://github.com/honghailiang/RetrofitUpLoadImage

原文链接:http://blog.csdn.net/honghailiang888/article/details/62884231

一、再次膜拜下Retrofit

Retrofit无论从性能还是使用方便性上都很屌!!!,本文不去介绍其运作原理(虽然很想搞明白),后面会出专题文章解析Retrofit的内部原理;本文只是从使用上解析Retrofit实现多图片/文件、图文上传的功能。文件上传相关可参考Multipart/form-data文件上传简介 及Apache
FileUpload文件上传功能 。

二、概念介绍

1)注解@Multipart

从字面上理解就是与多媒体文件相关的,没错,图片、文件等的上传都要用到该注解,其中每个部分需要使用@Part来注解。。看其注释

[html] view
plain copy

/**

* Denotes that the request body is multi-part. Parts should be declared as parameters and

* annotated with {@link Part @Part}.

*/

2)注解@PartMap

当然可以理解为使用@PartMap注释,传递多个Part,以实现多文件上传。注释

[html] view
plain copy

/**

* 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/

[html] view
plain copy

Multipart parts use one of Retrofit's converters or they can implement RequestBody to handle their own serialization.

四、基本实现

了解了以上概念,下面就一一实现

1)接口定义

[html] view
plain copy

public interface IHttpService {

@Multipart

@POST("file/upLoad.do")

Call<BaseBean> upLoadAgree(@PartMap Map<String, RequestBody>params);

}

BaseBean是根据服务端返回数据进行定义的,这个使用时可以根据自有Server定义。

2)Retrofit实现

[java] view
plain copy

/**

* 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加入队列并实现回调。最重要的就是添加参数:

[java] view
plain copy

/**

* 添加参数

* 根据传进来的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)使用

[java] view
plain copy

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)

五、报文日志

其中图片报文有省略

[html] view
plain copy

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?M
K5x#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?? ?{????7
S?????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????s
n??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

七、效果图:





八、服务端代码

[java] view
plain copy

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 = null;

if (fItem.isFormField()) { // 普通文本框的值

fName = fItem.getFieldName();

fValue = fItem.getString("UTF-8");

if("pickupId".equals(fName)){

pickupAppeal.setPickupId(fValue.toString());

}else if("cause".equals(fName)){

pickupAppeal.setCause(fValue.toString());

}else if("connectname".equals(fName)){

pickupAppeal.setConnectname(fValue.toString());

}else if("connectphone".equals(fName)){

pickupAppeal.setConnectphone(fValue.toString());

}else if("details".equals(fName)){

pickupAppeal.setDetails(fValue.toString());

}else if("status".equals(fName)){

String status = fValue.toString();

BigDecimal big = new BigDecimal(status);

pickupAppeal.setStatus(big);

}

//map.put(fName, fValue);

} else { // 获取上传文件的值

fName = fItem.getFieldName();

fValue = fItem.getInputStream();

String name = fItem.getName();

if (name != null && !("".equals(name))) {

name = name.substring(name.lastIndexOf(File.separator) + 1);

int lenN =name.indexOf(".");

String suf = name.substring(lenN, name.length());

String day = DateUtil.format(Calendar.getInstance().getTime(),

"yyyy-MM-dd");

String path = PATH+File.separator+day;

directory = new File(path);

if (!directory.exists()) {

directory.mkdirs();

}

String preFile =UUID.randomUUID().toString().replace("-", "");

String filePath = path + File.separator+preFile+suf;

String serverPath = day+ File.separator+preFile+suf;

map.put(fName, serverPath);

map.put(fName+"m", name);

is = fItem.getInputStream();

fos = new FileOutputStream(filePath);

byte[] buffer = new byte[1024];

int len = 0;

while ((len = is.read(buffer, 0, 1024)) != -1) {

fos.write(buffer, 0, len);

}

fos.flush();

}

}

}

} catch (Exception e) {

pLog.error("接收参数错误:" + e.getMessage());

resultInfo.setStatus(ComStatus.RESULTINFO_STATUS_FAILE);

resultInfo.setMessage(ComStatus.RESULTINFO_MESSAGE_FAILE);

resultInfo.setData(map);

resReslt.setResData(resultInfo);

} finally {

try {

if (null != fos) {

fos.close();

}

if (null != is) {

is.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐