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

Posted cxchanpin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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?

?

?

?

???

?

???

????

?

?

?

[email protected]?

??

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[email protected]?,??

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: ?

?[*?\[email protected]/[email protected],?|@\$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臸[email protected]?‘ u\-????

??

?

CN,?

?

??-?

[email protected]?+"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???;[email protected]?

??

^[email protected]??K?

????

PI??4?

qM|?

?V?

?

h[Ld?

?R????or?

U?M??)_?J?^S?

41n }[email protected]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 = 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();
			}
		}






??
??
??

以上是关于Android实战----基于Retrofit实现多图片/文件图文上传的主要内容,如果未能解决你的问题,请参考以下文章

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

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

Android实战----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的

Android实战----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的

Android实战----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的

Android网络实战篇——OkHttp3(Retrofit2)五种缓存模式的实现